Skip to main content

Documentation Index

Fetch the complete documentation index at: https://openwearables.io/docs/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Provider webhook subscriptions tell a wearable provider (Polar, Oura, Strava) where to send event notifications. When an active subscription is in place, the provider pushes a lightweight notification to your Open Wearables instance whenever new data is available. Open Wearables then fetches, normalizes, and stores the data — and can forward it onward via Outgoing Webhooks.
This page covers provider → Open Wearables subscriptions (inbound). For Open Wearables → your app event delivery, see the Outgoing Webhooks guide.
All endpoints require a developer Bearer token:
Authorization: Bearer <token>
Base path: /api/v1/providers/{provider}/webhooks/subscriptions Supported {provider} values: polar, oura, strava

Provider differences

ProviderMax subscriptionsVerification methodSecret
Polar1 per appPolar sends a PING event to the URL; must return 200 OKsignature_secret_key returned once on creation — auto-saved to DB
Oura1 per data typeGET challenge with verification_token query paramClient secret used for HMAC verification
Strava1 per appGET hub.challenge echoSTRAVA_WEBHOOK_VERIFY_TOKEN env var
Polar only: when you register or update a webhook, Polar immediately sends a PING to the configured URL. The request must respond 200 OK before Polar accepts the subscription. Use a publicly reachable URL (e.g. set up via ngrok).

Endpoints

List subscriptions

Returns all active subscriptions for a provider.
curl "http://localhost:8000/api/v1/providers/{provider}/webhooks/subscriptions" \
  -H "Authorization: Bearer $TOKEN"
Response:
{
  "subscriptions": [
    {
      "id": "abc123",
      "events": ["EXERCISE", "SLEEP", "CONTINUOUS_HEART_RATE", "DAILY_ACTIVITY"],
      "url": "https://yourapp.com/api/v1/providers/polar/webhooks"
    }
  ]
}

Register subscriptions

Creates a new subscription (or updates an existing one if the URL changed). Pass callback_url as a query parameter.
curl -X POST "http://localhost:8000/api/v1/providers/{provider}/webhooks/subscriptions?callback_url=https://yourapp.com/api/v1/providers/{provider}/webhooks" \
  -H "Authorization: Bearer $TOKEN"
Provider-specific behaviour:
ProviderIf no subscription existsIf subscription exists with same URLIf subscription exists with different URL
PolarCreates webhook; Polar POSTs a PING to verify. signature_secret_key auto-saved to DB.Returns skippedUpdates URL via PATCH; Polar PINGs the new URL
OuraCreates one subscription per data typeReturns skipped per already-registered typeCreates new subscriptions for missing types
StravaRegisters subscription; Strava sends a GET challenge that must echo back hub.challengeReturns skipped
Polar: the signature_secret_key is returned by Polar exactly once, on creation. Open Wearables saves it automatically to the provider_settings table (webhook_secret column). If you need to rotate it, delete and recreate the webhook.

Get a subscription

Fetch a single subscription by ID.
curl "http://localhost:8000/api/v1/providers/{provider}/webhooks/subscriptions/{subscription_id}" \
  -H "Authorization: Bearer $TOKEN"
Response:
{
  "subscription": {
    "id": "sub_01abc",
    "callback_url": "https://yourapp.com/api/v1/providers/oura/webhooks",
    "event_type": "create",
    "data_type": "workout",
    "expiration_time": "2026-06-28T00:00:00+00:00"
  }
}
Returns { "subscription": null } when the subscription is not found.
Get by ID is currently implemented for Oura only. Polar and Strava return 501 Not Implemented — use list subscriptions instead.

Update a subscription

Change the callback URL of an existing subscription.
curl -X PUT "http://localhost:8000/api/v1/providers/{provider}/webhooks/subscriptions/{subscription_id}?callback_url=https://newurl.com/api/v1/providers/{provider}/webhooks" \
  -H "Authorization: Bearer $TOKEN"
Response:
{
  "updated": {
    "subscription_id": "abc123",
    "status": "patched"
  }
}
On error:
{
  "updated": {
    "subscription_id": "abc123",
    "status": "error",
    "error": "422 Unprocessable Entity ..."
  }
}
Polar: updating the URL triggers a new PING from Polar to the new address. If the URL does not respond 200 OK, the update is rejected by Polar and the endpoint returns status: "error".
Possible status values: updated, patched, deleted, created, skipped, error.

Delete a subscription

Remove a webhook subscription from the provider.
curl -X DELETE "http://localhost:8000/api/v1/providers/{provider}/webhooks/subscriptions/{subscription_id}" \
  -H "Authorization: Bearer $TOKEN"
Response:
{
  "deleted": {
    "subscription_id": "abc123",
    "status": "deleted"
  }
}

Renew subscriptions (Oura only)

Oura subscriptions have an expiration_time. Call this endpoint to extend all active subscriptions before they expire.
curl -X POST "http://localhost:8000/api/v1/providers/oura/webhooks/subscriptions/renew" \
  -H "Authorization: Bearer $TOKEN"
Response:
{
  "renewed": [
    { "id": "sub_01abc", "status": "renewed", ... },
    { "id": "sub_01def", "status": "renewed", ... }
  ]
}

Inbound event endpoints

These endpoints receive the actual event notifications from providers. They are not management operations — you do not call them directly.
MethodPathDescription
POST/api/v1/providers/{provider}/webhooksReceives incoming event payloads. Verifies the provider signature, parses the event, and dispatches a background sync task.
GET/api/v1/providers/{provider}/webhooksHandles subscription verification challenges (Oura verification_token, Strava hub.challenge).
The callback URL you register with a provider must point to POST /api/v1/providers/{provider}/webhooks. For local development, use ngrok to expose that endpoint publicly.

Response schema reference

Subscription objects

Each provider returns a different subscription shape: Polar
FieldTypeDescription
idstringWebhook ID assigned by Polar
eventsstring[]Subscribed event types (e.g. EXERCISE, SLEEP)
urlstringRegistered callback URL
Oura
FieldTypeDescription
idstringSubscription ID
callback_urlstringRegistered callback URL
event_typestringcreate or update
data_typestringworkout, sleep, session, etc.
expiration_timestring | nullISO 8601 expiry datetime
Strava
FieldTypeDescription
idintegerSubscription ID
application_idintegerStrava application ID
callback_urlstringRegistered callback URL
created_atstringISO 8601 creation timestamp
updated_atstringISO 8601 last-update timestamp

Operation result

Returned by delete and update endpoints.
FieldTypeDescription
subscription_idstringID of the affected subscription
statusstringOne of: created, updated, patched, deleted, skipped, error
errorstringPresent only when status is error