Skip to main content

Overview

Outgoing webhooks let Open Wearables push events to your backend in real time, so you don’t need to poll for new data. Each time a workout is saved, sleep is recorded, or a timeseries batch is ingested, Open Wearables fires an HTTP POST request to every registered endpoint that matches the event. Webhooks are delivered via Svix, which handles retries, signature signing, and delivery history.
Self-hosting: outgoing webhooks are disabled by default. To emit events, set OUTGOING_WEBHOOKS_ENABLED=true in the backend .env and restart — no command-line flags needed, plain docker compose up. The svix-server container runs either way, and the app runs fine when it is absent (e.g. a deployment without webhooks). On managed Postgres (AWS RDS / Railway), the app’s database user must be allowed to create databases, or create a svix database up front — Svix keeps its schema there.

Things you can do

Trigger downstream processing when a workout is synced, update your UI in real time when sleep data arrives, scope an endpoint to a single user’s events, or verify payloads are genuinely from Open Wearables.

Requirements

A developer account and a Bearer token (from POST /api/v1/auth/login), plus a publicly reachable HTTPS URL for your endpoint.

Quickstart

1

Register an endpoint

Send the URL your server is listening on. This returns an endpoint object you’ll use in subsequent calls.
Response:
Save the id — you’ll need it to fetch the signing secret and inspect delivery attempts.
2

Get the signing secret

Retrieve the HMAC signing key for your endpoint to verify incoming payloads.
Response:
Store this secret securely on your server — you’ll use it to verify every incoming request.
3

Handle incoming events

Open Wearables sends a POST to your URL with a JSON body and three signature headers. Verify the signature before processing.
Install the Svix library: pip install svix / npm install svix. The Webhook.verify() call handles timestamp tolerance (rejects messages older than 5 minutes) and all signature edge cases automatically.
4

Send a test event

Trigger a realistic example payload to your endpoint to confirm end-to-end delivery before you go live.
Check delivery status at any time:

Event Types

All events follow the resource.action naming convention. Use GET /api/v1/webhooks/event-types to retrieve this list programmatically.

Session Events

Fired once when a complete session is saved or merged.

Timeseries Events

Fired per ingestion batch — one event per distinct (user, provider, series_type) combination in a sync run. Each event carries the full samples array, so consumers can store data directly from the webhook without issuing follow-up API calls.

Payload Reference

Every payload envelope has type (the event name as a string) and data.

connection.created

connection.revoked

reason is a short cause, e.g. refresh_failed, deregistration, or user_disconnected.

workout.created

Fields like calories_kcal, distance_meters, and heart-rate fields are null when the provider did not report them.

sleep.created

menstrual_cycle.created

pregnancy_snapshot is populated (as a list with one entry) when the user is tracking a pregnancy. Garmin reuses the same summary ID for daily cycle updates — Open Wearables deduplicates by external_id, so the event fires only when a new record is first inserted.

activity.created

Timeseries events (all share the same shape)

Each timeseries event carries the full sample array so your backend can act on the data immediately — no follow-up API call needed.
Each entry in samples matches the schema returned by GET /api/v1/users/{user_id}/timeseries, so consumers work with a single schema regardless of whether data comes from a webhook or the pull API.
is_daily_total distinguishes pre-aggregated daily totals from intraday samples for additive series (steps, energy, distance, flights):For non-additive series (heart rate, SpO2, temperature, …) is_daily_total is always null and can be ignored.
Large batches (exceeding 2 500 samples) are automatically split into consecutive chunk events to stay within Svix’s 1 MB payload limit. Each chunk event includes chunk_index (0-based) and total_chunks so you can detect and reassemble split deliveries. The sample_count field always reflects the total number of samples across all chunks.
The pull API (GET /api/v1/users/{user_id}/timeseries) remains available for backfill, reconciliation, or recovery after missed deliveries.

Filtering Events

Filter by event type

Pass filter_types when creating or updating an endpoint to receive only the events you care about.
Omit the field entirely to receive all event types.

Filter by user

Pass user_id to scope an endpoint to events for a single user. All other users’ events are silently dropped before delivery.
To later remove the user filter (receive all users again), send a PATCH with "user_id": null:
Combine both filters to receive e.g. only workout.created events for a specific user — pass both filter_types and user_id in the same request.

Signature Verification

Every delivery includes three headers that let you confirm the payload was sent by Open Wearables and hasn’t been tampered with:
Install: pip install svix
Always verify signatures before processing the payload. This prevents replay attacks and ensures events cannot be forged by third parties.

Idempotency

The svix-id header is stable across retries — the same logical event always carries the same ID. Store received IDs and skip duplicates to make your handler idempotent.

Managing Endpoints

List all endpoints

Update an endpoint

All fields are optional — send only what you want to change.

Delete an endpoint

Returns 204 No Content on success.

Delivery and Retries

Open Wearables (via Svix) retries failed deliveries automatically with exponential back-off. A delivery is considered failed when your endpoint returns a non-2xx status code or does not respond within the timeout.
Svix retries each failed message at increasing intervals. If all retries are exhausted the message is marked as failed in delivery history — you can inspect it and manually trigger a resend from the Svix dashboard.
  • Respond with a 2xx status code as quickly as possible (before doing any heavy processing).
  • Offload slow work to a background queue — process the event asynchronously.
  • Return 2xx even for events you choose to ignore (otherwise they’ll be retried).
Data ingestion is never blocked by webhook failures — if delivery infrastructure is temporarily unavailable, data continues to be stored and events are queued for retry.

Debugging

View delivery history for an endpoint

Each attempt includes the HTTP status code returned by your server and the timestamp of the attempt.

View all sent messages

Send a test event

Send a realistic example payload for any event type to an endpoint without waiting for real data:
Omit the body to default to workout.created.