Your endpoint must respond with HTTP 200 to acknowledge successful receipt. If the request fails due to a network error, exceeds 30 seconds to complete or returns any status code other than 200, the system will retry the request up to 5 times with exponential backoff. After the final attempt, the event will be discarded.

Webhook event Payload

  • application_user_id The application user ID you’ve provided when getting the access token
  • timestamp Time of the event
  • event_type
    • record_change new or updated data recieved from the provider
    • provider_integration_created user has integrated with a provider
    • provider_integration_deleted user integration has been deleted
  • metrics metrics involved with the event
  • activity_types activity types if any involved with the event
  • provider_slug provider triggering the event
  • earliest_record_start_at earliest timestamp of records involved with the event in ISO 8601 format
  • latest_record_end_at latest timestamp of records involved with the event in ISO 8601 format

Example Payload

[
  {
    "application_user_id": "User1",
    "timestamp": "2025-04-15T13:33:55.271331177Z",
    "event_type": "record_change",
    "metrics": ["calories_burned_active", "distance", "steps"],
    "activity_types": ["sedentary", "walking"],
    "provider_slug": "garmin",
    "earliest_record_start_at": "2025-04-15T09:30:00Z",
    "latest_record_end_at": "2025-04-15T13:33:00Z"
  },
  {
    "application_user_id": "User2",
    "timestamp": "2025-04-15T13:33:55.271331177Z",
    "event_type": "record_change",
    "metrics": ["calories_burned_active"],
    "activity_types": ["walking"],
    "provider_slug": "garmin",
    "earliest_record_start_at": "2025-04-15T09:30:00Z",
    "latest_record_end_at": "2025-04-15T13:33:00Z"
  }
]

Signature

Each webhook event is signed using an HMAC-SHA256 signature for verification. The signature is included in the X-Body-Signature header.

The signature is computed by signing the raw request body as-is using a shared secret key. You can retrieve this key from the admin console.

To verify authenticity:

  1. Compute the HMAC-SHA256 hash of the request body using the shared key.
  2. Compare the result to the value in the X-Body-Signature header.

Code Examples

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"
)

// PushEvent represents a webhook event from the health data provider
const hmacKey = "HMAC_KEY_FROM_ADMIN_CONSOLE"

type PushEvent struct {
	ApplicationUserID     string    `json:"application_user_id"`      // ID of the application user
	Timestamp             time.Time `json:"timestamp"`                // Event timestamp
	EventType             string    `json:"event_type"`               // Type of event
	Metrics               []string  `json:"metrics"`                  // List of metrics
	ActivityTypes         []string  `json:"activity_types"`           // List of activity types
	ProviderSlug          string    `json:"provider_slug"`            // Provider identifier
	EarliestRecordStartAt time.Time `json:"earliest_record_start_at"` // Start of data range
	LatestRecordEndAt     time.Time `json:"latest_record_end_at"`     // End of data range
}

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// Verify HMAC signature
		signature := r.Header.Get("X-Body-Signature")
		if signature == "" {
			http.Error(w, "Missing signature", http.StatusBadRequest)
			return
		}

		// Read and verify request body
		body, err := io.ReadAll(r.Body)
		if err != nil {
			http.Error(w, "Failed to read body", http.StatusInternalServerError)
			return
		}

		// Calculate HMAC
		hm := hmac.New(sha256.New, []byte(hmacKey))
		hm.Write(body)
		if signature != hex.EncodeToString(hm.Sum(nil)) {
			http.Error(w, "Invalid signature", http.StatusUnauthorized)
			return
		}

		// Parse events
		var events []PushEvent
		if err := json.Unmarshal(body, &events); err != nil {
			http.Error(w, "Invalid JSON", http.StatusBadRequest)
			return
		}

		// Process events
		for _, event := range events {
			fmt.Printf("Received event: %+v\n", event)
		}

		w.WriteHeader(http.StatusOK)
		w.Write([]byte("OK"))
	})

	fmt.Println("Starting server on port 8000")
	http.ListenAndServe(":8000", nil)
}