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.

9 min read Updated Sep 17, 2026

Prerequisites

  • A sandbox API key stored on your backend
  • A Sonar mobile-application registration for each native build
  • The app_id generated 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

text
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 ──► Sonar

The 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

PlatformPackage
iOSSwift package https://github.com/Sonar-Health/sonar-ios-sdk.git
AndroidMaven artifact co.sonarhealth:sonar-sdk:1.0.0
React Nativenpm 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:

bash
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" }
  }'
json
{
  "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:

typescript
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:

swift
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
}
kotlin
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
}
typescript
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:

swift
let observation = Sonar.observeState { state in
    renderNativeHealthState(state)
}

try await Sonar.connect(.appleHealth)
kotlin
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.
typescript
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:

typescript
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:

json
{
  "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:

bash
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.