Nirmitee.io
InteroperabilityFHIRMobile App

Apple HealthKit and Google Health Connect Integration: Getting Consumer Wearable Data into Your FHIR Server

April 24, 202614 min readUpdated Aug 24, 2026
Written by
Dinesh Thorat
Dinesh Thorat

Digital Growth Lead

Writes about healthcare technology, interoperability, and AI-driven transformation across modern care systems.

Apple HealthKit and Google Health Connect Integration: Getting Consumer Wearable Data into Your FHIR Server

Over 500 million people worldwide wear smartwatches and fitness trackers that continuously collect heart rate, sleep patterns, activity levels, blood oxygen, and more. Yet the vast majority of this data never reaches a clinician. Patients track their health obsessively on Apple Watch, Fitbit, Oura Ring, and Garmin — but their providers see none of it during appointments.

Related case study: consumer health data at work — our wellness and nutrition management platform with gym health.

The technical bridge between consumer wearables and clinical systems runs through two platform APIs: Apple HealthKit (iOS) and Google Health Connect (Android). By building a pipeline from these APIs through a FHIR-compliant backend, healthcare organizations can ingest consumer wearable data as structured FHIR Observation resources — ready for clinical decision-making, remote monitoring programs, and population health analytics.

This guide provides the complete technical implementation: Swift code for HealthKit, Kotlin code for Health Connect, FHIR mapping tables for 20+ health data types, consent management patterns, and data quality strategies for production deployments.

Apple HealthKit vs Google Health Connect: Platform Comparison

Before writing integration code, understand the architectural differences between Apple and Google's health data platforms. Both serve the same purpose — unified health data storage — but their APIs, data models, and permission systems differ significantly.

FeatureApple HealthKitGoogle Health Connect
PlatformiOS only (iPhone, Apple Watch)Android 14+ (built-in), Android 9+ (installable)
Data Types100+ quantity, category, and correlation types50+ standardized data types across 6 categories
StorageOn-device HealthKit Store (encrypted)On-device, managed by Health Connect module
Background DeliveryYes — observer queries trigger on new dataYes — via WorkManager and data change tokens
PermissionsPer-data-type read/write, cannot check read statusPer-data-type read/write, can check grant status
BLE Device SupportCoreBluetooth framework, direct pairingCompanion device manager, varies by OEM
LanguageSwift / Objective-CKotlin / Java
Key LibraryHealthKit framework (built-in)androidx.health.connect:connect-client
Wearable SourcesApple Watch, Withings, Dexcom, Oura, GarminFitbit, Samsung Health, Google Fit, Oura, Garmin

Swift HealthKit Integration: Reading Wearable Data on iOS

The HealthKit framework provides access to health data stored in the Apple Health app. Here is a production-ready Swift implementation for reading heart rate, step count, SpO2, and sleep data from HealthKit.

Step 1: Configure HealthKit Capabilities

In your Xcode project, enable the HealthKit capability under Signing & Capabilities. Add the required usage descriptions to Info.plist:


<key>NSHealthShareUsageDescription</key>
<string>We need access to your health data to share it with your care team for remote monitoring.</string>
<key>NSHealthUpdateUsageDescription</key>
<string>We write health summaries back to Apple Health for your records.</string>

Step 2: Request Authorization and Read Data


import HealthKit

class HealthKitManager {
    let healthStore = HKHealthStore()

    // Define the data types we want to read
    let readTypes: Set<HKObjectType> = [
        HKQuantityType(.heartRate),
        HKQuantityType(.stepCount),
        HKQuantityType(.oxygenSaturation),
        HKQuantityType(.bloodPressureSystolic),
        HKQuantityType(.bloodPressureDiastolic),
        HKQuantityType(.bodyMass),
        HKQuantityType(.bodyTemperature),
        HKQuantityType(.respiratoryRate),
        HKQuantityType(.activeEnergyBurned),
        HKQuantityType(.bloodGlucose),
        HKCategoryType(.sleepAnalysis)
    ]

    func requestAuthorization() async throws {
        guard HKHealthStore.isHealthDataAvailable() else {
            throw HealthKitError.notAvailable
        }
        try await healthStore.requestAuthorization(
            toShare: [],
            read: readTypes
        )
    }

    func fetchHeartRate(from startDate: Date, to endDate: Date) async throws -> [HeartRateReading] {
        let heartRateType = HKQuantityType(.heartRate)
        let predicate = HKQuery.predicateForSamples(
            withStart: startDate,
            end: endDate,
            options: .strictStartDate
        )
        let sortDescriptor = NSSortDescriptor(
            key: HKSampleSortIdentifierStartDate,
            ascending: true
        )

        return try await withCheckedThrowingContinuation { continuation in
            let query = HKSampleQuery(
                sampleType: heartRateType,
                predicate: predicate,
                limit: HKObjectQueryNoLimit,
                sortDescriptors: [sortDescriptor]
            ) { _, samples, error in
                if let error = error {
                    continuation.resume(throwing: error)
                    return
                }
                let readings = (samples as? [HKQuantitySample])?.map { sample in
                    HeartRateReading(
                        value: sample.quantity.doubleValue(
                            for: HKUnit.count().unitDivided(by: .minute())
                        ),
                        timestamp: sample.startDate,
                        sourceDevice: sample.sourceRevision.source.name
                    )
                } ?? []
                continuation.resume(returning: readings)
            }
            healthStore.execute(query)
        }
    }

    // Background delivery — get notified when new data arrives
    func enableBackgroundDelivery() {
        let heartRateType = HKQuantityType(.heartRate)
        healthStore.enableBackgroundDelivery(
            for: heartRateType,
            frequency: .immediate
        ) { success, error in
            if success {
                print("Background delivery enabled for heart rate")
            }
        }
    }
}

Step 3: Send Data to Your FHIR Backend


struct FHIRUploader {
    let serverURL: URL
    let authToken: String

    func uploadHeartRate(_ readings: [HeartRateReading], patientId: String) async throws {
        for reading in readings {
            let observation: [String: Any] = [
                "resourceType": "Observation",
                "status": "final",
                "category": [["coding": [["system": "http://terminology.hl7.org/CodeSystem/observation-category",
                                          "code": "vital-signs"]]]],
                "code": ["coding": [["system": "http://loinc.org",
                                     "code": "8867-4",
                                     "display": "Heart rate"]]],
                "subject": ["reference": "Patient/\\(patientId)"],
                "effectiveDateTime": ISO8601DateFormatter().string(from: reading.timestamp),
                "valueQuantity": [
                    "value": reading.value,
                    "unit": "beats/minute",
                    "system": "http://unitsofmeasure.org",
                    "code": "/min"
                ],
                "device": ["display": reading.sourceDevice]
            ]

            var request = URLRequest(url: serverURL.appendingPathComponent("Observation"))
            request.httpMethod = "POST"
            request.setValue("application/fhir+json", forHTTPHeaderField: "Content-Type")
            request.setValue("Bearer \\(authToken)", forHTTPHeaderField: "Authorization")
            request.httpBody = try JSONSerialization.data(withJSONObject: observation)

            let (_, response) = try await URLSession.shared.data(for: request)
            guard let httpResponse = response as? HTTPURLResponse,
                  (200...299).contains(httpResponse.statusCode) else {
                throw FHIRError.uploadFailed
            }
        }
    }
}

Kotlin Health Connect Integration: Reading Wearable Data on Android

Google Health Connect provides a unified API for reading health data from Fitbit, Samsung Health, Google Fit, and other Android health apps. Here is the Kotlin implementation.

Step 1: Add Dependencies and Permissions


// build.gradle.kts
dependencies {
    implementation("androidx.health.connect:connect-client:1.1.0-alpha10")
}

// AndroidManifest.xml permissions
// <uses-permission android:name="android.permission.health.READ_HEART_RATE" />
// <uses-permission android:name="android.permission.health.READ_STEPS" />
// <uses-permission android:name="android.permission.health.READ_BLOOD_PRESSURE" />
// <uses-permission android:name="android.permission.health.READ_OXYGEN_SATURATION" />
// <uses-permission android:name="android.permission.health.READ_SLEEP" />
// <uses-permission android:name="android.permission.health.READ_BODY_TEMPERATURE" />

Step 2: Read Health Data from Health Connect


import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.records.*
import androidx.health.connect.client.request.ReadRecordsRequest
import androidx.health.connect.client.time.TimeRangeFilter
import java.time.Instant

class HealthConnectManager(private val context: Context) {

    private val client = HealthConnectClient.getOrCreate(context)

    suspend fun readHeartRate(
        startTime: Instant,
        endTime: Instant
    ): List<HeartRateRecord> {
        val request = ReadRecordsRequest(
            recordType = HeartRateRecord::class,
            timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
        )
        val response = client.readRecords(request)
        return response.records
    }

    suspend fun readSteps(
        startTime: Instant,
        endTime: Instant
    ): List<StepsRecord> {
        val request = ReadRecordsRequest(
            recordType = StepsRecord::class,
            timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
        )
        return client.readRecords(request).records
    }

    suspend fun readBloodPressure(
        startTime: Instant,
        endTime: Instant
    ): List<BloodPressureRecord> {
        val request = ReadRecordsRequest(
            recordType = BloodPressureRecord::class,
            timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
        )
        return client.readRecords(request).records
    }

    suspend fun readOxygenSaturation(
        startTime: Instant,
        endTime: Instant
    ): List<OxygenSaturationRecord> {
        val request = ReadRecordsRequest(
            recordType = OxygenSaturationRecord::class,
            timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
        )
        return client.readRecords(request).records
    }

    // Convert to FHIR-ready format
    fun toFhirObservation(
        record: HeartRateRecord,
        patientId: String
    ): Map<String, Any> {
        return mapOf(
            "resourceType" to "Observation",
            "status" to "final",
            "category" to listOf(mapOf(
                "coding" to listOf(mapOf(
                    "system" to "http://terminology.hl7.org/CodeSystem/observation-category",
                    "code" to "vital-signs"
                ))
            )),
            "code" to mapOf(
                "coding" to listOf(mapOf(
                    "system" to "http://loinc.org",
                    "code" to "8867-4",
                    "display" to "Heart rate"
                ))
            ),
            "subject" to mapOf("reference" to "Patient/$patientId"),
            "effectiveDateTime" to record.startTime.toString(),
            "valueQuantity" to mapOf(
                "value" to record.samples.first().beatsPerMinute,
                "unit" to "beats/minute",
                "system" to "http://unitsofmeasure.org",
                "code" to "/min"
            )
        )
    }
}

FHIR Observation Mapping: 20+ Health Data Types

Every health data type from HealthKit and Health Connect must map to a specific FHIR Observation with the correct LOINC code, unit of measure, and category. The following table provides the complete mapping for the most clinically relevant data types.

Health DataHealthKit TypeHealth Connect RecordLOINC CodeFHIR Unit
Heart RateHKQuantityType(.heartRate)HeartRateRecord8867-4/min
StepsHKQuantityType(.stepCount)StepsRecord55423-8{steps}/d
SpO2HKQuantityType(.oxygenSaturation)OxygenSaturationRecord59408-5%
Systolic BPHKQuantityType(.bloodPressureSystolic)BloodPressureRecord8480-6mm[Hg]
Diastolic BPHKQuantityType(.bloodPressureDiastolic)BloodPressureRecord8462-4mm[Hg]
Body WeightHKQuantityType(.bodyMass)WeightRecord29463-7kg
Body TempHKQuantityType(.bodyTemperature)BodyTemperatureRecord8310-5Cel
Respiratory RateHKQuantityType(.respiratoryRate)RespiratoryRateRecord9279-1/min
Blood GlucoseHKQuantityType(.bloodGlucose)BloodGlucoseRecord2339-0mg/dL
Active EnergyHKQuantityType(.activeEnergyBurned)ActiveCaloriesBurnedRecord41981-2kcal
Sleep DurationHKCategoryType(.sleepAnalysis)SleepSessionRecord93832-4h
HeightHKQuantityType(.height)HeightRecord8302-2cm
BMIHKQuantityType(.bodyMassIndex)n/a (calculated)39156-5kg/m2
HRV (SDNN)HKQuantityType(.heartRateVariabilitySDNN)n/a80404-7ms
VO2 MaxHKQuantityType(.vo2Max)Vo2MaxRecord60842-2mL/kg/min
Resting HRHKQuantityType(.restingHeartRate)RestingHeartRateRecord40443-4/min
Walking HR AvgHKQuantityType(.walkingHeartRateAverage)n/a8867-4/min
Distance WalkingHKQuantityType(.distanceWalkingRunning)DistanceRecord41953-1km
Flights ClimbedHKQuantityType(.flightsClimbed)FloorsClimbedRecord93831-6{floors}
Dietary EnergyHKQuantityType(.dietaryEnergyConsumed)NutritionRecord9052-2kcal

FHIR Observation Resource: Complete Structure

Each wearable data point becomes a FHIR Observation resource. Here is the complete structure for a heart rate reading from an Apple Watch, fully compliant with the FHIR Vital Signs profile:


{
  "resourceType": "Observation",
  "id": "hr-reading-20260316-1030",
  "meta": {
    "profile": ["http://hl7.org/fhir/StructureDefinition/vitalsigns"]
  },
  "status": "final",
  "category": [{
    "coding": [{
      "system": "http://terminology.hl7.org/CodeSystem/observation-category",
      "code": "vital-signs",
      "display": "Vital Signs"
    }]
  }],
  "code": {
    "coding": [{
      "system": "http://loinc.org",
      "code": "8867-4",
      "display": "Heart rate"
    }]
  },
  "subject": {
    "reference": "Patient/patient-123"
  },
  "effectiveDateTime": "2026-03-16T10:30:00-05:00",
  "valueQuantity": {
    "value": 72,
    "unit": "beats/minute",
    "system": "http://unitsofmeasure.org",
    "code": "/min"
  },
  "device": {
    "reference": "Device/apple-watch-series-9",
    "display": "Apple Watch Series 9"
  },
  "extension": [{
    "url": "http://example.org/fhir/StructureDefinition/data-quality",
    "valueString": "consumer-grade"
  }]
}

Consent Management: Patient-Controlled Data Sharing

Both HealthKit and Health Connect enforce granular, patient-controlled permissions. Patients decide exactly which data types to share. Your application must respect these boundaries and build a transparent consent flow that builds trust.

Consent Architecture

  • Platform-level consent: HealthKit/Health Connect permissions dialog — patient grants read access to specific data types
  • Application-level consent: Your app explains how data will be used, who sees it, and how to revoke
  • FHIR Consent resource: Store the consent decision as a FHIR Consent resource for audit trail and compliance
  • Revocation: Patient can revoke at any time — app must stop querying revoked data types immediately

{
  "resourceType": "Consent",
  "status": "active",
  "scope": {
    "coding": [{
      "system": "http://terminology.hl7.org/CodeSystem/consentscope",
      "code": "patient-privacy"
    }]
  },
  "category": [{
    "coding": [{
      "system": "http://loinc.org",
      "code": "59284-0",
      "display": "Consent Document"
    }]
  }],
  "patient": {
    "reference": "Patient/patient-123"
  },
  "dateTime": "2026-03-16T09:00:00Z",
  "provision": {
    "type": "permit",
    "data": [
      {"meaning": "instance", "reference": {"display": "Heart Rate (8867-4)"}},
      {"meaning": "instance", "reference": {"display": "Steps (55423-8)"}},
      {"meaning": "instance", "reference": {"display": "Sleep (93832-4)"}},
      {"meaning": "instance", "reference": {"display": "SpO2 (59408-5)"}}
    ]
  }
}

Data Quality: Consumer-Grade vs Medical-Grade

Consumer wearables are not FDA-cleared medical devices (with few exceptions like Apple Watch ECG and Dexcom CGM). This creates important data quality considerations when ingesting wearable data into clinical systems.

MetricConsumer Wearable AccuracyMedical Device AccuracyClinical Implication
Heart Rate+/- 5 BPM (optical PPG)+/- 1 BPM (ECG)Sufficient for trends, not diagnostics
SpO2+/- 3% (wrist-based)+/- 2% (fingertip pulse ox)May miss mild desaturation
Steps+/- 10% (accelerometer)N/AGood for activity tracking
Sleep Stages~80% agreement with PSG100% (polysomnography)Useful for patterns, not diagnosis
Blood PressureNot measured (most wearables)+/- 3 mmHg (validated cuff)Requires dedicated device
Blood GlucoseN/A (CGMs are medical devices)+/- 10-15% (CGM vs lab)CGMs like Dexcom are FDA-cleared

Data Quality Mitigation Strategies

  1. Confidence scoring: Tag each Observation with a data quality extension indicating consumer-grade vs medical-grade source
  2. Outlier filtering: Reject physiologically impossible values (HR > 250 or < 20, SpO2 < 50%)
  3. Trend analysis over point values: Use rolling averages rather than individual readings for clinical decisions
  4. Source device tracking: Record the Device resource so clinicians know the data source (Apple Watch vs Masimo pulse ox)
  5. Wear-time validation: Only count data from periods of confirmed device wear (motion sensor cross-reference)

Cloud Backend Architecture

The backend service that receives wearable data from mobile apps and converts it to FHIR must handle high-volume ingestion, data validation, FHIR conversion, and delivery to EHR systems.

Key Components

  • API Gateway: Receives authenticated REST calls from mobile apps, rate limiting, request validation
  • Data Validation Service: Filters outliers, validates physiologic ranges, checks timestamps, deduplicates
  • FHIR Conversion Engine: Maps platform-specific data types to FHIR Observation resources with correct LOINC codes, units, and profiles
  • Consent Service: Checks patient consent status before processing each data type — respects revocations in real-time
  • FHIR Server: Stores Observations as FHIR R4 resources, supports search, subscriptions, and bulk export
  • Alert Engine: Monitors incoming data against clinical thresholds — triggers notifications for abnormal values. Integrates with clinical decision support systems for intelligent alerting
  • EHR Integration: Pushes relevant Observations to the EHR system via FHIR API or HL7v2 interface

Supported Wearable Ecosystem

The breadth of supported devices determines how many patients can participate in your wearable data program. Here is the current ecosystem by platform:

DeviceHealthKit (iOS)Health Connect (Android)Key Data Types
Apple WatchNativeN/AHR, HRV, ECG, SpO2, Steps, Sleep, VO2 Max
FitbitVia appNativeHR, Steps, Sleep, SpO2, Active Minutes
Oura RingYesYesHR, HRV, Sleep Stages, Body Temp, Readiness
GarminYesYesHR, Steps, Sleep, SpO2, Stress, Body Battery
Samsung Galaxy WatchN/ANativeHR, BP (select models), SpO2, Sleep, ECG
WithingsYesYesHR, BP, Weight, SpO2, Sleep, ECG, Temp
Dexcom CGMYesYesContinuous Glucose (5-min intervals)
WhoopYesComing 2026HR, HRV, Sleep, Strain, Recovery

RPM Program Integration: Wearables as Monitoring Devices

Consumer wearable data, when properly integrated via HealthKit/Health Connect and FHIR, can support Remote Patient Monitoring (RPM) programs. However, RPM billing requires FDA-cleared devices — not all consumer wearables qualify. Devices like Withings BPM Connect (FDA-cleared BP cuff with HealthKit integration) and Dexcom G7 (FDA-cleared CGM) bridge the gap between consumer convenience and clinical billing requirements.

The architecture described in this article supports both FDA-cleared device data (billable under RPM) and consumer wearable data (clinically useful but not billable) through the same pipeline — tagged appropriately with device metadata and data quality indicators.

Conclusion: 

The combination of Apple HealthKit, Google Health Connect, and FHIR creates a standardized pipeline for bringing consumer wearable data into clinical workflows. The code examples, mapping tables, and architecture patterns in this guide provide everything needed to build a production-ready integration.

The key to success is treating consumer wearable data as complementary to — not a replacement for — clinical-grade monitoring. Tag data quality, respect patient consent, validate readings, and present trends rather than point values to clinicians. For organizations building remote patient monitoring solutions or healthcare technology platforms, wearable data integration is rapidly becoming a competitive requirement — and the technical barriers are lower than ever.

Ready to scale?

Talk to our healthcare engineering team about building, integrating, and shipping faster.

Frequently Asked Questions

Can I use Apple Watch data for RPM billing?

Not directly. Most Apple Watch sensors are not FDA-cleared for clinical monitoring. However, the Apple Watch ECG (De Novo FDA clearance) and certain third-party accessories that connect through HealthKit (Withings BP cuff, Dexcom CGM) are FDA-cleared and can support RPM billing. The wearable data pipeline described here supports both billable and non-billable data streams.

How do I handle patients who use both iOS and Android devices?

Build both HealthKit and Health Connect integrations in your mobile app. The cloud backend receives data in a normalized format regardless of source platform. The FHIR Observation resource is identical whether the data originated from HealthKit or Health Connect — the only difference is the Device reference indicating the source.

What about HIPAA compliance for wearable data?

Once wearable data enters your healthcare application, it becomes Protected Health Information (PHI) and must comply with HIPAA. This means: encrypted transmission (TLS 1.2+), encrypted storage, access controls, audit logging, and Business Associate Agreements with cloud providers. HealthKit and Health Connect data stored on the patient's device is not PHI until your app accesses and transmits it.

How frequently should the app sync wearable data?

For RPM programs, daily sync is sufficient. For acute monitoring scenarios, use HealthKit background delivery (immediate notifications on new data) or Health Connect change tokens (periodic polling). Avoid syncing raw sensor data at full frequency (every second) — aggregate to clinically meaningful intervals (every 5 minutes for HR, daily for steps, nightly for sleep).

Can wearable data trigger clinical alerts?

Yes, but with caveats. Consumer wearable data should trigger informational alerts (e.g., "Patient's resting heart rate trend increased 15% this week") rather than urgent clinical alerts. Reserve critical alerts for FDA-cleared device data. Implement a tiered alerting system: consumer data triggers trend-based notifications, medical device data triggers threshold-based urgent alerts.

How does Nirmitee.io approach healthcare software development?

Nirmitee.io follows a healthcare-first engineering approach — every solution is built with HIPAA compliance, HL7/FHIR interoperability standards, and clinical workflow requirements at the core. We combine domain expertise with modern engineering practices to deliver production-ready systems.
Share