Connect a patient's wearable

Send a signed-in patient through the provider's consent page and back to your app, with @ovok/core, React hooks or plain HTTP.

A patient connects their own account, from your patient app, while signed in as themselves. Every patient route reads the project and the Patient from the bearer token; a practitioner or admin token answers 403 PATIENT_PROFILE_REQUIRED.

Before you start

  • The provider is set up and enabled in the project.
  • PATIENT_APP_URL is set. Ovok appends /wearables/<provider> to it as written, so https://app.example.com returns to https://app.example.com/wearables/oura, and myapp:// returns to myapp:///wearables/oura.
  • Your app can handle that URL: a route in a web app, or a deep link in a native app.

The flow

  1. Ask for a consent link. authorizationUrl is valid for 10 minutes and for one use. Ask for a new one each time the patient taps "Connect".
  2. Open it in the system browser or an auth session: ASWebAuthenticationSession on iOS, Custom Tabs on Android, openAuthSessionAsync from expo-web-browser, or a full-page redirect on the web. Do not use an embedded WebView: the patient should see the provider's own page in a real browser.
  3. Ovok handles the callback. You never call it. It exchanges the code, stores the tokens encrypted and redirects to your app with result:
    • connected: the tokens are stored;
    • denied: the patient refused, or the provider returned an error;
    • failed: the code exchange or the save failed, an admin disabled the provider meanwhile, or Polar refused to register the patient because they have not accepted Polar's data-sharing consents. Nothing stays connected.
  4. Re-read the connections. result is for display only. GET /v1/wearables/connections is the state of record.

The first data arrives with the next daily sync at 08:00 UTC. To show data straight away, sync on demand after connected.

Connecting a provider the patient is already connected to replaces the stored tokens. This is also how a patient clears reconnectRequired.

For Withings, Ovok also subscribes the patient to Withings' notifications at connect time, so new measurements and nights arrive within minutes of reaching Withings.

Polar: consents first

Tell Polar users to accept Polar's mandatory data-sharing consents in their Polar account before connecting. Without them, Polar refuses to register the patient and the flow returns result=failed.

With @ovok/core

@ovok/core 0.4.46 and later has the provider and connection operations. Install or update it:

npm install @ovok/core@latest

Start the flow from a client signed in as the patient:

const { authorizationUrl } = await client.authorizeWearableConnection('oura');
window.location.assign(authorizationUrl);

On the return route, re-read the list:

const params = new URLSearchParams(window.location.search);
const result = params.get('result'); // 'connected' | 'denied' | 'failed', for display only
const { connections } = await client.listWearableConnections();

Disconnect:

await client.deleteWearableConnection('oura');

The SDK does not wrap the sync route yet. Call it through the same client:

await client.post('/v1/wearables/connections/oura/sync', undefined);

SDK types lag the API

In @ovok/core 0.4.46, the WearableConnection type declares only connectedAt. The API also returns syncedThrough and reconnectRequired; read them from the response until the types catch up.

React hooks

@ovok/core also exports React hooks for the same operations: useWearableConnections(), useAuthorizeWearableConnection() and useDeleteWearableConnection() for the patient, and useWearableProviders(), useCreateWearableProvider(), useUpdateWearableProvider() and useDeleteWearableProvider() for admin screens.

import { useWearableConnections } from '@ovok/core';

const { data, reload } = useWearableConnections();

// After the app returns from the provider:
reload();

A successful deleteWearableConnection() refreshes mounted useWearableConnections() hooks that use the same client.

React Native SDK

@ovok/native has no wearable connect flow yet. In a React Native or Expo app, use the @ovok/core client as above and open authorizationUrl with expo-web-browser, with your PATIENT_APP_URL deep link as the return URL:

import * as WebBrowser from 'expo-web-browser';

const { authorizationUrl } = await client.authorizeWearableConnection('oura');
await WebBrowser.openAuthSessionAsync(authorizationUrl, 'myapp:///wearables/oura');
const { connections } = await client.listWearableConnections();

With plain HTTP

export OVOK_TOKEN='<access token of a patient>'

Start the flow:

curl --request POST \
  --url 'https://api.sandbox.ovok.com/v1/wearables/connections/oura/authorize' \
  --header "Authorization: Bearer ${OVOK_TOKEN}"
{
  "provider": "oura",
  "authorizationUrl": "https://cloud.ouraring.com/oauth/authorize?response_type=code&client_id=…&redirect_uri=…&state=…&scope=…"
}

Read the connections:

curl --request GET \
  --url 'https://api.sandbox.ovok.com/v1/wearables/connections' \
  --header "Authorization: Bearer ${OVOK_TOKEN}"
{
  "connections": [
    {
      "provider": "oura",
      "connection": {
        "connectedAt": "2026-10-21T09:00:00.000Z",
        "syncedThrough": "2026-10-22T08:00:00.000Z",
        "reconnectRequired": false
      }
    },
    { "provider": "whoop", "connection": null }
  ]
}

Disconnect:

curl --request DELETE \
  --url 'https://api.sandbox.ovok.com/v1/wearables/connections/oura' \
  --header "Authorization: Bearer ${OVOK_TOKEN}"

The answer is 204. Where the provider allows it, Ovok first undoes what it set up there: Withings stops notifying Ovok about the patient, and Polar deregisters the patient, which also revokes your app's access. Other providers keep your app's access: tell the patient they can remove it in their provider account. A failure at the provider never stops the disconnect.

Build the screen from the list

  • Render the providers from connections, not from a hard-coded list. It holds every provider the project has enabled, plus any provider the patient is still connected to after an admin disabled it.
  • connection: null: offer Connect.
  • reconnectRequired: true: the provider no longer accepts the connection, usually because the patient removed your app's access at the provider. Offer Reconnect, which is the same authorize call.
  • syncedThrough: null: connected, not synced yet.

Errors

StatusCodeWhat to do
403PATIENT_PROFILE_REQUIREDSign in as the patient. Admins and practitioners cannot connect on a patient's behalf.
404PROVIDER_NOT_AVAILABLEThe project has not set up the provider or disabled it. Hide it; re-read the list.
404CONNECTION_NOT_FOUNDOn disconnect: the patient is not connected to this provider. Re-read the list.
409APP_URL_NOT_CONFIGUREDThe project has no usable PATIENT_APP_URL. Show it as a setup problem and tell the project admin.
422INVALID_STATEOn the callback: the link expired (10 minutes), was already used, or belongs to another provider. Start again from Connect.
501FEATURE_NOT_CONFIGUREDWearables are not set up on this Ovok server. Contact Ovok support.
503STATE_STORE_UNAVAILABLEOvok could not store or read the pending authorization. Retry after Retry-After.

Last updated on

On this page