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 10 times with exponential backoff (At first after 5s, then 2m, 30m, 2h and then the rest every 12h). 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.
Console Client Webhook Pn

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)
}