React Native Health Data Integration | Open Wearables
Integrate the Open Wearables React Native SDK for health data sync on iOS and Android. Covers backend auth, Expo Module setup, sign-in flow, and HealthKit permission requests.
The SDK supports two authentication modes: token-based (recommended) and API key. The token-based flow keeps your API keys safe on your backend:
Your Backend generates token
Your backend calls the Open Wearables API with your App credentials (app_id + app_secret) to generate a user-scoped token (server-to-server, HTTPS) via Create User Token endpoint. Open Wearables returns access_token + refresh_token.
Your Backend returns tokens to the app
Your backend exposes its own custom endpoint that forwards the access_token and refresh_token to the mobile app. Never expose app_id or app_secret to the client.
Mobile App calls SDK signIn
The React Native app receives the tokens and passes them to OpenWearablesHealthSdk.signIn(accessToken, refreshToken).
SDK stores & syncs
React Native SDK stores credentials in iOS Keychain / Android EncryptedSharedPreferences and uses accessToken to sync health data directly to Open Wearables.
Never embed your app_id / app_secret in the mobile app. App credentials should only exist on your backend server. Only the access_token and refresh_token are passed to the mobile app.
// Express.js exampleconst express = require('express');const app = express();app.post('/api/health/connect', authenticateUser, async (req, res) => { try { // 1. Get your authenticated user and their stored OW user ID const owUserId = req.user.owUserId; // Stored when user was first registered // 2. Call Open Wearables API to generate token const response = await fetch(`${process.env.OPENWEARABLES_HOST}/api/v1/users/${owUserId}/token`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ app_id: process.env.OPENWEARABLES_APP_ID, // Secret! Never expose! app_secret: process.env.OPENWEARABLES_APP_SECRET, // Secret! Never expose! }), }); if (!response.ok) { throw new Error('Failed to generate token'); } const { access_token, refresh_token } = await response.json(); // 3. Return credentials to mobile app (NOT the app credentials!) res.json({ userId: owUserId, accessToken, refreshToken, }); } catch (error) { console.error('Health connect error:', error); res.status(500).json({ error: 'Failed to connect health' }); }});
# FastAPI examplefrom fastapi import FastAPI, Depends, HTTPExceptionimport httpximport osapp = FastAPI()@app.post("/api/health/connect")async def connect_health(current_user = Depends(get_current_user)): # 1. Get the stored OW user ID for this user ow_user_id = current_user.ow_user_id # Stored when user was first registered # 2. Call Open Wearables API to generate token async with httpx.AsyncClient() as client: response = await client.post( f"{os.environ['OPENWEARABLES_HOST']}/api/v1/users/{ow_user_id}/token", json={ "app_id": os.environ["OPENWEARABLES_APP_ID"], "app_secret": os.environ["OPENWEARABLES_APP_SECRET"], }, ) if response.status_code != 200: raise HTTPException(500, "Failed to generate token") data = response.json() # 3. Return credentials to mobile app return { "userId": str(ow_user_id), "accessToken": data["access_token"], "refreshToken": data.get("refresh_token"), }
# Rails controller exampleclass HealthController < ApplicationController before_action :authenticate_user! def connect # 1. Get the stored OW user ID for this user ow_user_id = current_user.ow_user_id # Stored when user was first registered # 2. Call Open Wearables API to generate token response = HTTParty.post( "#{ENV['OPENWEARABLES_HOST']}/api/v1/users/#{ow_user_id}/token", headers: { 'Content-Type' => 'application/json' }, body: { app_id: ENV['OPENWEARABLES_APP_ID'], app_secret: ENV['OPENWEARABLES_APP_SECRET'] }.to_json ) if response.success? # 3. Return credentials to mobile app render json: { userId: ow_user_id, accessToken: response['access_token'], refreshToken: response['refresh_token'] } else render json: { error: 'Failed to connect' }, status: 500 end endend
The user_id in the URL is the Open Wearables User ID (UUID). You should store this mapping in your database when you first Create User via the Open Wearables API.
On app startup, call getStoredCredentials() to retrieve any previously saved session. If a host is stored, pass it to configure(), then check isSessionValid() to decide whether a fresh sign-in is needed:
import OpenWearablesHealthSDK from "open-wearables";useEffect(() => { const init = async () => { const stored = OpenWearablesHealthSDK.getStoredCredentials(); if (stored?.host) { OpenWearablesHealthSDK.configure(stored.host); } if (OpenWearablesHealthSDK.isSessionValid()) { // Session restored — sync can start immediately } else { // No valid session — prompt user to sign in } }; init();}, []);
import OpenWearablesHealthSDK from "open-wearables";// 1. Get credentials from YOUR backendconst response = await fetch('https://your-api-host.com/api/health/connect', { method: 'POST' });const credentials = await response.json();// 2. Sign in with the SDKawait OpenWearablesHealthSDK.signIn( credentials.userId, credentials.accessToken, credentials.refreshToken, null);
For simpler setups (e.g. internal tools), you can use API key authentication directly:
import OpenWearablesHealthSDK from "open-wearables";// Sign in with API Keyawait OpenWearablesHealthSDK.signIn( 'your_user_id', null, null, 'your_api_key');
API key authentication embeds the key in the app. Only use this for internal or trusted applications. For production apps, always use token-based authentication.
When you provide a refreshToken, the SDK automatically handles 401 responses by refreshing the access token and retrying the request.You can also update tokens manually:
import OpenWearablesHealthSDK from "open-wearables";OpenWearablesHealthSDK.updateTokens(accessToken, refreshToken);
On Android, you must select a health data provider before requesting authorization. Call getAvailableProviders() to list what is installed on the device, then call setProvider(id) with the chosen provider’s id.
import AsyncStorage from "@react-native-async-storage/async-storage";// Expo projects: import * as SecureStore from "expo-secure-store";import { Platform } from "react-native";import OpenWearablesHealthSDK from "open-wearables";const PROVIDER_KEY = "health_provider";async function selectAndSaveProvider(): Promise<boolean> { if (Platform.OS !== "android") return true; const providers = OpenWearablesHealthSDK.getAvailableProviders(); if (providers.length === 0) return false; // Restore the previously chosen provider if it is still available const savedId = await AsyncStorage.getItem(PROVIDER_KEY); const restored = savedId ? providers.find((p) => p.id === savedId && p.isAvailable) : null; const chosen = restored ?? providers.find((p) => p.id === "google" && p.isAvailable) ?? providers.find((p) => p.isAvailable); if (!chosen) return false; await AsyncStorage.setItem(PROVIDER_KEY, chosen.id); return OpenWearablesHealthSDK.setProvider(chosen.id);}
Persist the selected provider ID so the user’s choice survives app restarts. Use @react-native-async-storage/async-storage for bare React Native projects, or AsyncStorage from expo-secure-store for Expo apps (though a plain key like a provider ID doesn’t require encryption — AsyncStorage is sufficient).
getAvailableProviders() returns an empty array on iOS — provider selection is not needed there, as HealthKit is the only source.
Control SDK log output using setLogLevel. By default, the SDK uses OWLogLevel.debug, which prints logs only in debug builds:
import OpenWearablesHealthSDK, { OWLogLevel } from "open-wearables";// Always show logs (including release builds)OpenWearablesHealthSDK.setLogLevel(OWLogLevel.always);// Only show logs in debug builds (default)OpenWearablesHealthSDK.setLogLevel(OWLogLevel.debug);// Disable all logsOpenWearablesHealthSDK.setLogLevel(OWLogLevel.none);// Read the current levelconst current = OpenWearablesHealthSDK.getLogLevel();
Level
Description
OWLogLevel.none
No logs at all
OWLogLevel.always
Logs are always printed regardless of build mode
OWLogLevel.debug
Logs are printed only in debug builds (default)
Set OWLogLevel.always during development or when troubleshooting sync issues in production. Switch to OWLogLevel.none if you want to suppress all SDK output.