Mobile SDK Quickstart
Connect a signed-in mobile user to Apple Health, Health Connect, or Samsung Health and read their normalized data from your backend.
Prerequisites
- A sandbox API key stored on your backend
- A Sonar mobile-application registration for each native build
- The
app_idgenerated for the registration - A signed-in user in your application
- A webhook endpoint subscribed to
subject.synced
The API key, mobile registration, SDK configuration, and Sonar user must all belong to the same environment. This quickstart uses sandbox.
The Complete Flow
Mobile app ── authenticated request ──► Customer backend
│
│ API key creates client token
▼
Mobile app + Sonar SDK ── native data ──► Sonar ingestion
│
│ subject.synced webhook
▼
Mobile app ◄── your API ── Customer backend ── health-data request ──► SonarThe mobile SDK can only synchronize native health data. Your backend remains responsible for user management and every API read.
Integrate the SDK
Install the Package
| Platform | Package |
|---|---|
| iOS | Swift package https://github.com/Sonar-Health/sonar-ios-sdk.git |
| Android | Maven artifact co.sonarhealth:sonar-sdk:1.0.0 |
| React Native | npm package @sonar/react-native-sdk@1.0.0 |
Complete the native capabilities, permissions, and provider declarations in the iOS (Swift), Android (Kotlin), or React Native guide before connecting a provider.
Create or Retrieve the Sonar User
Your backend creates a Sonar user once and stores the returned id beside the corresponding user in your system:
curl "https://atlas.sonarhealth.co/v1/users" \
-X POST \
-H "Authorization: Bearer $SONAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_ref": "member_123",
"display_name": "Jordan Kim",
"profile": { "timezone": "Europe/Madrid" }
}'{
"id": "7e4e91a5-1e4f-4fc2-903c-251046d2a4d3",
"external_ref": "member_123",
"display_name": "Jordan Kim",
"created_at": "2026-09-14T09:30:00.000Z"
}Reuse that Sonar user ID on later application sessions. Do not create a new Sonar user for each installation or provider.
Add a Client-Token Route to Your Backend
The mobile app calls a route protected by your normal application authentication. Your backend resolves the signed-in customer to its stored Sonar user ID and creates a single-use SDK client token:
import { randomUUID } from "node:crypto";
const SONAR_API = "https://atlas.sonarhealth.co/v1";
export async function createSonarClientToken(request: Request) {
const customerUser = await requireSignedInUser(request);
const sonarUserId = await users.sonarUserIdFor(customerUser.id);
const context = await request.json();
const response = await fetch(`${SONAR_API}/users/${sonarUserId}/sdk-sessions`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SONAR_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": randomUUID(),
},
body: JSON.stringify({
app_id: context.appId,
installation_id: context.installationId,
}),
});
if (!response.ok) throw new Error(`Sonar returned ${response.status}`);
const session = await response.json();
return Response.json({ client_token: session.client_token });
}Resolve identity on your backend
Never accept a Sonar user ID from the mobile application. Determine it from the customer account authenticated to your backend, and return the client token only to that same user.
The Idempotency-Key belongs to your backend-to-Sonar request. If your backend retries that request after losing the response, reuse the same key for that logical attempt. See SDK Authentication.
The SDK creates this context for the configured installation. The application registration is the provider allowlist, so the mobile app does not send a second provider list. Forward the SDK-generated application and installation identifiers without replacing them.
Configure and Authenticate
Initialize the SDK with the registered application and supply a callback to your authenticated backend route:
import SonarSDK
try Sonar.configure(
appId: "app_01K4M7D9R3W2V8Y6T5Q1N0PABC",
environment: .sandbox
)
try await Sonar.authenticate { context in
try await customerAPI.createSonarClientToken(
appId: context.appId,
installationId: context.installationId
).clientToken
}import co.sonarhealth.sdk.Environment
import co.sonarhealth.sdk.Sonar
Sonar.configure(
context = applicationContext,
appId = "app_01K4M7D9R3W2V8Y6T5Q1N0PABC",
environment = Environment.SANDBOX,
)
Sonar.authenticate { context ->
customerApi.createSonarClientToken(
appId = context.appId,
installationId = context.installationId,
).clientToken
}import { Sonar } from "@sonar/react-native-sdk";
Sonar.configure({
appId: "app_01K4M7D9R3W2V8Y6T5Q1N0PABC",
environment: "sandbox",
});
await Sonar.authenticate({
clientTokenProvider: async (context) => {
const response = await customerApi.createSonarClientToken(context);
return response.client_token;
},
});The SDK stores and refreshes its renewable session. It calls the token provider again only when that session cannot be recovered directly with Sonar.
Observe State and Connect a Provider
Subscribe before connecting so the application receives permission, synchronization, and recovery transitions:
let observation = Sonar.observeState { state in
renderNativeHealthState(state)
}
try await Sonar.connect(.appleHealth)import co.sonarhealth.sdk.NativeProvider
val observation = Sonar.observeState { state ->
renderNativeHealthState(state)
}
Sonar.connect(NativeProvider.HEALTH_CONNECT)
// Connect SAMSUNG_HEALTH separately when the app offers both providers.import { Platform } from "react-native";
const unsubscribe = Sonar.observeState((state) => {
renderNativeHealthState(state);
});
const provider = Platform.OS === "ios" ? "apple_health" : "health_connect";
await Sonar.connect(provider);connect presents the provider’s permission flow and starts synchronization automatically. It returns after the connection is accepted, not after historical data finishes. Keep the observation for the owning lifecycle and cancel, close, or unsubscribe from it afterward.
Let the SDK Synchronize
The SDK synchronizes the most recent 30 days first, continues backwards for up to two years, and then reads changes incrementally. You do not construct ingestion payloads or schedule uploads.
Use sync() when the user expects an immediate refresh, such as when opening a health dashboard:
await Sonar.sync();The call schedules an incremental pull and returns immediately. Automatic and historical synchronization continue normally.
Handle the Webhook and Read Through the API
After Sonar processes a batch, your backend receives subject.synced:
{
"version": 1,
"event_id": "4bde887b-6d27-45fe-817a-87f2c1746056",
"kind": "subject.synced",
"at": "2026-09-14T10:42:13.502Z",
"user": {
"user_id": "7e4e91a5-1e4f-4fc2-903c-251046d2a4d3",
"external_ref": "member_123"
},
"payload": {
"daily": ["resting_heart_rate", "steps"],
"scores": [],
"timeseries": ["heart_rate"],
"workouts": 1,
"sleep": 0,
"from_date": "2026-09-13",
"to_date": "2026-09-14"
}
}Verify the webhook signature, deduplicate on event_id, and fetch the affected resources with your API key:
curl "https://atlas.sonarhealth.co/v1/users/7e4e91a5-1e4f-4fc2-903c-251046d2a4d3/daily?types=steps,resting_heart_rate" \
-H "Authorization: Bearer $SONAR_API_KEY"Return the health data your application needs through your own authenticated API. The mobile SDK session cannot perform this request.
Finished State
At this point:
- The mobile installation has a renewable, user-scoped SDK session.
- The native provider is connected and synchronizing in the background.
- The SDK owns local cursors, batching, retry, and historical progress.
- Your backend receives change notifications and reads normalized data through the API.
- Your Sonar API key has never entered the mobile application.
Continue with Supported Native Data, SDK Synchronization, or the complete SDK Interface.
Sonar