Wearable events, pushed to your endpoint.
Health data does not arrive on a schedule. Open Wearables pushes each event to your endpoint once it is saved in your deployment, signed, retried if your service was down, with every attempt there to inspect.
Built on Open Wearables
Why push
Polling asks. Push tells you.
Polling checks every user on an interval, whether anything happened or not. Wiring each provider's own webhooks means a format and a signature scheme per provider. Open Wearables gives you one push for all of them.
| Poll each provider | Wire each provider's webhooks | Open Wearables webhooks | |
|---|---|---|---|
| [01] How new data reaches you | You ask on an interval | Each provider calls you in its own format, some with only an ID to fetch | A signed POST when data is saved, in one schema |
| [02] Requests to your service when nothing changed | Every interval, for every user | None | None |
| [03] Providers without webhooks | Polled | Still polled, by you | Polled by Open Wearables, pushed to you |
| [04] Signatures and retries | Not applicable | Different for every provider | One signature scheme, retries built in |
| [05] Delivery log | Your own logs | Your own logs | Every attempt, from the API |
Events
What lands on your endpoint.
Every delivery is a POST with a JSON body of two fields, type and data, in the same schema as the pull API, so the payload does not change shape with the provider.
Session events.
Fired once when a workout, a night of sleep, a generic activity or a menstrual cycle record is saved or merged. A field the provider did not report is null rather than missing.
- workout.created
- sleep.created
Timeseries events.
Fired per ingestion batch for each user, provider and series type, with the full samples array in the payload, so you store it without a follow-up request. Batches over 2,500 samples arrive in numbered chunks.
- heart_rate.created
- steps.created
Connection and sync events.
A revoked connection comes with its reason: a refresh token the provider rejected, a user who disconnected or signed out, or a deregistration on the provider's side. Sync events say when a sync starts, finishes or fails.
- connection.revoked
- sync.failed
Delivery
What happens when your endpoint is down.
Delivery decides whether a webhook integration is something you trust or something you check by hand. All of it is in the deployment rather than on a pricing tier.
- 01
Automatic retries.
A delivery counts as failed when your endpoint returns a non-2xx status or does not respond in time. It is retried at increasing intervals, for more than a day on the default schedule.
- 02
The same ID on every try.
Each delivery carries an svix-id that stays the same across retries, so storing the IDs you received makes your handler safe against duplicates.
- 03
Every attempt on record.
The attempts endpoint returns each try with the status code your server sent back and when it happened.
- 04
Data first.
Delivery never holds up ingestion. The data is saved in your database either way, and the pull API returns it in the same schema.
Security
Every payload is signed.
Each delivery carries three headers: a message ID, a timestamp and an HMAC signature. Checking them tells your endpoint the request came from your own deployment, not from anyone who found the URL.
One call to verify
wh.verify(body, headers) from the Svix library, in Python or Node, checks the signature and rejects deliveries older than five minutes. The manual recipe is in the docs for any other language.
A secret per endpoint
Each endpoint has its own signing key, fetched from the API and stored on your side.
Verify before processing
That is what stops a forged or replayed payload from reaching your business logic.
Getting started
From the first flag to the first event.
Registration is an API call, so it belongs in the same infrastructure code as the rest of your deployment. There is also an admin panel for it if you would rather click.
- 01
Turn webhooks on
Outgoing webhooks are off by default, so a deployment that does not need them does not emit anything. Set OUTGOING_WEBHOOKS_ENABLED=true in the backend environment and restart.
- 02
Register an endpoint
One request with your URL. Add filter_types or a user_id to receive only some events. The response carries the endpoint's id.
Terminal
curl -X POST localhost:8000/api/v1/webhooks/endpoints \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"url": "https://yourapp.com/webhooks/health", "filter_types": ["sleep.created", "workout.created"]}' - 03
Fetch its signing secret
Store it where your handler can read it.
Terminal
curl localhost:8000/api/v1/webhooks/endpoints/{id}/secret \ -H "Authorization: Bearer $TOKEN" - 04
Send a test event
An example payload for the event type you name, signed and sent the way a real one is, before a single user connects.
Terminal
curl -X POST localhost:8000/api/v1/webhooks/endpoints/{id}/test \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"event_type": "sleep.created"}' - 05
Read the attempts
Every attempt with the status code your server returned, so the first failing handler shows up here.
Terminal
curl localhost:8000/api/v1/webhooks/endpoints/{id}/attempts \ -H "Authorization: Bearer $TOKEN"
Run it in your own cloud
Webhooks, the delivery log and the signing keys all sit inside your deployment, under an MIT licence. Run it yourself, or see what it costs to have us stand it up inside your account.
FAQ
Do you have more questions?
How fast do events arrive?
Open Wearables sends an event once the data is saved in your deployment. What happens before that is the provider's: the path from a watch to a phone to the provider's cloud runs on each provider's own schedule. So we mirror the provider's latency rather than promise real time.
Which providers support webhooks?
It varies by provider. Some push data to Open Wearables, others have to be polled, and either way the event reaches your endpoint as a push. The provider coverage in the docs says how each one delivers.
What happens if our endpoint is down for an hour?
An hour is well inside the retries, which run at increasing intervals for more than a day on the default schedule, and the attempts endpoint shows each try with its status code. The data itself is already in your database, so the pull API returns it in the same schema whatever happened to the delivery.
What should our endpoint return?
A 2xx, as quickly as possible, before any heavy processing, with the slow work queued. Return it for events you ignore too, or they get retried.
Can we receive only some events?
Yes. An endpoint can be scoped by event type, by user, or both, and different endpoints can take different event types, routed however your services are split.
Do we still need to poll for anything?
Not for ongoing data. The pull API stays available for three things: backfilling a user's history when they first connect, reconciling after an outage, and recovering deliveries you missed. Same schema either way, so it is the same code path.
Is this metered?
No. Webhooks are part of the deployment, and the bill does not track how many events you receive or how many users generate them.
Can we change what gets sent?
The code that emits events is in a repository you cloned, so changing what a payload carries is a change you make rather than a feature you request.



