Webhooks
Receive signed notifications when devices connect, data is processed, or a user is deleted.
Create and manage webhook endpoints in Developers → Webhooks. Each endpoint belongs to either the sandbox or live environment and subscribes to selected event kinds.
Event Envelope
{
"version": 1,
"event_id": "4bde887b-6d27-45fe-817a-87f2c1746056",
"kind": "subject.synced",
"at": "2026-09-09T10:42:13.502Z",
"user": {
"user_id": "7e4e91a5-1e4f-4fc2-903c-251046d2a4d3",
"external_ref": "member_123"
},
"payload": {
"daily": ["distance", "rem_sleep", "steps"],
"scores": ["recovery_score", "sleep_score"],
"timeseries": ["heart_rate"],
"workouts": 1,
"sleep": 1,
"from_date": "2026-09-08",
"to_date": "2026-09-09"
}
}A subject.synced payload is keyed by resource: which daily metric and score IDs changed, which timeseries metrics received samples, and how many workout and sleep sessions fall inside the affected window. Every ID uses the public catalog names, so a consumer can pass them straight back to /daily, /scores, or /timeseries.
Use event_id as the idempotency key in your handler. A test event uses kind: "webhook_test", user: null, and payload: {"test": true}.
Event Kinds
| Kind | Payload | Meaning |
|---|---|---|
device.connected | device_id, provider | A provider connection completed |
device.disconnected | device_id, provider | A provider was disconnected |
subject.synced | daily, scores, timeseries, workouts, sleep, optional from_date, to_date | New data was consolidated |
subject.deleted | external_ref | User deletion completed |
Events are notifications. Fetch the current values from the resource endpoints after receiving the event.
Historical data may arrive across multiple subject.synced events. There is no separate completion event because providers do not supply a reliable signal that historical ingestion has finished.
Verify the Signature
Each request includes:
Content-Type: application/json
X-Sonar-Signature: sha256=<hex digest>
X-Sonar-Delivery-Id: delivery:<event id>:<endpoint id>
X-Sonar-Event-Id: <event id>Compute HMAC-SHA256 over the exact raw request bytes using the signing secret shown when the endpoint is created or rotated. Compare the expected and received signatures with a timing-safe operation before parsing or acting on the event.
import hashlib
import hmac
def valid_sonar_signature(raw_body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, header)import { createHmac, timingSafeEqual } from "node:crypto";
function validSonarSignature(rawBody: Buffer, header: string, secret: string) {
const expected = `sha256=${createHmac("sha256", secret).update(rawBody).digest("hex")}`;
const received = Buffer.from(header);
const wanted = Buffer.from(expected);
return received.length === wanted.length && timingSafeEqual(received, wanted);
}Verify the raw body
Serializing a parsed JSON object can change whitespace or key order and produce a different digest. Capture the raw request body first.
Delivery Behavior
- Any
2xxresponse marks the delivery successful. - Requests time out after 10 seconds.
- Failed deliveries retry with exponential backoff for up to 24 hours.
- Deliveries can be duplicated, so handlers must be idempotent.
- Webhook order is not guaranteed across retries.
Return a 2xx response quickly and move expensive processing to your own queue.
Endpoint Rules and Operations
Production webhook URLs must use HTTPS, resolve to public addresses, and cannot contain credentials or a URL fragment. In Atlas you can create endpoints, choose subscriptions, update the URL, disable or enable delivery, rotate the secret, send a test, retry failed deliveries, and inspect the event feed.
The event feed is currently available as an operational view in Atlas.
Sonar