Android Health Connect Integration Guide | Open Wearables
Integrate the Open Wearables Android SDK into a Kotlin app. Covers backend auth setup, SDK initialization, health provider selection, and Health Connect permission requests.
This guide walks you through the complete integration of the Open Wearables Android SDK into a native Kotlin application, from backend setup to production deployment.
The SDK supports two authentication modes: token-based (recommended) and API key. The token-based flow keeps your App credentials 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 Android app receives the tokens and passes them to sdk.signIn(accessToken, refreshToken).
SDK stores & syncs
Android SDK stores credentials in 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.
Return the access_token and refresh_token to the mobile app
Node.js
Python
Ruby
// Express.js example — runs on YOUR backend (e.g. https://api.yourapp.com)const express = require('express');const app = express();app.post('/api/health/connect', authenticateUser, async (req, res) => { try { const owUserId = req.user.openWearablesUserId; // Call Open Wearables API to generate a user-scoped 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, app_secret: process.env.OPENWEARABLES_APP_SECRET, }), } ); if (!response.ok) { throw new Error('Failed to generate token'); } const { access_token, refresh_token } = await response.json(); // Return tokens to the mobile app (NOT the app credentials!) res.json({ userId: owUserId, accessToken: access_token, refreshToken: refresh_token, }); } catch (error) { console.error('Health connect error:', error); res.status(500).json({ error: 'Failed to connect health' }); }});
# FastAPI example — runs on YOUR backend (e.g. https://api.yourapp.com)from fastapi import FastAPI, Depends, HTTPExceptionimport httpximport osapp = FastAPI()@app.post("/api/health/connect")async def connect_health(current_user = Depends(get_current_user)): ow_user_id = current_user.open_wearables_user_id # Call Open Wearables API to generate a user-scoped 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() # Return tokens to the mobile app return { "userId": str(ow_user_id), "accessToken": data["access_token"], "refreshToken": data["refresh_token"], }
# Rails controller example — runs on YOUR backend (e.g. https://api.yourapp.com)class HealthController < ApplicationController before_action :authenticate_user! def connect ow_user_id = current_user.open_wearables_user_id # Call Open Wearables API to generate a user-scoped 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? # Return tokens to the 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.
Required. The Open Wearables API base URL (host only, without path suffix)
Provide only the base host URL, e.g. https://your-domain.com. Do not append /api/v1/ or any other path — the SDK adds the required path prefix automatically.
// For self-hosted Open Wearablessdk.configure(host = "https://your-domain.com")
The SDK automatically restores credentials from EncryptedSharedPreferences:
val sdk = OpenWearablesHealthSDK.getInstance()if (sdk.isSessionValid()) { val userId = sdk.restoreSession() Log.d("HealthSDK", "Session restored for user: $userId")} else { // Need to sign in}
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:
Before requesting permissions, you must select a health data provider:
val sdk = OpenWearablesHealthSDK.getInstance()// Check available providersval providers = sdk.getAvailableProviders()for (p in providers) { Log.d("HealthSDK", "${p["displayName"]} (${p["id"]}) - available: ${p["isAvailable"]}")}// Set the provider based on user selection or your defaultval success = sdk.setProvider("google") // or "samsung" for Samsung Healthif (!success) { Log.e("HealthSDK", "Selected provider is unavailable on this device")}
Call setProvider() before requestAuthorization(). The SDK needs to know which provider to request permissions from.
Request access to specific health data types. The type IDs are string-based:
suspend fun requestHealthPermissions(): Boolean { val sdk = OpenWearablesHealthSDK.getInstance() val authorized = sdk.requestAuthorization( types = listOf( "steps", "heartRate", "restingHeartRate", "sleep", "workout", "activeEnergy", "bodyMass" ) ) if (authorized) { Log.d("HealthSDK", "Health permissions granted") } else { Log.d("HealthSDK", "Some permissions were denied") } return authorized}
Make sure to call setActivity() on the SDK before requesting authorization, so the SDK can launch the permission dialog from the correct Activity context.
By default, the SDK syncs all available historical data on the first sync. Use the syncDaysBack parameter to limit how far back the sync goes:
// Sync only the last 90 days of datasdk.startBackgroundSync(syncDaysBack = 90)// Sync last 30 dayssdk.startBackgroundSync(syncDaysBack = 30)// Full sync — all available history (default)sdk.startBackgroundSync()
Parameter
Type
Default
Description
syncDaysBack
Int?
null
Number of days of historical data to sync. Syncs from the start of the day that many days ago. When null, syncs all available history. The value is persisted and used for subsequent background syncs until changed.
Control SDK log output using the logLevel property. By default, the SDK uses OWLogLevel.DEBUG, which outputs logs only in debuggable builds:
val sdk = OpenWearablesHealthSDK.getInstance()// Always show logs (including release builds)sdk.logLevel = OWLogLevel.ALWAYS// Only show logs in debuggable builds (default)sdk.logLevel = OWLogLevel.DEBUG// Disable all logssdk.logLevel = OWLogLevel.NONE
Level
Description
OWLogLevel.NONE
No logs at all
OWLogLevel.ALWAYS
Logs are always emitted (Logcat + listener) regardless of build type
OWLogLevel.DEBUG
Logs are emitted only in debuggable 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.