Webhooks

Receive signed notifications when devices connect, data is processed, or a user is deleted.

8 min read Updated Sep 09, 2026

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

json
{
  "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

KindPayloadMeaning
device.connecteddevice_id, providerA provider connection completed
device.disconnecteddevice_id, providerA provider was disconnected
subject.synceddaily, scores, timeseries, workouts, sleep, optional from_date, to_dateNew data was consolidated
subject.deletedexternal_refUser 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:

http
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.

python
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)
typescript
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 2xx response 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.