How to Access Google Health Connect Data in Your Backend
Introduction
Google Health Connect has no server-side API. If you want Health Connect data in your backend, there is only one path: build an Android app that reads data from Health Connect on the device and uploads it to your server. This is architecturally different from every other wearable integration covered in this series. There is no OAuth flow you can initiate from your backend, no token to exchange, no webhook to receive. The data lives on the user's Android device and your app must go get it.
This guide covers the complete pipeline: setting up Health Connect in your Android app, requesting permissions correctly, implementing reliable background sync with WorkManager and change tokens, serializing and uploading data to your backend, and handling the production edge cases that trip up most implementations. It also covers how to normalize Health Connect data once it reaches your backend so it fits alongside data from server-side providers.
Android App Setup
Add the Health Connect SDK dependency to your app's build.gradle file. Use the androidx.health.connect:connect-client library. Check the current version in Google's Maven repository. Add the required permissions to your AndroidManifest.xml for each Health Connect data type you intend to read. Each data type has a separate read permission. Declare only the permissions you will actually request and use.
Also add a queries element to your AndroidManifest.xml declaring the androidx.health.connect.client package. This is required for Android to recognize that your app uses Health Connect and enables the permission request flow.
Check for Health Connect availability when the app starts. On Android 14+, Health Connect is built into the system. On older versions, the user may need to install the Health Connect app separately. Use HealthConnectClient.getSdkStatus(context) to check availability before attempting to use the SDK. If the status indicates unavailable, direct the user to the Play Store to install Health Connect.
Requesting and Managing Permissions
Health Connect permissions are requested using a specialized ActivityResultContract called RequestMultiplePermissions paired with Health Connect's PermissionController.createRequestPermissionResultContract(). This is not the same as the standard Android runtime permissions flow, although it looks similar to the user.
Request permissions at the right moment in your onboarding flow. Explain to the user before the permission dialog appears which data you will read and why. Users who understand the value of sharing health data are significantly more likely to grant all requested permissions. A one-screen explanation before the permission dialog showing what the app does with each data type improves grant rates.
After permissions are granted, check which were actually approved using healthConnectClient.permissionController.getGrantedPermissions(). Do not assume all requested permissions were granted. Build your sync logic to work with whatever subset of permissions the user granted and surface clear messaging about which features require which permissions.
Permissions can be revoked at any time. Check granted permissions at the start of each sync job. If a previously granted permission is no longer present, skip the affected data types and update your UI to show the restricted data types.
Reading Data and Change Tokens
Health Connect read operations use the readRecords method on the HealthConnectClient. Each call specifies a record type class, a time range filter, a page size and an optional page token for pagination. For initial sync, use a time range from 30 days ago to now. For incremental sync, use change tokens instead.
Obtain a change token by calling getChangesToken with the record types you want to track. After your initial data read, store this token. On subsequent syncs, call getChanges with the stored token to receive only records that have been added, modified or deleted since the token was issued. This dramatically reduces the data you need to process and upload on each sync cycle.
Handle token expiry: change tokens expire after 30 days. If getChanges returns a ChangesTokenExpiredException, fall back to a full time-range read and obtain a new change token after completing the read.
Reliable Background Sync With WorkManager
Background work in Android is subject to battery optimization restrictions that can kill background services. WorkManager is the correct tool for reliable background Health Connect sync. It survives process death, handles retry with backoff, and respects Android's battery optimization while ensuring work eventually completes.
class HealthConnectSyncWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val client = HealthConnectClient.getOrCreate(applicationContext)
val prefs = applicationContext.getSharedPreferences("hc_sync", Context.MODE_PRIVATE)
return try {
val recordTypes = listOf(
ExerciseSessionRecord::class,
SleepSessionRecord::class,
HeartRateRecord::class,
StepsRecord::class,
HeartRateVariabilityRmssdRecord::class
)
val payloads = mutableListOf<Any>()
for (recordType in recordTypes) {
val tokenKey = "change_token_${recordType.simpleName}"
val storedToken = prefs.getString(tokenKey, null)
if (storedToken != null) {
val changesResponse = client.getChanges(storedToken)
payloads.addAll(changesResponse.changes)
prefs.edit().putString(tokenKey, changesResponse.nextChangesToken).apply()
} else {
val timeFilter = TimeRangeFilter.between(
Instant.now().minus(30, ChronoUnit.DAYS),
Instant.now()
)
val response = client.readRecords(
ReadRecordsRequest(recordType, timeFilter)
)
payloads.addAll(response.records)
val newToken = client.getChangesToken(
ChangesTokenRequest(setOf(recordType))
)
prefs.edit().putString(tokenKey, newToken).apply()
}
}
uploadToBackend(payloads)
Result.success()
} catch (e: PermissionException) {
// Permissions revoked, do not retry
Result.failure()
} catch (e: Exception) {
Result.retry()
}
}
private suspend fun uploadToBackend(payloads: List<Any>) {
// Serialize and POST to your backend endpoint
}
}
Register this Worker as a PeriodicWorkRequest with a one-hour interval and a network connectivity constraint. Use enqueueUniquePeriodicWork with ExistingPeriodicWorkPolicy.KEEP to ensure only one instance runs at a time.
Serializing and Uploading Health Connect Data
Health Connect SDK objects are not directly JSON serializable. You need to map each record type to a serializable data class before uploading. Define a data class for each record type you sync and write a conversion function that extracts the fields you need.
Include the dataOrigin source package name from each record's metadata in your serialized payload. This allows your backend to identify which app contributed each record, which is important for deduplication when multiple apps write overlapping data to Health Connect.
Upload batches rather than individual records. Group all records from a sync cycle into a single request payload and POST to your backend. Your backend should process the payload idempotently: uploading the same record twice should not create duplicates. Use the Health Connect record ID (available in each record's metadata) as a stable deduplication key in your backend storage.
Backend Normalization
Once Health Connect data reaches your backend, normalize it to your common health data schema. Map Health Connect record types to your canonical entities: ExerciseSessionRecord to a workout event, SleepSessionRecord with SleepStageRecord children to a sleep record, HeartRateRecord to heart rate time series data, StepsRecord to daily activity, and HeartRateVariabilityRmssdRecord to HRV measurements.
Health Connect sport type identifiers are enumerations from the ExerciseSessionRecord class (for example ExerciseSessionRecord.EXERCISE_TYPE_RUNNING, ExerciseSessionRecord.EXERCISE_TYPE_CYCLING). Map these to your internal sport type taxonomy using the same mapping table you use for other providers.
Timestamps in Health Connect are stored as Instant objects (UTC). Ensure your backend preserves UTC timestamps and handles timezone display on the client side based on user preference or the local timezone recorded in the session metadata.
Production Edge Cases
Large initial syncs can fail due to payload size limits or upload timeouts. Implement chunked uploads for the initial 30-day backfill: process data in daily batches and upload each batch before moving to the next day. Store the last successfully uploaded date so you can resume from where you left off if the initial sync is interrupted.
Device battery optimization can delay or kill WorkManager jobs on some Android manufacturers that apply aggressive background process restrictions (Xiaomi, OnePlus, Huawei and others are known for this). Include device-specific battery optimization guidance in your app's settings or onboarding flow, directing users to exempt your app from battery optimization. Without this, sync jobs may not run reliably on a significant portion of Android devices.
Multiple overlapping change token streams for the same record type can arise if a user has multiple sessions of your app running or reinstalls the app. Manage change token storage carefully: store tokens in a persistent location that survives app updates (SharedPreferences or Room database) and implement a reset mechanism that clears all change tokens and triggers a fresh time-range backfill if you detect inconsistency.
Open Wearables
Open Wearables supports Health Connect as a companion-upload provider. Your Android app handles the on-device read using the patterns described in this guide, and uploads the serialized data to the Open Wearables backend API endpoint. The backend normalizes Health Connect data to the same schema used for server-side providers.
This means Health Connect users on Android and Garmin, Polar, Whoop or Samsung Health users all produce normalized health data through the same pipeline in your product. Self-hosted, MIT licensed, no per-user fees.
FAQ
Is there any way to access Health Connect data without an Android app?
No. Health Connect is an on-device store with no server-side access API. An Android app acting as the data reading and upload intermediary is the only supported architecture. There is no workaround or undocumented server-side API.
Can I use Health Connect data for the same user who also has Garmin or Polar connected?
Yes, but implement deduplication. If a user has Garmin connected via the server-side API and also has Garmin contributing data to Health Connect on their phone, you may receive the same workout from both sources. Use a combination of start time, duration, sport type and data source metadata to detect and deduplicate overlapping records.
How do I handle users who uninstall Health Connect or deny all permissions?
If all permissions are denied, your sync worker has nothing to read. Handle this state by stopping the periodic work request, clearing stored change tokens, and showing the user a clear explanation of what features require Health Connect access. Do not repeatedly prompt for permissions after the user has denied them: follow Android's guidelines on permission request frequency.
Does Health Connect work on iOS?
No. Health Connect is Android-only. Apple HealthKit is the equivalent on iOS. If your application needs to support both platforms, you need a Health Connect integration for Android and a separate HealthKit integration for iOS. Open Wearables supports both.
Google Health Connect Integration
View the full Google Health Connect integration documentation on Open Wearables.
See Related Articles
Google Health Connect Integration: Android Health Data for Developers