Skip to main content

[Early Access] Wearable webhook ingress

POST /v1/wearables/{provider}/webhook
devhttps://api.sandbox.ovok.com

Early Access — interfaces may change without notice.

Endpoint the wearable provider POSTs activity / sleep / heart-rate events to. Not called by the dashboard — only by the provider's webhook delivery service.

Per-provider behaviour

Strava (webhookPing model):

The payload contains only { object_type, object_id, aspect_type, owner_id }. We follow up with GET /api/v3/activities/{id} using the connection's access token, map the result to a FHIR Procedure plus summary Observations (distance, moving-time, kcal, average HR, max HR), and enqueue them for write.

Signature verification: Strava does not HMAC-sign events. We accept the payload if (a) the structural shape is correct and (b) an optional x-ovok-signature HMAC header matches the configured WEARABLE_STRAVA_WEBHOOK_SECRET when present.

Polar (webhookStream model):

The payload contains the full event (EXERCISE, SLEEP, CONTINUOUS_HEART_RATE). EXERCISE maps to a Procedure plus summary Observations; SLEEP maps to a duration Observation on LOINC 93832-4. CONTINUOUS_HEART_RATE is currently a no-op and reserved for a future pass that writes valueSampledData so the stream renders via [GET /Observation/:id?asTimeseries=true].

Signature verification: mandatory. Polar HMAC-SHA256-signs the raw body and sends the hex digest in the polar-webhook-signature header. We compare against WEARABLE_POLAR_WEBHOOK_SECRET; mismatches return 400.

Routing

The webhook arrives without project context. We look up the matching FHIR Endpoint resource cross-tenant by (provider, remote_user_id) — provider's user id is owner_id (Strava) or user_id (Polar). Webhook events for an unknown user are silently 200'd to stop the provider retrying after a revoke.

Body

application/json — raw provider payload. We parse it with the strategy and enqueue the result onto the wearable-ingest BullMQ queue, then 200 quickly so the provider doesn't retry. FHIR writes happen off-thread.

Parameters

NameInTypeRequiredDescription
providerpathstringyesWearable provider slug delivering the event. Provider-specific payload + signing rules apply (see description).

Responses

CodeDescription
200Success.
400The request could not be operated by the server.
401The resource owner or authorization server denied the request.
404The requested resource could not be found.
422The request could not be validated by the server.
500The server encountered an unexpected condition. Please try again later.

200OkResponseDto (application/json)

Side-effect-only acknowledgement. Body is the literal { "ok": true }.

  • ok: boolean (required)