Skip to main content

Overview

This guide walks you through the complete integration of the Open Wearables Flutter SDK, from backend setup to production deployment.
1

Set up backend authentication endpoint

2

Configure the SDK in your Flutter app

3

Implement sign-in flow

4

Request health permissions

5

Start background sync

Authentication Architecture

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 Flutter app receives the tokens and passes them to OpenWearablesHealthSdk.signIn(accessToken, refreshToken).

SDK stores & syncs

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

Step 1: Backend Setup

Your backend needs a single endpoint that generates access tokens for your users by calling the Open Wearables API and forwarding the tokens.

Generate Access Token

When a user wants to connect their health data, your backend should:
  1. Authenticate the user (your own auth system)
  2. Call Open Wearables API at POST /api/v1/users/{user_id}/token with your App credentials
  3. Return the access_token and refresh_token to the mobile app
// 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' });
  }
});
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.

Step 2: SDK Configuration

Configure the SDK once at app startup, typically in your main initialization code.
import 'package:open_wearables_health_sdk/open_wearables_health_sdk.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Configure SDK with your backend host - this also restores any existing session
  await OpenWearablesHealthSdk.configure(
    host: 'https://api.openwearables.io',
  );
  
  runApp(MyApp());
}

Configuration Options

ParameterTypeDescription
hostStringRequired. The Open Wearables API base URL — host only, without path suffix (e.g. https://api.openwearables.io)
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 Wearables
await OpenWearablesHealthSdk.configure(
  host: 'https://your-domain.com',
);

Session Restoration

The SDK automatically restores the user session from secure storage when configure() is called:
await OpenWearablesHealthSdk.configure(host: 'https://api.openwearables.io');

// Check if user was previously signed in
if (OpenWearablesHealthSdk.isSignedIn) {
  print('Welcome back, ${OpenWearablesHealthSdk.currentUser?.userId}!');
  // User is already signed in, can start sync directly
} else {
  // Need to sign in first
}

Step 3: Sign In

After getting credentials from your backend, sign in with the SDK:
The userId parameter is the Open Wearables User ID (UUID) — the id returned by the Create User endpoint. Do not pass your own external_user_id here.
Future<void> connectHealth() async {
  try {
    // 1. Get tokens from YOUR backend (e.g. https://api.yourapp.com)
    final response = await yourApi.post('/api/health/connect');
    final credentials = json.decode(response.body);
    
    // 2. Sign in with the SDK
    final user = await OpenWearablesHealthSdk.signIn(
      userId: credentials['userId'],
      accessToken: credentials['accessToken'],
      refreshToken: credentials['refreshToken'],
    );
    
    print('Connected: ${user.userId}');
  } on SignInException catch (e) {
    print('Sign-in failed: ${e.message} (status: ${e.statusCode})');
  }
}

API Key Authentication

For simpler setups (e.g. internal tools), you can use API key authentication directly:
final user = await OpenWearablesHealthSdk.signIn(
  userId: 'user123',
  apiKey: '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.

Automatic Token Refresh

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:
await OpenWearablesHealthSdk.updateTokens(
  accessToken: newAccessToken,
  refreshToken: newRefreshToken,
);

Step 4: Request Permissions

Request access to specific health data types:
Future<bool> requestHealthPermissions() async {
  final authorized = await OpenWearablesHealthSdk.requestAuthorization(
    types: [
      HealthDataType.steps,
      HealthDataType.heartRate,
      HealthDataType.restingHeartRate,
      HealthDataType.sleep,
      HealthDataType.workout,
      HealthDataType.activeEnergy,
      HealthDataType.bodyMass,
    ],
  );
  
  if (authorized) {
    print('Health permissions granted');
  } else {
    print('Some permissions were denied');
  }
  
  return authorized;
}

Android Provider Selection

On Android, you must select a health data provider before requesting authorization:
import 'dart:io';
import 'package:shared_preferences/shared_preferences.dart';

Future<AndroidHealthProvider> selectAndroidProvider() async {
  final prefs = await SharedPreferences.getInstance();
  final savedProvider = prefs.getString('health_provider');

  // Restore previously chosen provider if still available
  final providers = await OpenWearablesHealthSdk.getAvailableProviders();

  if (savedProvider != null) {
    final restored = providers.cast<AndroidHealthProvider?>().firstWhere(
      (p) => p?.name == savedProvider,
      orElse: () => null,
    );
    if (restored != null) return restored;
  }

  // Prefer Health Connect, fall back to Samsung Health
  final AndroidHealthProvider chosen;
  if (providers.contains(AndroidHealthProvider.healthConnect)) {
    chosen = AndroidHealthProvider.healthConnect;
  } else if (providers.contains(AndroidHealthProvider.samsungHealth)) {
    chosen = AndroidHealthProvider.samsungHealth;
  } else {
    throw Exception(
      'No supported health provider found. '
      'Please install Health Connect or Samsung Health.',
    );
  }

  await prefs.setString('health_provider', chosen.name);
  return chosen;
}

// Usage
if (Platform.isAndroid) {
  final provider = await selectAndroidProvider();
  await OpenWearablesHealthSdk.setProvider(provider);
}

// Then request authorization
final authorized = await OpenWearablesHealthSdk.requestAuthorization(
  types: [
    HealthDataType.steps,
    HealthDataType.heartRate,
    HealthDataType.sleep,
    HealthDataType.workout,
    HealthDataType.activeEnergy,
  ],
);
print('Permissions granted: $authorized');
On iOS, users can grant partial permissions. The SDK will sync whatever data the user allows.
Request only the data types you actually need. Requesting too many types can overwhelm users and reduce acceptance rates.

Step 5: Start Background Sync

Enable background sync to keep data flowing even when your app is in the background:
await OpenWearablesHealthSdk.startBackgroundSync();

// Check sync status
print('Sync active: ${OpenWearablesHealthSdk.isSyncActive}');

Controlling Sync History Depth

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 data
await OpenWearablesHealthSdk.startBackgroundSync(syncDaysBack: 90);

// Sync last 30 days
await OpenWearablesHealthSdk.startBackgroundSync(syncDaysBack: 30);

// Full sync — all available history (default)
await OpenWearablesHealthSdk.startBackgroundSync();
ParameterTypeDefaultDescription
syncDaysBackint?nullNumber 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.

Background Sync Behavior

MechanismFrequency
HealthKit Observer QueriesImmediate on new data
BGAppRefreshTaskEvery ~15 minutes (system-managed)
BGProcessingTaskNetwork-required background processing
Background sync frequency is managed by the OS and may vary based on battery level, network conditions, and user behavior.

Manual Sync

Trigger an immediate sync when needed:
await OpenWearablesHealthSdk.syncNow();

Log Level

Control SDK log output using setLogLevel. By default, the SDK uses OWLogLevel.debug, which prints logs only in debug builds:
// Always show logs (including release builds)
await OpenWearablesHealthSdk.setLogLevel(OWLogLevel.always);

// Only show logs in debug builds (default)
await OpenWearablesHealthSdk.setLogLevel(OWLogLevel.debug);

// Disable all logs
await OpenWearablesHealthSdk.setLogLevel(OWLogLevel.none);
LevelDescription
OWLogLevel.noneNo logs at all
OWLogLevel.alwaysLogs are always printed regardless of build mode
OWLogLevel.debugLogs 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.

Complete Integration Example

Here’s a complete service class showing the full integration:
import 'dart:convert';
import 'dart:io';
import 'package:open_wearables_health_sdk/open_wearables_health_sdk.dart';
import 'package:open_wearables_health_sdk/open_wearables_health_sdk_method_channel.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';

class HealthSyncService {
  final String _backendUrl;
  final String _authToken;

  HealthSyncService({
    required String backendUrl,
    required String authToken,
  })  : _backendUrl = backendUrl,
        _authToken = authToken;

  /// Initialize the health sync service
  Future<void> initialize() async {
    // Set up event listeners
    MethodChannelOpenWearablesHealthSdk.logStream.listen((message) {
      print('[HealthSync] $message');
    });

    MethodChannelOpenWearablesHealthSdk.authErrorStream.listen((error) {
      print('[HealthSync] Auth error: ${error['statusCode']}');
    });

    await OpenWearablesHealthSdk.configure(
      host: 'https://api.openwearables.io',
    );

    // Enable logs in all builds for debugging (default is debug-only)
    await OpenWearablesHealthSdk.setLogLevel(OWLogLevel.always);
  }

  /// Get current status
  OpenWearablesHealthSdkStatus get status => OpenWearablesHealthSdk.status;
  bool get isConnected => OpenWearablesHealthSdk.isSignedIn;
  bool get isSyncing => OpenWearablesHealthSdk.isSyncActive;

  /// Connect health data for the current user
  Future<void> connect() async {
    switch (OpenWearablesHealthSdk.status) {
      case OpenWearablesHealthSdkStatus.signedIn:
        if (!OpenWearablesHealthSdk.isSyncActive) {
          await _startSync();
        }
        return;

      case OpenWearablesHealthSdkStatus.configured:
        await _signIn();
        await _startSync();
        return;

      case OpenWearablesHealthSdkStatus.notConfigured:
        throw Exception('SDK not configured. Call initialize() first.');
    }
  }

  Future<void> _signIn() async {
    // Call YOUR backend — not the Open Wearables API directly
    final response = await http.post(
      Uri.parse('$_backendUrl/api/health/connect'),
      headers: {
        'Authorization': 'Bearer $_authToken',
        'Content-Type': 'application/json',
      },
    );

    if (response.statusCode != 200) {
      throw Exception('Failed to get health credentials');
    }

    final data = json.decode(response.body);

    await OpenWearablesHealthSdk.signIn(
      userId: data['userId'],
      accessToken: data['accessToken'],
      refreshToken: data['refreshToken'],
    );
  }

  Future<void> _startSync() async {
    if (Platform.isAndroid) {
      final providers = await OpenWearablesHealthSdk.getAvailableProviders();

      final prefs = await SharedPreferences.getInstance();
      final savedName = prefs.getString('health_provider');

      // Restore saved provider or pick the best available one
      AndroidHealthProvider? chosen;
      if (savedName != null) {
        chosen = providers.cast<AndroidHealthProvider?>().firstWhere(
          (p) => p?.name == savedName,
          orElse: () => null,
        );
      }
      chosen ??= providers.contains(AndroidHealthProvider.healthConnect)
          ? AndroidHealthProvider.healthConnect
          : providers.contains(AndroidHealthProvider.samsungHealth)
              ? AndroidHealthProvider.samsungHealth
              : null;

      if (chosen == null) {
        throw Exception(
          'No supported health provider available on this device. '
          'Please install Health Connect or Samsung Health.',
        );
      }

      await prefs.setString('health_provider', chosen.name);
      await OpenWearablesHealthSdk.setProvider(chosen);
    }

    final authorized = await OpenWearablesHealthSdk.requestAuthorization(
      types: [
        HealthDataType.steps,
        HealthDataType.heartRate,
        HealthDataType.sleep,
        HealthDataType.workout,
        HealthDataType.activeEnergy,
      ],
    );

    if (!authorized) {
      throw Exception('Health permissions not granted');
    }

    await OpenWearablesHealthSdk.startBackgroundSync(syncDaysBack: 90);
  }

  /// Disconnect health data
  Future<void> disconnect() async {
    await OpenWearablesHealthSdk.stopBackgroundSync();
    await OpenWearablesHealthSdk.signOut();
  }

  /// Check for interrupted sync sessions
  Future<void> checkAndResumeSync() async {
    final status = await OpenWearablesHealthSdk.getSyncStatus();
    
    if (status['hasResumableSession'] == true) {
      print('Resuming interrupted sync...');
      print('Already sent: ${status['sentCount']} records');
      await OpenWearablesHealthSdk.resumeSync();
    }
  }

  /// Force a full re-sync of all data
  Future<void> resyncAllData() async {
    await OpenWearablesHealthSdk.resetAnchors();
    await OpenWearablesHealthSdk.syncNow();
  }
}

Using the Service

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late HealthSyncService _healthService;

  @override
  void initState() {
    super.initState();
    _initializeHealth();
  }

  Future<void> _initializeHealth() async {
    _healthService = HealthSyncService(
      backendUrl: 'https://api.yourapp.com',
      authToken: await getAuthToken(),
    );

    await _healthService.initialize();
    
    // Check for interrupted syncs
    await _healthService.checkAndResumeSync();
  }

  Future<void> _connectHealth() async {
    try {
      await _healthService.connect();
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Health connected!')),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed: $e')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: _connectHealth,
          child: Text(_healthService.isConnected 
            ? 'Health Connected' 
            : 'Connect Health'),
        ),
      ),
    );
  }
}

Data Sync Endpoint

The SDK sends health data to:
host
string
required
Your Open Wearables API base URL (e.g. https://api.example.com).
userId
string
required
The user ID returned when registering the SDK user.
POST {host}/api/v1/sdk/users/{userId}/sync
Data is automatically normalized to the Open Wearables unified data model and can be accessed through the standard API endpoints.

Next Steps

Troubleshooting

Common issues and solutions for iOS and Android.

Data Types

Available health metrics and data formats.