Skip to content
AF
Reference

Wearable webhook payloads, as each vendor documents them

31 webhook payload examples from the documentation of Oura, WHOOP, Strava, Polar AccessLink, Withings, Wahoo, Terra and Google Health API, each reproduced as published with a link to the page it came from, plus a JSON Schema for each built from the fields the vendor documents. Each vendor's section states its signature scheme, the response it expects and its retry behavior in its current wording; Terra, for example, now says it retries "with exponential backoff for several hours". A zero-dependency Node script posts any of these examples, correctly signed, to a local URL so you can test your handler. Read on 2026-11-05.

At a glance

VendorPayloadsSignatureRetries
Oura1HMAC-SHA256 keyed by your client secret over the x-oura-timestamp value followed by JSON.stringify(body), hex, upper-case, in x-oura-signature.10 retries each for 4xx responses, 5xx responses and timeouts, for about an hour; a 410 response cancels the subscription.
WHOOP2Base64 of HMAC-SHA256, keyed by your client secret, over the X-WHOOP-Signature-Timestamp value (milliseconds since epoch) followed by the raw body, in X-WHOOP-Signature.Five retries over about one hour; anything but a 2XX, or no response before the timeout, counts as a failure.
Strava1No signature is documented for event POSTs. Subscription validation is a GET with hub.mode, hub.challenge and hub.verify_token; echo hub.challenge as JSON.Retried up to a total of three attempts if a 200 is not returned.
Polar AccessLink8HMAC-SHA256 of the payload, keyed by the signature_secret_key returned once when you create the webhook, hex, in Polar-Webhook-Signature; the event type is in Polar-Webhook-Event.No retry schedule is documented. A webhook whose deliveries fail for seven days is deactivated; re-enable it with the activate endpoint, which pings the URL again.
Withings8No body signature. Append a secret query parameter to the callback URL (Withings' example: ?token=<random-secret>) and check it, plus an allowlist of the IP ranges Withings publishes as DNS TXT records.If your endpoint does not return HTTP < 400: 5 retry cycles over about 5 hours (+10 s, +1 min, +1 h, +4 h), 2 HTTP attempts each. After 20 days of unresolved failures the subscription is canceled; missed notifications are not redelivered.
Wahoo1No signature. Wahoo sends the webhook_token you set in the app's webhook settings inside the JSON body; ignore any request without it.Without an HTTP 200, retried after 30 minutes, 4 hours, 24 hours and 72 hours.
Terra5terra-signature: t=<timestamp>,v1=<signature>; HMAC-SHA256 keyed by the webhook's signing secret over the timestamp, a "." and the raw body. Ignore schemes other than v1.Retried automatically with exponential backoff for several hours; Terra says the number and timing of retries may change without notice. After the retry budget is spent the event is dropped, with no notification.
Google Health API5Google sends the endpointAuthorization secret you configured in the Authorization header. It also signs the raw JSON with Tink (ECDSA P-256, SHA-256), base64 in GOOGLE-HEALTH-API-SIGNATURE, keys rotated every 30 days and published as a JSON keyset.Anything other than 204, or a timeout, is retried later. Pending notifications are stored for up to 7 days and retried with exponential backoff; older ones are discarded.
Suunto0Not documented on Suunto's FAQ. The webhook address is set in your app's OAuth settings in Suunto API Zone and must be publicly reachable.Not documented on the pages read here.

Oura

Signature
HMAC-SHA256 keyed by your client secret over the x-oura-timestamp value followed by JSON.stringify(body), hex, upper-case, in x-oura-signature. Source
Respond
Respond with a 2xx in under 10 seconds and process the event asynchronously. Before events flow, Oura verifies the callback with a GET carrying verification_token and challenge; echo the challenge as JSON. Source
Retries
10 retries each for 4xx responses, 5xx responses and timeouts, for about an hour; a 410 response cancels the subscription. Source
Note
4xx responses are retried too. Source
Note
One subscription per event_type and data_type pair; the event carries the object_id to fetch. Source

Verify a Oura delivery · Oura integration guide

Data event

Oura sends a POST to your callback URL when subscribed data changes (the example: a user syncs new sleep data).

Headers in the example

x-oura-signature: HMAC_SIGNATURE
x-oura-timestamp: 1234567890

Example body

{
  "event_type": "update",
  "data_type": "sleep",
  "object_id": "12345abc",
  "event_time": "2023-01-01T08:00:00+00:00",
  "user_id": "user123"
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "oura-event",
  "title": "Oura webhook: Data event",
  "description": "Built from the fields Oura documents at https://cloud.ouraring.com/v2/static/json/openapi-1.43.json. JSON body.",
  "type": "object",
  "properties": {
    "event_type": {
      "description": "create, update or delete (Oura's WebhookOperation).",
      "type": "string",
      "enum": [
        "create",
        "update",
        "delete"
      ],
      "x-type-from": "documented"
    },
    "data_type": {
      "description": "One of the data types partners can subscribe to (Oura's ExtApiV2DataType).",
      "type": "string",
      "enum": [
        "tag",
        "enhanced_tag",
        "workout",
        "session",
        "sleep",
        "daily_sleep",
        "daily_readiness",
        "daily_activity",
        "daily_spo2",
        "sleep_time",
        "rest_mode_period",
        "ring_configuration",
        "daily_stress",
        "daily_cardiovascular_age",
        "daily_resilience",
        "vo2_max"
      ],
      "x-type-from": "documented"
    },
    "object_id": {
      "description": "Use it to fetch the complete record from the API.",
      "type": "string",
      "x-type-from": "example"
    },
    "event_time": {
      "type": "string",
      "x-type-from": "example"
    },
    "user_id": {
      "type": "string",
      "x-type-from": "example"
    }
  },
  "additionalProperties": true
}

From Oura API v2 OpenAPI document (1.43), “Webhooks for Real-Time Data Updates”, read 2026-11-05 (via the site's source-snapshot workflow).

WHOOP

Signature
Base64 of HMAC-SHA256, keyed by your client secret, over the X-WHOOP-Signature-Timestamp value (milliseconds since epoch) followed by the raw body, in X-WHOOP-Signature. Source
Respond
Return a 2XX, ideally within a second, and process on a queue. Every event type goes to every configured URL; answer 2XX to the ones you ignore. Source
Retries
Five retries over about one hour; anything but a 2XX, or no response before the timeout, counts as a failure. Source
Note
Duplicates happen: deduplicate on trace_id. Source
Note
Creates arrive as update events. Source
Note
v1 webhooks are no longer published; v2 uses UUIDs. Source

Verify a WHOOP delivery · WHOOP integration guide

v2 example: sleep.updated

User 456 records a sleep and WHOOP posts to your v2 webhook endpoint; you then GET v2/activity/sleep/{id}.

Example body

{
  "user_id": 456,
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "type": "sleep.updated",
  "trace_id": "e369c784-5100-49e8-8098-75d35c47b31b"
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "whoop-v2-sleep-updated",
  "title": "WHOOP webhook: v2 example: sleep.updated",
  "description": "Built from the fields WHOOP documents at https://developer.whoop.com/docs/developing/webhooks/. JSON body.",
  "type": "object",
  "properties": {
    "user_id": {
      "description": "The WHOOP User for the event",
      "type": "integer",
      "x-type-from": "documented"
    },
    "id": {
      "description": "Identifier of the object that triggered this webhook. For v2 webhooks a UUID; for v2 recovery events, the UUID of the sleep the recovery belongs to.",
      "type": [
        "integer",
        "string"
      ],
      "x-type-from": "documented"
    },
    "type": {
      "description": "The type of event that triggered this webhook",
      "type": "string",
      "enum": [
        "workout.updated",
        "workout.deleted",
        "sleep.updated",
        "sleep.deleted",
        "recovery.updated",
        "recovery.deleted"
      ],
      "x-type-from": "documented"
    },
    "trace_id": {
      "description": "Trace ID for the event that triggered this webhook",
      "type": "string",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From WHOOP Developer Platform, “Webhooks”, read 2026-11-05 (via the site's source-snapshot workflow).

Field-table example: workout.updated

The example beside WHOOP's field table, with integer ids (the table documents id as int64 or string).

Example body

{
  "user_id": 10129,
  "id": 10235,
  "type": "workout.updated",
  "trace_id": "d3709ee7-104e-4f70-a928-2932964b017b"
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "whoop-spec-workout-updated",
  "title": "WHOOP webhook: Field-table example: workout.updated",
  "description": "Built from the fields WHOOP documents at https://developer.whoop.com/docs/developing/webhooks/. JSON body.",
  "type": "object",
  "properties": {
    "user_id": {
      "description": "The WHOOP User for the event",
      "type": "integer",
      "x-type-from": "documented"
    },
    "id": {
      "description": "Identifier of the object that triggered this webhook. For v2 webhooks a UUID; for v2 recovery events, the UUID of the sleep the recovery belongs to.",
      "type": [
        "integer",
        "string"
      ],
      "x-type-from": "documented"
    },
    "type": {
      "description": "The type of event that triggered this webhook",
      "type": "string",
      "enum": [
        "workout.updated",
        "workout.deleted",
        "sleep.updated",
        "sleep.deleted",
        "recovery.updated",
        "recovery.deleted"
      ],
      "x-type-from": "documented"
    },
    "trace_id": {
      "description": "Trace ID for the event that triggered this webhook",
      "type": "string",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From WHOOP Developer Platform, “Webhooks”, read 2026-11-05 (via the site's source-snapshot workflow).

Strava

Signature
No signature is documented for event POSTs. Subscription validation is a GET with hub.mode, hub.challenge and hub.verify_token; echo hub.challenge as JSON. Source
Respond
Acknowledge each POST with 200 OK within two seconds; do further work asynchronously. Source
Retries
Retried up to a total of three attempts if a 200 is not returned. Source
Note
One subscription per application, covering every athlete who authorized it. Source
Note
One save can produce several events. Source

Verify a Strava delivery · Strava integration guide

Activity update

Sent when an activity is created or deleted, when its title, type or privacy changes, and when an athlete revokes access (updates then holds "authorized": "false").

Example body

{
  "aspect_type": "update",
  "event_time": 1516126040,
  "object_id": 1360128428,
  "object_type": "activity",
  "owner_id": 134815,
  "subscription_id": 120475,
  "updates": {
    "title": "Messy"
  }
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "strava-activity-update",
  "title": "Strava webhook: Activity update",
  "description": "Built from the fields Strava documents at https://developers.strava.com/docs/webhooks/. JSON body.",
  "type": "object",
  "properties": {
    "object_type": {
      "description": "Always either \"activity\" or \"athlete.\"",
      "type": "string",
      "enum": [
        "activity",
        "athlete"
      ],
      "x-type-from": "documented"
    },
    "object_id": {
      "description": "For activity events, the activity's ID. For athlete events, the athlete's ID.",
      "type": "integer",
      "x-type-from": "documented"
    },
    "aspect_type": {
      "description": "Always \"create,\" \"update,\" or \"delete.\"",
      "type": "string",
      "enum": [
        "create",
        "update",
        "delete"
      ],
      "x-type-from": "documented"
    },
    "updates": {
      "description": "For activity updates: title, type and private. For app deauthorization events: always \"authorized\": \"false\".",
      "type": "object",
      "x-type-from": "documented"
    },
    "owner_id": {
      "description": "The athlete's ID.",
      "type": "integer",
      "x-type-from": "documented"
    },
    "subscription_id": {
      "description": "The push subscription ID that is receiving this event.",
      "type": "integer",
      "x-type-from": "documented"
    },
    "event_time": {
      "description": "The time that the event occurred.",
      "type": "integer",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Strava Developers, “Webhook Events API”, read 2026-11-05 (via the site's source-snapshot workflow).

Polar AccessLink

Signature
HMAC-SHA256 of the payload, keyed by the signature_secret_key returned once when you create the webhook, hex, in Polar-Webhook-Signature; the event type is in Polar-Webhook-Event. Source
Respond
Answer with 200. The PING sent when a webhook is created or its URL changes must get 200 OK, or the change is not made. Source
Retries
No retry schedule is documented. A webhook whose deliveries fail for seven days is deactivated; re-enable it with the activate endpoint, which pings the URL again. Source
Note
A client application can have only one webhook. Source

Verify a Polar AccessLink delivery · Polar AccessLink integration guide

PING

Sent as an HTTP POST when a webhook is created or modified, to verify the URL.

Headers in the example

Polar-Webhook-Event: PING

Example body

{
  "timestamp": "2018-05-15T14:22:24Z",
  "event": "PING"
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "polar-ping",
  "title": "Polar AccessLink webhook: PING",
  "description": "Built from the fields Polar AccessLink documents at https://www.polar.com/accesslink-api/. JSON body.",
  "type": "object",
  "properties": {
    "timestamp": {
      "type": "string",
      "format": "date-time",
      "x-type-from": "example"
    },
    "event": {
      "type": "string",
      "x-type-from": "example"
    }
  },
  "additionalProperties": true
}

From Polar AccessLink API v3, “Accesslink webhooks” and data models, read 2026-11-05 (via the site's source-snapshot workflow).

Exercise

This event is sent when new exercise data is available for the user.

Headers in the example

Polar-Webhook-Event: EXERCISE

Example body

{
  "event": "EXERCISE",
  "user_id": 475,
  "entity_id": "aQlC83",
  "timestamp": "2018-05-15T14:22:24Z",
  "url": "https://www.polaraccesslink.com/v3/exercises/aQlC83"
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "polar-exercise",
  "title": "Polar AccessLink webhook: Exercise",
  "description": "Built from the fields Polar AccessLink documents at https://www.polar.com/accesslink-api/. JSON body.",
  "type": "object",
  "properties": {
    "event": {
      "description": "Type of available data.",
      "type": "string",
      "x-type-from": "documented"
    },
    "user_id": {
      "description": "Id of the user who has new data.",
      "type": "integer",
      "x-type-from": "documented"
    },
    "entity_id": {
      "description": "Id of the available data.",
      "type": "string",
      "x-type-from": "documented"
    },
    "timestamp": {
      "description": "Time when webhook notification is sent.",
      "type": "string",
      "format": "date-time",
      "x-type-from": "documented"
    },
    "url": {
      "description": "Url to the new available data.",
      "type": "string",
      "format": "uri",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Polar AccessLink API v3, “Accesslink webhooks” and data models, read 2026-11-05 (via the site's source-snapshot workflow).

Sleep

This event is sent when new sleep data is available for the user.

Headers in the example

Polar-Webhook-Event: SLEEP

Example body

{
  "event": "SLEEP",
  "user_id": 475,
  "date": "2019-12-31",
  "timestamp": "2018-05-15T14:22:24Z",
  "url": "https://www.polaraccesslink.com/v3/users/sleep/2019-12-31"
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "polar-sleep",
  "title": "Polar AccessLink webhook: Sleep",
  "description": "Built from the fields Polar AccessLink documents at https://www.polar.com/accesslink-api/. JSON body.",
  "type": "object",
  "properties": {
    "event": {
      "description": "Type of available data.",
      "type": "string",
      "x-type-from": "documented"
    },
    "user_id": {
      "description": "Id of the user who has new data.",
      "type": "integer",
      "x-type-from": "documented"
    },
    "date": {
      "description": "Date of the available sleep data.",
      "type": "string",
      "x-type-from": "documented"
    },
    "timestamp": {
      "description": "Time when webhook notification is sent.",
      "type": "string",
      "format": "date-time",
      "x-type-from": "documented"
    },
    "url": {
      "description": "Url to the new available data.",
      "type": "string",
      "format": "uri",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Polar AccessLink API v3, “Accesslink webhooks” and data models, read 2026-11-05 (via the site's source-snapshot workflow).

Continuous heart rate

This event is sent when new continuous heart rate data is available for the user.

Headers in the example

Polar-Webhook-Event: CONTINUOUS_HEART_RATE

Example body

{
  "event": "CONTINUOUS_HEART_RATE",
  "user_id": 475,
  "date": "2022-09-30",
  "timestamp": "2018-05-15T14:22:24Z",
  "url": "https://www.polaraccesslink.com/v3/users/continuous-heart-rate/2022-09-30"
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "polar-continuous_heart_rate",
  "title": "Polar AccessLink webhook: Continuous heart rate",
  "description": "Built from the fields Polar AccessLink documents at https://www.polar.com/accesslink-api/. JSON body.",
  "type": "object",
  "properties": {
    "event": {
      "description": "Type of available data.",
      "type": "string",
      "x-type-from": "documented"
    },
    "user_id": {
      "description": "Id of the user who has new data.",
      "type": "integer",
      "x-type-from": "documented"
    },
    "date": {
      "description": "Date of the available continuous hr data.",
      "type": "string",
      "x-type-from": "documented"
    },
    "timestamp": {
      "description": "Time when webhook notification is sent.",
      "type": "string",
      "format": "date-time",
      "x-type-from": "documented"
    },
    "url": {
      "description": "Url to the new available data.",
      "type": "string",
      "format": "uri",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Polar AccessLink API v3, “Accesslink webhooks” and data models, read 2026-11-05 (via the site's source-snapshot workflow).

SleepWise circadian bedtime

This event is sent when new circadian bedtime data is available for the user.

Headers in the example

Polar-Webhook-Event: SLEEP_WISE_CIRCADIAN_BEDTIME

Example body

{
  "event": "SLEEP_WISE_CIRCADIAN_BEDTIME",
  "user_id": 475,
  "from": "2022-09-30",
  "to": "2022-10-02",
  "timestamp": "2022-10-02T14:22:24Z",
  "url": "https://www.polaraccesslink.com/v3/users/sleepwise/circadian-bedtime/date?from=2022-09-30&to=2022-10-02"
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "polar-sleep_wise_circadian_bedtime",
  "title": "Polar AccessLink webhook: SleepWise circadian bedtime",
  "description": "Built from the fields Polar AccessLink documents at https://www.polar.com/accesslink-api/. JSON body.",
  "type": "object",
  "properties": {
    "event": {
      "description": "Type of available data.",
      "type": "string",
      "x-type-from": "documented"
    },
    "user_id": {
      "description": "Id of the user who has new data.",
      "type": "integer",
      "x-type-from": "documented"
    },
    "from": {
      "description": "Start date of the available circadian bedtime data.",
      "type": "string",
      "x-type-from": "documented"
    },
    "to": {
      "description": "End date of the available circadian bedtime data.",
      "type": "string",
      "x-type-from": "documented"
    },
    "timestamp": {
      "description": "Time when webhook notification is sent.",
      "type": "string",
      "format": "date-time",
      "x-type-from": "documented"
    },
    "url": {
      "description": "Url to the new available data.",
      "type": "string",
      "format": "uri",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Polar AccessLink API v3, “Accesslink webhooks” and data models, read 2026-11-05 (via the site's source-snapshot workflow).

SleepWise alertness

This event is sent when new alertness data is available for the user.

Headers in the example

Polar-Webhook-Event: SLEEP_WISE_ALERTNESS

Example body

{
  "event": "SLEEP_WISE_ALERTNESS",
  "user_id": 475,
  "from": "2022-09-30",
  "to": "2022-10-02",
  "timestamp": "2022-10-02T14:22:24Z",
  "url": "https://www.polaraccesslink.com/v3/users/sleepwise/alertness/date?from=2022-09-30&to=2022-10-02"
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "polar-sleep_wise_alertness",
  "title": "Polar AccessLink webhook: SleepWise alertness",
  "description": "Built from the fields Polar AccessLink documents at https://www.polar.com/accesslink-api/. JSON body.",
  "type": "object",
  "properties": {
    "event": {
      "description": "Type of available data.",
      "type": "string",
      "x-type-from": "documented"
    },
    "user_id": {
      "description": "Id of the user who has new data.",
      "type": "integer",
      "x-type-from": "documented"
    },
    "from": {
      "description": "Start date of the available alertness data.",
      "type": "string",
      "x-type-from": "documented"
    },
    "to": {
      "description": "End date of the available alertness data.",
      "type": "string",
      "x-type-from": "documented"
    },
    "timestamp": {
      "description": "Time when webhook notification is sent.",
      "type": "string",
      "format": "date-time",
      "x-type-from": "documented"
    },
    "url": {
      "description": "Url to the new available data.",
      "type": "string",
      "format": "uri",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Polar AccessLink API v3, “Accesslink webhooks” and data models, read 2026-11-05 (via the site's source-snapshot workflow).

Activity summary

This event is sent when new activity summary data is available for the user.

Headers in the example

Polar-Webhook-Event: ACTIVITY_SUMMARY

Example body

{
  "event": "ACTIVITY_SUMMARY",
  "user_id": 475,
  "date": "2022-09-30",
  "timestamp": "2022-10-02T14:22:24Z",
  "url": "https://www.polaraccesslink.com/v3/users/activities/2022-09-30"
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "polar-activity_summary",
  "title": "Polar AccessLink webhook: Activity summary",
  "description": "Built from the fields Polar AccessLink documents at https://www.polar.com/accesslink-api/. JSON body.",
  "type": "object",
  "properties": {
    "event": {
      "description": "Type of available data.",
      "type": "string",
      "x-type-from": "documented"
    },
    "user_id": {
      "description": "Id of the user who has new data.",
      "type": "integer",
      "x-type-from": "documented"
    },
    "date": {
      "description": "Date of the available activity summary data.",
      "type": "string",
      "x-type-from": "documented"
    },
    "timestamp": {
      "description": "Time when webhook notification is sent.",
      "type": "string",
      "format": "date-time",
      "x-type-from": "documented"
    },
    "url": {
      "description": "Url to the new available data.",
      "type": "string",
      "format": "uri",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Polar AccessLink API v3, “Accesslink webhooks” and data models, read 2026-11-05 (via the site's source-snapshot workflow).

Physical information

This event is sent when new physical information data is available for the user.

Headers in the example

Polar-Webhook-Event: PHYSICAL_INFORMATION

Example body

{
  "event": "PHYSICAL_INFORMATION",
  "user_id": 475,
  "timestamp": "2025-12-31T14:22:24Z",
  "url": "https://www.polaraccesslink.com/v3/users/physical-info"
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "polar-physical_information",
  "title": "Polar AccessLink webhook: Physical information",
  "description": "Built from the fields Polar AccessLink documents at https://www.polar.com/accesslink-api/. JSON body.",
  "type": "object",
  "properties": {
    "event": {
      "description": "Type of available data.",
      "type": "string",
      "x-type-from": "documented"
    },
    "user_id": {
      "description": "Id of the user who has new data.",
      "type": "integer",
      "x-type-from": "documented"
    },
    "timestamp": {
      "description": "Time when webhook notification is sent.",
      "type": "string",
      "format": "date-time",
      "x-type-from": "documented"
    },
    "url": {
      "description": "Url to the new available data.",
      "type": "string",
      "format": "uri",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Polar AccessLink API v3, “Accesslink webhooks” and data models, read 2026-11-05 (via the site's source-snapshot workflow).

Withings

Signature
No body signature. Append a secret query parameter to the callback URL (Withings' example: ?token=<random-secret>) and check it, plus an allowlist of the IP ranges Withings publishes as DNS TXT records. Source
Respond
An application/x-www-form-urlencoded POST; return HTTP 2xx within a few seconds, then fetch the data from the Data API. Source
Retries
If your endpoint does not return HTTP < 400: 5 retry cycles over about 5 hours (+10 s, +1 min, +1 h, +4 h), 2 HTTP attempts each. After 20 days of unresolved failures the subscription is canceled; missed notifications are not redelivered. Source
Note
Missed notifications are not redelivered: backfill with lastupdate. Source
Note
Typical delay is under 2 minutes, with no real-time SLA on the standard plan. Source

Verify a Withings delivery · Withings integration guide

appli 1: Weight & body composition

A new weight or body composition measurement was recorded by a Withings scale.

Example body (form-encoded fields)

userid=12345
appli=1
startdate=1530576000
enddate=1530698753
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "withings-appli-1",
  "title": "Withings webhook: appli 1: Weight & body composition",
  "description": "Built from the fields Withings documents at https://developer.withings.com/developer-guide/v3/integration-guide/public-health-data-api/data-api/notifications/notification-content/. The payload is application/x-www-form-urlencoded; this schema describes the parsed key/value object.",
  "type": "object",
  "properties": {
    "userid": {
      "description": "The user this notification concerns.",
      "type": "string",
      "x-documented-type": "int",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "appli": {
      "description": "The notification category.",
      "type": "string",
      "x-documented-type": "int",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "startdate": {
      "description": "Start of the data range.",
      "type": "string",
      "x-documented-type": "int (unix epoch)",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "enddate": {
      "description": "End of the data range.",
      "type": "string",
      "x-documented-type": "int (unix epoch)",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Withings developer guide, “Notification categories”, read 2026-11-05 (via the site's source-snapshot workflow).

appli 4: Blood pressure & heart rate

A new blood pressure measurement (and heart rate, when supported) was recorded.

Example body (form-encoded fields)

userid=12345
appli=4
startdate=1530576000
enddate=1530576120
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "withings-appli-4",
  "title": "Withings webhook: appli 4: Blood pressure & heart rate",
  "description": "Built from the fields Withings documents at https://developer.withings.com/developer-guide/v3/integration-guide/public-health-data-api/data-api/notifications/notification-content/. The payload is application/x-www-form-urlencoded; this schema describes the parsed key/value object.",
  "type": "object",
  "properties": {
    "userid": {
      "description": "The user this notification concerns.",
      "type": "string",
      "x-documented-type": "int",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "appli": {
      "description": "The notification category.",
      "type": "string",
      "x-documented-type": "int",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "startdate": {
      "description": "Start of the data range.",
      "type": "string",
      "x-documented-type": "int (unix epoch)",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "enddate": {
      "description": "End of the data range.",
      "type": "string",
      "x-documented-type": "int (unix epoch)",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Withings developer guide, “Notification categories”, read 2026-11-05 (via the site's source-snapshot workflow).

appli 16: Activity

New activity data is available — steps, distance, calories, intensity, or workouts.

Example body (form-encoded fields)

userid=12345
appli=16
date=2018-07-02
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "withings-appli-16",
  "title": "Withings webhook: appli 16: Activity",
  "description": "Built from the fields Withings documents at https://developer.withings.com/developer-guide/v3/integration-guide/public-health-data-api/data-api/notifications/notification-content/. The payload is application/x-www-form-urlencoded; this schema describes the parsed key/value object.",
  "type": "object",
  "properties": {
    "userid": {
      "description": "The user this notification concerns.",
      "type": "string",
      "x-documented-type": "int",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "appli": {
      "description": "The notification category.",
      "type": "string",
      "x-documented-type": "int",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "date": {
      "description": "Single date for event-based notifications.",
      "type": "string",
      "x-documented-type": "string (YYYY-MM-DD) or int (unix epoch)",
      "pattern": "^([0-9]{4}-[0-9]{2}-[0-9]{2}|-?[0-9]+)$",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Withings developer guide, “Notification categories”, read 2026-11-05 (via the site's source-snapshot workflow).

appli 44: Sleep summary

New sleep data is available — duration, sleep state, deep/light state duration (no REM), and wakeup counts.

Example body (form-encoded fields)

userid=12345
appli=44
startdate=1530576000
enddate=1530604800
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "withings-appli-44",
  "title": "Withings webhook: appli 44: Sleep summary",
  "description": "Built from the fields Withings documents at https://developer.withings.com/developer-guide/v3/integration-guide/public-health-data-api/data-api/notifications/notification-content/. The payload is application/x-www-form-urlencoded; this schema describes the parsed key/value object.",
  "type": "object",
  "properties": {
    "userid": {
      "description": "The user this notification concerns.",
      "type": "string",
      "x-documented-type": "int",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "appli": {
      "description": "The notification category.",
      "type": "string",
      "x-documented-type": "int",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "startdate": {
      "description": "Start of the data range.",
      "type": "string",
      "x-documented-type": "int (unix epoch)",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "enddate": {
      "description": "End of the data range.",
      "type": "string",
      "x-documented-type": "int (unix epoch)",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Withings developer guide, “Notification categories”, read 2026-11-05 (via the site's source-snapshot workflow).

appli 54: ECG measurement

A new ECG recording was completed.

Example body (form-encoded fields)

userid=12345
appli=54
startdate=1530576000
enddate=1530576030
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "withings-appli-54",
  "title": "Withings webhook: appli 54: ECG measurement",
  "description": "Built from the fields Withings documents at https://developer.withings.com/developer-guide/v3/integration-guide/public-health-data-api/data-api/notifications/notification-content/. The payload is application/x-www-form-urlencoded; this schema describes the parsed key/value object.",
  "type": "object",
  "properties": {
    "userid": {
      "description": "The user this notification concerns.",
      "type": "string",
      "x-documented-type": "int",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "appli": {
      "description": "The notification category.",
      "type": "string",
      "x-documented-type": "int",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "startdate": {
      "description": "Start of the data range.",
      "type": "string",
      "x-documented-type": "int (unix epoch)",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "enddate": {
      "description": "End of the data range.",
      "type": "string",
      "x-documented-type": "int (unix epoch)",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Withings developer guide, “Notification categories”, read 2026-11-05 (via the site's source-snapshot workflow).

appli 55: ECG measurement failed

An ECG recording was attempted but failed; notification only.

Example body (form-encoded fields)

userid=12345
appli=55
date=1530576000
deviceid=abc123
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "withings-appli-55",
  "title": "Withings webhook: appli 55: ECG measurement failed",
  "description": "Built from the fields Withings documents at https://developer.withings.com/developer-guide/v3/integration-guide/public-health-data-api/data-api/notifications/notification-content/. The payload is application/x-www-form-urlencoded; this schema describes the parsed key/value object.",
  "type": "object",
  "properties": {
    "userid": {
      "description": "The user this notification concerns.",
      "type": "string",
      "x-documented-type": "int",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "appli": {
      "description": "The notification category.",
      "type": "string",
      "x-documented-type": "int",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "date": {
      "description": "Single date for event-based notifications.",
      "type": "string",
      "x-documented-type": "string (YYYY-MM-DD) or int (unix epoch)",
      "pattern": "^([0-9]{4}-[0-9]{2}-[0-9]{2}|-?[0-9]+)$",
      "x-type-from": "documented"
    },
    "deviceid": {
      "description": "Source device, when applicable.",
      "type": "string",
      "x-documented-type": "string",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Withings developer guide, “Notification categories”, read 2026-11-05 (via the site's source-snapshot workflow).

appli 50: Bed in

The user lay down on the bed (Withings Sleep sensor); notification only.

Example body (form-encoded fields)

userid=12345
appli=50
date=1530576000
deviceid=abc123
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "withings-appli-50",
  "title": "Withings webhook: appli 50: Bed in",
  "description": "Built from the fields Withings documents at https://developer.withings.com/developer-guide/v3/integration-guide/public-health-data-api/data-api/notifications/notification-content/. The payload is application/x-www-form-urlencoded; this schema describes the parsed key/value object.",
  "type": "object",
  "properties": {
    "userid": {
      "description": "The user this notification concerns.",
      "type": "string",
      "x-documented-type": "int",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "appli": {
      "description": "The notification category.",
      "type": "string",
      "x-documented-type": "int",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "date": {
      "description": "Single date for event-based notifications.",
      "type": "string",
      "x-documented-type": "string (YYYY-MM-DD) or int (unix epoch)",
      "pattern": "^([0-9]{4}-[0-9]{2}-[0-9]{2}|-?[0-9]+)$",
      "x-type-from": "documented"
    },
    "deviceid": {
      "description": "Source device, when applicable.",
      "type": "string",
      "x-documented-type": "string",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Withings developer guide, “Notification categories”, read 2026-11-05 (via the site's source-snapshot workflow).

appli 46: User profile change

The user account was deleted, unlinked from your app, or updated; notification only.

Example body (form-encoded fields)

userid=12345
appli=46
action=update
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "withings-appli-46",
  "title": "Withings webhook: appli 46: User profile change",
  "description": "Built from the fields Withings documents at https://developer.withings.com/developer-guide/v3/integration-guide/public-health-data-api/data-api/notifications/notification-content/. The payload is application/x-www-form-urlencoded; this schema describes the parsed key/value object.",
  "type": "object",
  "properties": {
    "userid": {
      "description": "The user this notification concerns.",
      "type": "string",
      "x-documented-type": "int",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "appli": {
      "description": "The notification category.",
      "type": "string",
      "x-documented-type": "int",
      "pattern": "^-?[0-9]+$",
      "x-type-from": "documented"
    },
    "action": {
      "description": "For profile-change events: delete, unlink, or update.",
      "type": "string",
      "enum": [
        "delete",
        "unlink",
        "update"
      ],
      "x-documented-type": "string",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Withings developer guide, “Notification categories”, read 2026-11-05 (via the site's source-snapshot workflow).

Wahoo

Signature
No signature. Wahoo sends the webhook_token you set in the app's webhook settings inside the JSON body; ignore any request without it. Source
Respond
An HTTP POST with Content-Type application/json; return HTTP 200. The users need the offline_data scope. Source
Retries
Without an HTTP 200, retried after 30 minutes, 4 hours, 24 hours and 72 hours. Source
Note
Duplicates are possible when files are updated or deleted. Source

Verify a Wahoo delivery · Wahoo integration guide

Workout summary

Sent when a workout summary is available for a user whose token has the offline_data scope.

Example body

{
"event_type" : "workout_summary" ,
"webhook_token" : <webhook_token> ,
"user" : {
"id" : 60462
},
"workout_summary" : {
"id" : 8297 ,
"ascent_accum" : "450.00" ,
"cadence_avg" : "52.00" ,
"calories_accum" : "1500.00" ,
"distance_accum" : "24909.71" ,
"duration_active_accum" : "179.00" ,
"duration_paused_accum" : "85.00" ,
"duration_total_accum" : "275.20" ,
"heart_rate_avg" : "124.23" ,
"power_bike_np_last" : "150.01" ,
"power_bike_tss_last" : "304.90" ,
"power_avg" : "94.59" ,
"speed_avg" : "10.75" ,
"work_accum" : "104148000.01" ,
"created_at" : "2018-10-23T20:43:50.000Z" ,
"updated_at" : "2018-10-23T20:43:50.000Z" ,
"file" : {
"url" : "https://server.com/4_Mile_Segment_.fit"
},
"workout" : {
"id" : 56519 ,
"starts" : "2015-08-12T09:00:00.000Z" ,
"minutes" : 12 ,
"name" : "Friday Fun" ,
"created_at" : "2018-10-23T20:41:55.000Z" ,
"updated_at" : "2018-10-23T20:41:55.000Z" ,
"plan_id" : null ,
"workout_token" : "123" ,
"workout_type_id" : 40
}
}
}

Wahoo's example has the placeholder <webhook_token> where the token goes; the mock sender puts your token there and sends the rest unchanged.

JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "wahoo-workout_summary",
  "title": "Wahoo webhook: Workout summary",
  "description": "Built from the fields Wahoo documents at https://cloud-api.wahooligan.com/. JSON body.",
  "type": "object",
  "properties": {
    "event_type": {
      "type": "string",
      "x-type-from": "example"
    },
    "webhook_token": {
      "description": "The token you configured; any request without it should be ignored.",
      "type": "string",
      "x-type-from": "documented"
    },
    "user": {
      "type": "object",
      "x-type-from": "example",
      "properties": {
        "id": {
          "type": "integer",
          "x-type-from": "example"
        }
      },
      "additionalProperties": true
    },
    "workout_summary": {
      "type": "object",
      "x-type-from": "example",
      "properties": {
        "id": {
          "type": "integer",
          "x-type-from": "example"
        },
        "ascent_accum": {
          "type": "string",
          "x-type-from": "example"
        },
        "cadence_avg": {
          "type": "string",
          "x-type-from": "example"
        },
        "calories_accum": {
          "type": "string",
          "x-type-from": "example"
        },
        "distance_accum": {
          "type": "string",
          "x-type-from": "example"
        },
        "duration_active_accum": {
          "type": "string",
          "x-type-from": "example"
        },
        "duration_paused_accum": {
          "type": "string",
          "x-type-from": "example"
        },
        "duration_total_accum": {
          "type": "string",
          "x-type-from": "example"
        },
        "heart_rate_avg": {
          "type": "string",
          "x-type-from": "example"
        },
        "power_bike_np_last": {
          "type": "string",
          "x-type-from": "example"
        },
        "power_bike_tss_last": {
          "type": "string",
          "x-type-from": "example"
        },
        "power_avg": {
          "type": "string",
          "x-type-from": "example"
        },
        "speed_avg": {
          "type": "string",
          "x-type-from": "example"
        },
        "work_accum": {
          "type": "string",
          "x-type-from": "example"
        },
        "created_at": {
          "type": "string",
          "x-type-from": "example"
        },
        "updated_at": {
          "type": "string",
          "x-type-from": "example"
        },
        "file": {
          "type": "object",
          "x-type-from": "example",
          "properties": {
            "url": {
              "type": "string",
              "x-type-from": "example"
            }
          },
          "additionalProperties": true
        },
        "workout": {
          "type": "object",
          "x-type-from": "example",
          "properties": {
            "id": {
              "type": "integer",
              "x-type-from": "example"
            },
            "starts": {
              "type": "string",
              "x-type-from": "example"
            },
            "minutes": {
              "type": "integer",
              "x-type-from": "example"
            },
            "name": {
              "type": "string",
              "x-type-from": "example"
            },
            "created_at": {
              "type": "string",
              "x-type-from": "example"
            },
            "updated_at": {
              "type": "string",
              "x-type-from": "example"
            },
            "workout_token": {
              "type": "string",
              "x-type-from": "example"
            },
            "workout_type_id": {
              "type": "integer",
              "x-type-from": "example"
            }
          },
          "additionalProperties": true
        }
      },
      "additionalProperties": true
    }
  },
  "additionalProperties": true
}

From Wahoo Cloud API documentation, “Webhooks”, read 2026-11-05 (via the site's source-snapshot workflow).

Terra

Signature
terra-signature: t=<timestamp>,v1=<signature>; HMAC-SHA256 keyed by the webhook's signing secret over the timestamp, a "." and the raw body. Ignore schemes other than v1. Source
Respond
Respond within 8 seconds; accept immediately (return 200) and process asynchronously. Sustained timeouts, 5xx and repeated 4xx can trip a per-destination circuit breaker. Source
Retries
Retried automatically with exponential backoff for several hours; Terra says the number and timing of retries may change without notice. After the retry budget is spent the event is dropped, with no notification. Source
Note
Do not depend on a specific retry schedule. Source
Note
Each webhook has its own signing secret; rotating one re-signs its retries with the new secret. Source
Note
Every event carries a terra-reference header tying it to the request that caused it. Source

Verify a Terra delivery · Terra integration guide

S3 payload (ping mode)

New data records are available and the destination uses ping mode: download url before it expires and process it as a normal data event.

Example body

{
  "status": "success",
  "type": "s3_payload",
  "url": "https://example.com",
  "expires_in": 300,
  "user_id": "0d09d3a4-f6e8-4f1c-9a3e-2b6c1d5e7f80",
  "reference_id": "your-user-ref-123"
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "terra-s3_payload",
  "title": "Terra webhook: S3 payload (ping mode)",
  "description": "Built from the fields Terra documents at https://docs.tryterra.co/reference/health-and-fitness-api/event-types.md. JSON body.",
  "type": "object",
  "properties": {
    "status": {
      "type": "string",
      "x-type-from": "example"
    },
    "type": {
      "type": "string",
      "x-type-from": "example"
    },
    "url": {
      "description": "A pre-signed link to the full data payload. Download it with a GET request.",
      "type": "string",
      "format": "uri",
      "x-type-from": "documented"
    },
    "expires_in": {
      "description": "URL validity in seconds (300 = 5 minutes).",
      "type": "integer",
      "x-type-from": "documented"
    },
    "user_id": {
      "description": "The Terra user the payload belongs to.",
      "type": "string",
      "x-type-from": "documented"
    },
    "reference_id": {
      "description": "The reference_id you set when connecting that user (empty if none was set).",
      "type": "string",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Terra API reference, “Event Types”, read 2026-11-05 (via the site's source-snapshot workflow).

Healthcheck

Sent periodically to your destination to confirm its health status.

Example body

{
  "type": "healthcheck",
  "status": String,
  "creation_timestamp": String,
  "trend_percentage": Number,
  "sent_webhooks_last_hour": Number
}

Terra documents this event as a format with type names (String, Number, User) rather than values, so there is no sample to send.

JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "terra-healthcheck",
  "title": "Terra webhook: Healthcheck",
  "description": "Built from the fields Terra documents at https://docs.tryterra.co/reference/health-and-fitness-api/event-types.md. JSON body.",
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "enum": [
        "healthcheck"
      ],
      "x-type-from": "documented"
    },
    "status": {
      "type": "string",
      "x-type-from": "documented"
    },
    "creation_timestamp": {
      "type": "string",
      "x-type-from": "documented"
    },
    "trend_percentage": {
      "type": "number",
      "x-type-from": "documented"
    },
    "sent_webhooks_last_hour": {
      "type": "number",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Terra API reference, “Event Types”, read 2026-11-05 (via the site's source-snapshot workflow).

Device account connection success

A user just finished connecting a device account to your app; save the user and link it with reference_id.

Example body

{
  "type": "auth",
  "user": User,
  "status": "success",
  "reference_id": String,
  "widget_session_id": String
}

Terra documents this event as a format with type names (String, Number, User) rather than values, so there is no sample to send.

JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "terra-auth",
  "title": "Terra webhook: Device account connection success",
  "description": "Built from the fields Terra documents at https://docs.tryterra.co/reference/health-and-fitness-api/event-types.md. JSON body.",
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "enum": [
        "auth"
      ],
      "x-type-from": "documented"
    },
    "user": {
      "description": "The Terra user (Terra's User model).",
      "type": "object",
      "x-type-from": "documented"
    },
    "status": {
      "type": "string",
      "x-type-from": "documented"
    },
    "reference_id": {
      "type": "string",
      "x-type-from": "documented"
    },
    "widget_session_id": {
      "type": "string",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Terra API reference, “Event Types”, read 2026-11-05 (via the site's source-snapshot workflow).

Device account disconnection

A user just disconnected their device account from your app.

Example body

{
  "type": "deauth",
  "user": User,
  "status": "success",
  "message": "User has deauthenticated"
}

Terra documents this event as a format with type names (String, Number, User) rather than values, so there is no sample to send.

JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "terra-deauth",
  "title": "Terra webhook: Device account disconnection",
  "description": "Built from the fields Terra documents at https://docs.tryterra.co/reference/health-and-fitness-api/event-types.md. JSON body.",
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "enum": [
        "deauth"
      ],
      "x-type-from": "documented"
    },
    "user": {
      "description": "The Terra user (Terra's User model).",
      "type": "object",
      "x-type-from": "documented"
    },
    "status": {
      "type": "string",
      "x-type-from": "documented"
    },
    "message": {
      "type": "string",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Terra API reference, “Event Types”, read 2026-11-05 (via the site's source-snapshot workflow).

Activity data event

New activity records are available for the user; overwrite earlier records by metadata.summary_id.

Example body

{
  "data": [Activity],
  "user": User,
  "type": "activity",
  "version": String
}

Terra documents this event as a format with type names (String, Number, User) rather than values, so there is no sample to send.

JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "terra-activity",
  "title": "Terra webhook: Activity data event",
  "description": "Built from the fields Terra documents at https://docs.tryterra.co/reference/health-and-fitness-api/event-types.md. JSON body.",
  "type": "object",
  "properties": {
    "data": {
      "description": "Activity records (Terra's Activity model).",
      "type": "array",
      "x-type-from": "documented"
    },
    "user": {
      "description": "The Terra user (Terra's User model).",
      "type": "object",
      "x-type-from": "documented"
    },
    "type": {
      "type": "string",
      "enum": [
        "activity"
      ],
      "x-type-from": "documented"
    },
    "version": {
      "type": "string",
      "x-type-from": "documented"
    }
  },
  "additionalProperties": true
}

From Terra API reference, “Event Types”, read 2026-11-05 (via the site's source-snapshot workflow).

Google Health API

Signature
Google sends the endpointAuthorization secret you configured in the Authorization header. It also signs the raw JSON with Tink (ECDSA P-256, SHA-256), base64 in GOOGLE-HEALTH-API-SIGNATURE, keys rotated every 30 days and published as a JSON keyset. Source
Respond
Validate the Authorization header and respond 204 No Content immediately; process asynchronously. On subscriber create or endpoint change, two POSTs with {"type": "verification"}: answer the authorized one with 200 or 201 and the unauthenticated one with 401 or 403. Source
Retries
Anything other than 204, or a timeout, is retried later. Pending notifications are stored for up to 7 days and retried with exponential backoff; older ones are discarded. Source
Note
Updates are batched as a JSON array, up to 99 messages per batch, possibly across users and data types. Source
Note
Retries can duplicate notifications: make handling idempotent. Source
Note
No notification is sent for revoked permissions or deleted accounts. Source

Verify a Google Health API delivery · Google Health API reference

Upsert (time-series data)

Sent for any data addition or modification.

Headers in the example

Authorization: Bearer example-secret-token
GOOGLE-HEALTH-API-SIGNATURE: signature-value
Content-Type: application/json

Example body

{
  "data": {
    "version": "1",
    "clientProvidedSubscriptionName": "subscription-name",
    "healthUserId": "health-user-id",
    "operation": "UPSERT",
    "dataType": "steps",
    "intervals": [
      {
        "physicalTimeInterval": {
          "startTime": "2026-03-08T01:29:00Z",
          "endTime": "2026-03-08T01:34:00Z"
        },
        "civilDateTimeInterval": {
          "startDateTime": {
            "date": {
              "year": 2026,
              "month": 3,
              "day": 7
            },
            "time": {
              "hours": 17,
              "minutes": 29
            }
          },
          "endDateTime": {
            "date": {
              "year": 2026,
              "month": 3,
              "day": 7
            },
            "time": {
              "hours": 17,
              "minutes": 34
            }
          }
        },
        "civilIso8601TimeInterval": {
          "startTime": "2026-03-07T17:29:00",
          "endTime": "2026-03-07T17:34:00"
        }
      }
    ]
  }
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "google-health-upsert",
  "title": "Google Health API webhook: Upsert (time-series data)",
  "description": "Built from the fields Google Health API documents at https://developers.google.com/health/webhooks. JSON body.",
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "data": {
          "description": "One notification.",
          "type": "object",
          "x-type-from": "example",
          "properties": {
            "version": {
              "type": "string",
              "x-type-from": "example"
            },
            "clientProvidedSubscriptionName": {
              "description": "Your manual subscription's name, or auto-<project-number>-<subscriber-id>-<WEBHOOK_DATA_TYPE_TOKEN> for automatic subscriptions.",
              "type": "string",
              "x-type-from": "documented"
            },
            "healthUserId": {
              "description": "The Google Health API identifier for the user whose data has changed.",
              "type": "string",
              "x-type-from": "documented"
            },
            "operation": {
              "description": "UPSERT for any data addition or modification; DELETE when a user explicitly deletes data.",
              "type": "string",
              "enum": [
                "UPSERT",
                "DELETE"
              ],
              "x-type-from": "documented"
            },
            "dataType": {
              "type": "string",
              "x-type-from": "example"
            },
            "intervals": {
              "description": "One or more time intervals of the modified data.",
              "type": "array",
              "x-type-from": "documented",
              "items": {
                "description": "One time interval of the modified data.",
                "type": "object",
                "properties": {
                  "physicalTimeInterval": {
                    "description": "The UTC physical time interval of the modified data. Populated for intraday metrics; omitted for daily rollups and full-day activities.",
                    "type": "object",
                    "x-type-from": "documented",
                    "properties": {
                      "startTime": {
                        "type": "string",
                        "x-type-from": "example"
                      },
                      "endTime": {
                        "type": "string",
                        "x-type-from": "example"
                      }
                    },
                    "additionalProperties": true
                  },
                  "civilDateTimeInterval": {
                    "description": "The civil (local) calendar date and time interval of the modified data, without UTC or timezone offsets. Google's field table marks it required, but its own delete examples omit it, so this schema does not require it.",
                    "type": "object",
                    "x-type-from": "documented",
                    "properties": {
                      "startDateTime": {
                        "type": "object",
                        "x-type-from": "example"
                      },
                      "endDateTime": {
                        "type": "object",
                        "x-type-from": "example"
                      }
                    },
                    "additionalProperties": true
                  },
                  "civilIso8601TimeInterval": {
                    "description": "The civil (local) time interval represented as an ISO 8601 string without UTC or timezone offsets.",
                    "type": "object",
                    "x-type-from": "documented",
                    "properties": {
                      "startTime": {
                        "type": "string",
                        "x-type-from": "example"
                      },
                      "endTime": {
                        "type": "string",
                        "x-type-from": "example"
                      }
                    },
                    "additionalProperties": true
                  }
                },
                "required": [
                  "civilIso8601TimeInterval"
                ],
                "additionalProperties": true,
                "x-type-from": "documented"
              }
            }
          },
          "additionalProperties": true
        }
      },
      "additionalProperties": true
    },
    {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "data": {
            "description": "One notification.",
            "type": "object",
            "x-type-from": "example",
            "properties": {
              "version": {
                "type": "string",
                "x-type-from": "example"
              },
              "clientProvidedSubscriptionName": {
                "description": "Your manual subscription's name, or auto-<project-number>-<subscriber-id>-<WEBHOOK_DATA_TYPE_TOKEN> for automatic subscriptions.",
                "type": "string",
                "x-type-from": "documented"
              },
              "healthUserId": {
                "description": "The Google Health API identifier for the user whose data has changed.",
                "type": "string",
                "x-type-from": "documented"
              },
              "operation": {
                "description": "UPSERT for any data addition or modification; DELETE when a user explicitly deletes data.",
                "type": "string",
                "enum": [
                  "UPSERT",
                  "DELETE"
                ],
                "x-type-from": "documented"
              },
              "dataType": {
                "type": "string",
                "x-type-from": "example"
              },
              "intervals": {
                "description": "One or more time intervals of the modified data.",
                "type": "array",
                "x-type-from": "documented",
                "items": {
                  "description": "One time interval of the modified data.",
                  "type": "object",
                  "properties": {
                    "physicalTimeInterval": {
                      "description": "The UTC physical time interval of the modified data. Populated for intraday metrics; omitted for daily rollups and full-day activities.",
                      "type": "object",
                      "x-type-from": "documented",
                      "properties": {
                        "startTime": {
                          "type": "string",
                          "x-type-from": "example"
                        },
                        "endTime": {
                          "type": "string",
                          "x-type-from": "example"
                        }
                      },
                      "additionalProperties": true
                    },
                    "civilDateTimeInterval": {
                      "description": "The civil (local) calendar date and time interval of the modified data, without UTC or timezone offsets. Google's field table marks it required, but its own delete examples omit it, so this schema does not require it.",
                      "type": "object",
                      "x-type-from": "documented",
                      "properties": {
                        "startDateTime": {
                          "type": "object",
                          "x-type-from": "example"
                        },
                        "endDateTime": {
                          "type": "object",
                          "x-type-from": "example"
                        }
                      },
                      "additionalProperties": true
                    },
                    "civilIso8601TimeInterval": {
                      "description": "The civil (local) time interval represented as an ISO 8601 string without UTC or timezone offsets.",
                      "type": "object",
                      "x-type-from": "documented",
                      "properties": {
                        "startTime": {
                          "type": "string",
                          "x-type-from": "example"
                        },
                        "endTime": {
                          "type": "string",
                          "x-type-from": "example"
                        }
                      },
                      "additionalProperties": true
                    }
                  },
                  "required": [
                    "civilIso8601TimeInterval"
                  ],
                  "additionalProperties": true,
                  "x-type-from": "documented"
                }
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      }
    }
  ]
}

From Google Health API, “Webhook subscriptions”, read 2026-11-05.

Delete (time-series data)

Sent when a user explicitly deletes data. Google's example arrives as an array.

Example body

[
  {
    "data": {
      "version": "1",
      "clientProvidedSubscriptionName": "subscription-name",
      "healthUserId": "health-user-id",
      "operation": "DELETE",
      "dataType": "steps",
      "intervals": [
        {
          "physicalTimeInterval": {
            "startTime": "2026-08-12T12:00:00Z",
            "endTime": "2026-08-12T12:30:00Z"
          },
          "civilIso8601TimeInterval": {
            "startTime": "2026-08-12T12:00:00",
            "endTime": "2026-08-12T12:30:00"
          }
        }
      ]
    }
  }
]
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "google-health-delete-time-series",
  "title": "Google Health API webhook: Delete (time-series data)",
  "description": "Built from the fields Google Health API documents at https://developers.google.com/health/webhooks. JSON body.",
  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "data": {
        "description": "One notification.",
        "type": "object",
        "x-type-from": "example",
        "properties": {
          "version": {
            "type": "string",
            "x-type-from": "example"
          },
          "clientProvidedSubscriptionName": {
            "description": "Your manual subscription's name, or auto-<project-number>-<subscriber-id>-<WEBHOOK_DATA_TYPE_TOKEN> for automatic subscriptions.",
            "type": "string",
            "x-type-from": "documented"
          },
          "healthUserId": {
            "description": "The Google Health API identifier for the user whose data has changed.",
            "type": "string",
            "x-type-from": "documented"
          },
          "operation": {
            "description": "UPSERT for any data addition or modification; DELETE when a user explicitly deletes data.",
            "type": "string",
            "enum": [
              "UPSERT",
              "DELETE"
            ],
            "x-type-from": "documented"
          },
          "dataType": {
            "type": "string",
            "x-type-from": "example"
          },
          "intervals": {
            "description": "One or more time intervals of the modified data.",
            "type": "array",
            "x-type-from": "documented",
            "items": {
              "description": "One time interval of the modified data.",
              "type": "object",
              "properties": {
                "physicalTimeInterval": {
                  "description": "The UTC physical time interval of the modified data. Populated for intraday metrics; omitted for daily rollups and full-day activities.",
                  "type": "object",
                  "x-type-from": "documented",
                  "properties": {
                    "startTime": {
                      "type": "string",
                      "x-type-from": "example"
                    },
                    "endTime": {
                      "type": "string",
                      "x-type-from": "example"
                    }
                  },
                  "additionalProperties": true
                },
                "civilDateTimeInterval": {
                  "description": "The civil (local) calendar date and time interval of the modified data, without UTC or timezone offsets. Google's field table marks it required, but its own delete examples omit it, so this schema does not require it.",
                  "type": "object",
                  "x-type-from": "documented",
                  "properties": {
                    "startDateTime": {
                      "type": "object",
                      "x-type-from": "example"
                    },
                    "endDateTime": {
                      "type": "object",
                      "x-type-from": "example"
                    }
                  },
                  "additionalProperties": true
                },
                "civilIso8601TimeInterval": {
                  "description": "The civil (local) time interval represented as an ISO 8601 string without UTC or timezone offsets.",
                  "type": "object",
                  "x-type-from": "documented",
                  "properties": {
                    "startTime": {
                      "type": "string",
                      "x-type-from": "example"
                    },
                    "endTime": {
                      "type": "string",
                      "x-type-from": "example"
                    }
                  },
                  "additionalProperties": true
                }
              },
              "required": [
                "civilIso8601TimeInterval"
              ],
              "additionalProperties": true,
              "x-type-from": "documented"
            }
          }
        },
        "additionalProperties": true
      }
    },
    "additionalProperties": true
  }
}

From Google Health API, “Webhook subscriptions”, read 2026-11-05.

Delete (identifiable data)

A delete of identifiable data such as a sleep carries the deleted record's recordId.

Example body

{
  "data": {
    "version": "1",
    "clientProvidedSubscriptionName": "subscription-name",
    "healthUserId": "health-user-id",
    "operation": "DELETE",
    "dataType": "sleep",
    "intervals": [
      {
        "physicalTimeInterval": {
          "startTime": "2026-08-12T22:00:00Z",
          "endTime": "2026-08-13T06:00:00Z"
        },
        "civilIso8601TimeInterval": {
          "startTime": "2026-08-12T22:00:00",
          "endTime": "2026-08-13T06:00:00"
        }
      }
    ],
    "recordId": "9875412"
  }
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "google-health-delete-identifiable",
  "title": "Google Health API webhook: Delete (identifiable data)",
  "description": "Built from the fields Google Health API documents at https://developers.google.com/health/webhooks. JSON body.",
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "data": {
          "description": "One notification.",
          "type": "object",
          "x-type-from": "example",
          "properties": {
            "version": {
              "type": "string",
              "x-type-from": "example"
            },
            "clientProvidedSubscriptionName": {
              "description": "Your manual subscription's name, or auto-<project-number>-<subscriber-id>-<WEBHOOK_DATA_TYPE_TOKEN> for automatic subscriptions.",
              "type": "string",
              "x-type-from": "documented"
            },
            "healthUserId": {
              "description": "The Google Health API identifier for the user whose data has changed.",
              "type": "string",
              "x-type-from": "documented"
            },
            "operation": {
              "description": "UPSERT for any data addition or modification; DELETE when a user explicitly deletes data.",
              "type": "string",
              "enum": [
                "UPSERT",
                "DELETE"
              ],
              "x-type-from": "documented"
            },
            "dataType": {
              "type": "string",
              "x-type-from": "example"
            },
            "intervals": {
              "description": "One or more time intervals of the modified data.",
              "type": "array",
              "x-type-from": "documented",
              "items": {
                "description": "One time interval of the modified data.",
                "type": "object",
                "properties": {
                  "physicalTimeInterval": {
                    "description": "The UTC physical time interval of the modified data. Populated for intraday metrics; omitted for daily rollups and full-day activities.",
                    "type": "object",
                    "x-type-from": "documented",
                    "properties": {
                      "startTime": {
                        "type": "string",
                        "x-type-from": "example"
                      },
                      "endTime": {
                        "type": "string",
                        "x-type-from": "example"
                      }
                    },
                    "additionalProperties": true
                  },
                  "civilDateTimeInterval": {
                    "description": "The civil (local) calendar date and time interval of the modified data, without UTC or timezone offsets. Google's field table marks it required, but its own delete examples omit it, so this schema does not require it.",
                    "type": "object",
                    "x-type-from": "documented",
                    "properties": {
                      "startDateTime": {
                        "type": "object",
                        "x-type-from": "example"
                      },
                      "endDateTime": {
                        "type": "object",
                        "x-type-from": "example"
                      }
                    },
                    "additionalProperties": true
                  },
                  "civilIso8601TimeInterval": {
                    "description": "The civil (local) time interval represented as an ISO 8601 string without UTC or timezone offsets.",
                    "type": "object",
                    "x-type-from": "documented",
                    "properties": {
                      "startTime": {
                        "type": "string",
                        "x-type-from": "example"
                      },
                      "endTime": {
                        "type": "string",
                        "x-type-from": "example"
                      }
                    },
                    "additionalProperties": true
                  }
                },
                "required": [
                  "civilIso8601TimeInterval"
                ],
                "additionalProperties": true,
                "x-type-from": "documented"
              }
            },
            "recordId": {
              "type": "string",
              "x-type-from": "example"
            }
          },
          "additionalProperties": true
        }
      },
      "additionalProperties": true
    },
    {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "data": {
            "description": "One notification.",
            "type": "object",
            "x-type-from": "example",
            "properties": {
              "version": {
                "type": "string",
                "x-type-from": "example"
              },
              "clientProvidedSubscriptionName": {
                "description": "Your manual subscription's name, or auto-<project-number>-<subscriber-id>-<WEBHOOK_DATA_TYPE_TOKEN> for automatic subscriptions.",
                "type": "string",
                "x-type-from": "documented"
              },
              "healthUserId": {
                "description": "The Google Health API identifier for the user whose data has changed.",
                "type": "string",
                "x-type-from": "documented"
              },
              "operation": {
                "description": "UPSERT for any data addition or modification; DELETE when a user explicitly deletes data.",
                "type": "string",
                "enum": [
                  "UPSERT",
                  "DELETE"
                ],
                "x-type-from": "documented"
              },
              "dataType": {
                "type": "string",
                "x-type-from": "example"
              },
              "intervals": {
                "description": "One or more time intervals of the modified data.",
                "type": "array",
                "x-type-from": "documented",
                "items": {
                  "description": "One time interval of the modified data.",
                  "type": "object",
                  "properties": {
                    "physicalTimeInterval": {
                      "description": "The UTC physical time interval of the modified data. Populated for intraday metrics; omitted for daily rollups and full-day activities.",
                      "type": "object",
                      "x-type-from": "documented",
                      "properties": {
                        "startTime": {
                          "type": "string",
                          "x-type-from": "example"
                        },
                        "endTime": {
                          "type": "string",
                          "x-type-from": "example"
                        }
                      },
                      "additionalProperties": true
                    },
                    "civilDateTimeInterval": {
                      "description": "The civil (local) calendar date and time interval of the modified data, without UTC or timezone offsets. Google's field table marks it required, but its own delete examples omit it, so this schema does not require it.",
                      "type": "object",
                      "x-type-from": "documented",
                      "properties": {
                        "startDateTime": {
                          "type": "object",
                          "x-type-from": "example"
                        },
                        "endDateTime": {
                          "type": "object",
                          "x-type-from": "example"
                        }
                      },
                      "additionalProperties": true
                    },
                    "civilIso8601TimeInterval": {
                      "description": "The civil (local) time interval represented as an ISO 8601 string without UTC or timezone offsets.",
                      "type": "object",
                      "x-type-from": "documented",
                      "properties": {
                        "startTime": {
                          "type": "string",
                          "x-type-from": "example"
                        },
                        "endTime": {
                          "type": "string",
                          "x-type-from": "example"
                        }
                      },
                      "additionalProperties": true
                    }
                  },
                  "required": [
                    "civilIso8601TimeInterval"
                  ],
                  "additionalProperties": true,
                  "x-type-from": "documented"
                }
              },
              "recordId": {
                "type": "string",
                "x-type-from": "example"
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      }
    }
  ]
}

From Google Health API, “Webhook subscriptions”, read 2026-11-05.

Full-day activity

Full-day activities and daily aggregates omit physicalTimeInterval; rely on the civil fields.

Example body

{
  "data": {
    "version": "1",
    "clientProvidedSubscriptionName": "auto-503461148219-lindasubscriber-WEBHOOK_DATA_TYPE_STEPS",
    "healthUserId": "343864966078399341",
    "operation": "UPSERT",
    "dataType": "steps",
    "intervals": [
      {
        "civilDateTimeInterval": {
          "startDateTime": {
            "date": {
              "year": 2026,
              "month": 7,
              "day": 14
            },
            "time": {}
          },
          "endDateTime": {
            "date": {
              "year": 2026,
              "month": 7,
              "day": 15
            },
            "time": {}
          }
        },
        "civilIso8601TimeInterval": {
          "startTime": "2026-07-14T00:00:00",
          "endTime": "2026-07-15T00:00:00"
        }
      }
    ]
  }
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "google-health-full-day",
  "title": "Google Health API webhook: Full-day activity",
  "description": "Built from the fields Google Health API documents at https://developers.google.com/health/webhooks. JSON body.",
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "data": {
          "description": "One notification.",
          "type": "object",
          "x-type-from": "example",
          "properties": {
            "version": {
              "type": "string",
              "x-type-from": "example"
            },
            "clientProvidedSubscriptionName": {
              "description": "Your manual subscription's name, or auto-<project-number>-<subscriber-id>-<WEBHOOK_DATA_TYPE_TOKEN> for automatic subscriptions.",
              "type": "string",
              "x-type-from": "documented"
            },
            "healthUserId": {
              "description": "The Google Health API identifier for the user whose data has changed.",
              "type": "string",
              "x-type-from": "documented"
            },
            "operation": {
              "description": "UPSERT for any data addition or modification; DELETE when a user explicitly deletes data.",
              "type": "string",
              "enum": [
                "UPSERT",
                "DELETE"
              ],
              "x-type-from": "documented"
            },
            "dataType": {
              "type": "string",
              "x-type-from": "example"
            },
            "intervals": {
              "description": "One or more time intervals of the modified data.",
              "type": "array",
              "x-type-from": "documented",
              "items": {
                "description": "One time interval of the modified data.",
                "type": "object",
                "properties": {
                  "physicalTimeInterval": {
                    "description": "The UTC physical time interval of the modified data. Populated for intraday metrics; omitted for daily rollups and full-day activities.",
                    "type": "object",
                    "x-type-from": "documented",
                    "properties": {
                      "startTime": {
                        "type": "string",
                        "x-type-from": "example"
                      },
                      "endTime": {
                        "type": "string",
                        "x-type-from": "example"
                      }
                    },
                    "additionalProperties": true
                  },
                  "civilDateTimeInterval": {
                    "description": "The civil (local) calendar date and time interval of the modified data, without UTC or timezone offsets. Google's field table marks it required, but its own delete examples omit it, so this schema does not require it.",
                    "type": "object",
                    "x-type-from": "documented",
                    "properties": {
                      "startDateTime": {
                        "type": "object",
                        "x-type-from": "example"
                      },
                      "endDateTime": {
                        "type": "object",
                        "x-type-from": "example"
                      }
                    },
                    "additionalProperties": true
                  },
                  "civilIso8601TimeInterval": {
                    "description": "The civil (local) time interval represented as an ISO 8601 string without UTC or timezone offsets.",
                    "type": "object",
                    "x-type-from": "documented",
                    "properties": {
                      "startTime": {
                        "type": "string",
                        "x-type-from": "example"
                      },
                      "endTime": {
                        "type": "string",
                        "x-type-from": "example"
                      }
                    },
                    "additionalProperties": true
                  }
                },
                "required": [
                  "civilIso8601TimeInterval"
                ],
                "additionalProperties": true,
                "x-type-from": "documented"
              }
            }
          },
          "additionalProperties": true
        }
      },
      "additionalProperties": true
    },
    {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "data": {
            "description": "One notification.",
            "type": "object",
            "x-type-from": "example",
            "properties": {
              "version": {
                "type": "string",
                "x-type-from": "example"
              },
              "clientProvidedSubscriptionName": {
                "description": "Your manual subscription's name, or auto-<project-number>-<subscriber-id>-<WEBHOOK_DATA_TYPE_TOKEN> for automatic subscriptions.",
                "type": "string",
                "x-type-from": "documented"
              },
              "healthUserId": {
                "description": "The Google Health API identifier for the user whose data has changed.",
                "type": "string",
                "x-type-from": "documented"
              },
              "operation": {
                "description": "UPSERT for any data addition or modification; DELETE when a user explicitly deletes data.",
                "type": "string",
                "enum": [
                  "UPSERT",
                  "DELETE"
                ],
                "x-type-from": "documented"
              },
              "dataType": {
                "type": "string",
                "x-type-from": "example"
              },
              "intervals": {
                "description": "One or more time intervals of the modified data.",
                "type": "array",
                "x-type-from": "documented",
                "items": {
                  "description": "One time interval of the modified data.",
                  "type": "object",
                  "properties": {
                    "physicalTimeInterval": {
                      "description": "The UTC physical time interval of the modified data. Populated for intraday metrics; omitted for daily rollups and full-day activities.",
                      "type": "object",
                      "x-type-from": "documented",
                      "properties": {
                        "startTime": {
                          "type": "string",
                          "x-type-from": "example"
                        },
                        "endTime": {
                          "type": "string",
                          "x-type-from": "example"
                        }
                      },
                      "additionalProperties": true
                    },
                    "civilDateTimeInterval": {
                      "description": "The civil (local) calendar date and time interval of the modified data, without UTC or timezone offsets. Google's field table marks it required, but its own delete examples omit it, so this schema does not require it.",
                      "type": "object",
                      "x-type-from": "documented",
                      "properties": {
                        "startDateTime": {
                          "type": "object",
                          "x-type-from": "example"
                        },
                        "endDateTime": {
                          "type": "object",
                          "x-type-from": "example"
                        }
                      },
                      "additionalProperties": true
                    },
                    "civilIso8601TimeInterval": {
                      "description": "The civil (local) time interval represented as an ISO 8601 string without UTC or timezone offsets.",
                      "type": "object",
                      "x-type-from": "documented",
                      "properties": {
                        "startTime": {
                          "type": "string",
                          "x-type-from": "example"
                        },
                        "endTime": {
                          "type": "string",
                          "x-type-from": "example"
                        }
                      },
                      "additionalProperties": true
                    }
                  },
                  "required": [
                    "civilIso8601TimeInterval"
                  ],
                  "additionalProperties": true,
                  "x-type-from": "documented"
                }
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      }
    }
  ]
}

From Google Health API, “Webhook subscriptions”, read 2026-11-05.

Endpoint verification

Two POSTs when you create a subscriber or change its endpoint: one with your Authorization header, one without.

Example body

{
  "type": "verification"
}
JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$anchor": "google-health-verification",
  "title": "Google Health API webhook: Endpoint verification",
  "description": "Built from the fields Google Health API documents at https://developers.google.com/health/webhooks. JSON body.",
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "verification"
          ],
          "x-type-from": "documented"
        }
      },
      "additionalProperties": true
    },
    {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "verification"
            ],
            "x-type-from": "documented"
          }
        },
        "additionalProperties": true
      }
    }
  ]
}

From Google Health API, “Webhook subscriptions”, read 2026-11-05.

Suunto

Signature
Not documented on Suunto's FAQ. The webhook address is set in your app's OAuth settings in Suunto API Zone and must be publicly reachable. Source
Respond
Fetch the workout as soon as the notification arrives; the FAQ states no response code or time limit. Source
Retries
Not documented on the pages read here. Source
Note
Workouts only: there is no notification for daily activity. Source

Suunto integration guide

Two parameters in the POST: username (find the user's token by the JWT's user claim) and workoutid (fetch the workout's FIT file). No example, encoding or signature is shown, so there is no schema or sample here. Source

Send a signed sample to your own handler

send-sample.mjs posts any example above to a URL on your machine, signed the way its vendor documents.

curl -O https://aifitnessapi.com/webhooks/send-sample.mjs
node send-sample.mjs --list
node send-sample.mjs whoop v2-sleep-updated --url http://localhost:3000/webhooks/whoop --secret "$WHOOP_CLIENT_SECRET"
node send-sample.mjs withings appli-44 --url http://localhost:3000/webhooks/withings --secret my-callback-token
node send-sample.mjs terra s3_payload --dry-run

It needs Node 18 or newer and nothing else. It sends only to localhost, 127.0.0.1 or ::1 unless you pass --allow-remote, and --dry-run prints the request without sending it.

Each request is a vendor's published example, signed now with your --secret the way that vendor documents: HMAC for Oura, WHOOP, Terra and Polar, the token on the URL for Withings, webhook_token in the body for Wahoo, the Authorization header for the Google Health API. Strava's events are unsigned. Without --secret it uses the demo secret the webhook verifier's examples use, so a --dry-run request pasted into the verifier checks as valid.

It cannot produce Google's GOOGLE-HEALTH-API-SIGNATURE header, which Google signs with its own private key, so it sends none; test that check against a real delivery.

Download every schema

schemas.json is one JSON Schema (draft 2020-12) bundle with each payload under $defs; reference one as schemas.json#<vendor>-<payload>, the id on its section above. Each property says in x-type-from whether its type comes from the vendor’s field table or only from its example, and only fields a vendor calls required are required.

Not covered, and why

  • Garmin. No public Garmin page read for this library shows a push payload. Garmin guide.
  • COROS. COROS's help center lists webhook push as part of its Partner API but shows no payload. COROS guide.
  • Dexcom. Dexcom's webhook documentation was not read for this library, so nothing is reproduced. Dexcom guide.
  • Fitbit Web API. Google says the legacy Fitbit Web API is turned off on November 2, 2026; its successor's notifications are under the Google Health API above. Fitbit Web API retirement.

Next step

Turning this into a choice of API: