Skip to main content
Access Apple HealthKit from your web app through the Despia native bridge. Despia gives you three ways to work with health data: read historical records on demand, write new samples back to HealthKit, and subscribe to live updates via server webhooks. All three work from JavaScript with no native code required.
HealthKit is iOS only. Always gate calls behind an isDespiaIOS check so your app degrades gracefully in a browser or on Android.

Installation

npm install despia-native
import despia from 'despia-native';

Choosing the right approach

HealthKit data can be accessed in three ways. Which one to use depends on whether you need data right now, want to save data, or need to react to changes continuously.
ApproachUse whenHow it works
ReadYou need historical data at a specific moment - on page load, on a button tap, or when building a reportCall readhealthkit:// and await the result. Returns records for the last N days.
WriteYou want to save a value back to HealthKit - for example, logging a weight entry or a step count from your own sensorCall writehealthkit:// with a value. Adds a new sample without replacing existing data.
ObserverYou need your server to stay in sync with the user’s health data automatically, without requiring the user to open the appRegister a webhook with healthkit://observe. Despia posts new data to your server whenever HealthKit updates, even in the background.
A fitness app that shows a weekly steps chart uses read - it fetches data when the user opens the dashboard. A coaching platform that tracks whether a user hit their daily goal uses observer - the server receives updates automatically and can send a push notification without any user action. An app that lets users log body weight from a form uses write to save the entry back to Apple Health. You can combine all three. Most apps that use observer also use read to populate the initial UI before the first webhook fires.

How it works

Define the isDespiaIOS check once at the top of your app and reuse it throughout. Despia requests HealthKit permission on the first call for each identifier, then fetches and returns the data.
const isDespiaIOS = navigator.userAgent.toLowerCase().includes('despia') && (
    navigator.userAgent.toLowerCase().includes('iphone') ||
    navigator.userAgent.toLowerCase().includes('ipad')
)

if (isDespiaIOS) {
    const data  = await despia('readhealthkit://HKQuantityTypeIdentifierStepCount?days=7', ['healthkitResponse'])
    const steps = data.healthkitResponse.HKQuantityTypeIdentifierStepCount
    // [{ "date": "2025-11-17", "value": 9820, "unit": "count" }, ...]
}

Read

Use readhealthkit:// to fetch historical data on demand. This is the right approach when your UI needs to display health data at a point in time - on load, after a user action, or when generating a report. You control exactly when the fetch happens and how many days of history to retrieve. Pass any valid HKQuantityTypeIdentifier, HKCategoryTypeIdentifier, HKWorkoutTypeIdentifier, or HKCharacteristicTypeIdentifier as the URL host, plus an optional days parameter. See Identifier reference for the full list.

Quantity types

Quantity types cover numeric metrics: steps, heart rate, distance, body mass, calories, and more. Each record represents one day’s value for the requested identifier.
if (isDespiaIOS) {
    const data      = await despia('readhealthkit://HKQuantityTypeIdentifierHeartRate?days=30', ['healthkitResponse'])
    const heartRate = data.healthkitResponse.HKQuantityTypeIdentifierHeartRate
}
[
  { "date": "2025-11-16", "value": 68, "unit": "count/min" },
  { "date": "2025-11-17", "value": 72, "unit": "count/min" },
  { "date": "2025-11-18", "value": 70, "unit": "count/min" }
]
date
string
Start of day in ISO 8601 format
value
number
The health metric value for that day
unit
string
Unit of measurement, e.g. count, count/min, kg, m. Determined automatically by the identifier.

Sleep data

Sleep analysis uses HKCategoryTypeIdentifierSleepAnalysis. Unlike quantity types, sleep returns individual stage intervals rather than daily aggregates - each record covers a contiguous block of one sleep stage with its own start and end time.
if (isDespiaIOS) {
    const data  = await despia('readhealthkit://HKCategoryTypeIdentifierSleepAnalysis?days=7', ['healthkitResponse'])
    const sleep = data.healthkitResponse.HKCategoryTypeIdentifierSleepAnalysis
}
[
  { "startDate": "2025-11-17T22:15:00Z", "endDate": "2025-11-17T22:45:00Z", "value": 0, "label": "inBed" },
  { "startDate": "2025-11-17T22:45:00Z", "endDate": "2025-11-18T00:30:00Z", "value": 3, "label": "core" },
  { "startDate": "2025-11-18T00:30:00Z", "endDate": "2025-11-18T02:15:00Z", "value": 4, "label": "deep" },
  { "startDate": "2025-11-18T02:15:00Z", "endDate": "2025-11-18T03:45:00Z", "value": 5, "label": "rem"  },
  { "startDate": "2025-11-18T03:45:00Z", "endDate": "2025-11-18T06:00:00Z", "value": 3, "label": "core" },
  { "startDate": "2025-11-18T06:00:00Z", "endDate": "2025-11-18T06:20:00Z", "value": 2, "label": "awake" }
]
startDate
string
ISO 8601 start time of the sleep stage interval
endDate
string
ISO 8601 end time of the sleep stage interval
value
number
Raw integer from HKCategoryValueSleepAnalysis
label
string
Human-readable stage: inBed, awake, asleep, core, deep, or rem

Workouts

Use HKWorkoutTypeIdentifier to fetch workout sessions. Each record includes the activity type, duration, calories burned, and distance where applicable.
if (isDespiaIOS) {
    const data     = await despia('readhealthkit://HKWorkoutTypeIdentifier?days=14', ['healthkitResponse'])
    const workouts = data.healthkitResponse.HKWorkoutTypeIdentifier
}
[
  {
    "date": "2025-11-16T07:12:00Z",
    "activityType": "running",
    "duration": 1820,
    "calories": 310,
    "distance": 5200,
    "value": 5200,
    "unit": "m"
  },
  {
    "date": "2025-11-17T08:30:00Z",
    "activityType": "hiit",
    "duration": 2400,
    "calories": 420,
    "distance": 0,
    "value": 2400,
    "unit": "s"
  },
  {
    "date": "2025-11-18T06:45:00Z",
    "activityType": "yoga",
    "duration": 3600,
    "calories": 180,
    "distance": 0,
    "value": 3600,
    "unit": "s"
  }
]
date
string
ISO 8601 start time of the workout
activityType
string
Activity name such as running, hiit, or yoga
duration
number
Duration in seconds
calories
number
Kilocalories burned
distance
number
Distance in meters. 0 if not applicable for the activity type
value
number
Primary metric: distance if available, calories if not, otherwise duration
unit
string
Unit of value: m, kcal, or s
ValueValueValue
americanFootballarcheryaustralianFootball
badmintonbaseballbasketball
barrebowlingboxing
climbingcoreTrainingcricket
crossCountrySkiingcrossTrainingcurling
cyclingdancedownhillSkiing
ellipticalequestrianSportsfencing
fishingflexibilityfitnessGaming
functionalStrengthTraininggolfgymnastics
handballhiithiking
hockeyhuntingjumpRope
kickboxinglacrossemartialArts
mindAndBodymixedCardiopaddleSports
pilatesplaypreparationAndRecovery
racquetballrowingrugby
runningsailingskatingSports
snowSportssnowboardingsoccer
softballsquashstairClimbing
stairsstepTrainingsurfingSports
swimmingtableTennistaiChi
tennistrackAndFieldtraditionalStrengthTraining
volleyballwalkingwaterFitness
waterPolowaterSportswheelchairWalkPace
wheelchairRunPacewrestlingyoga
other

Characteristics

Characteristics such as date of birth, biological sex, and blood type are static values that do not change over time. They return a plain string or raw integer and do not accept a days parameter.
if (isDespiaIOS) {
    const data = await despia('readhealthkit://HKCharacteristicTypeIdentifierDateOfBirth', ['healthkitResponse'])
    const dob  = data.healthkitResponse.HKCharacteristicTypeIdentifierDateOfBirth
}
"1990-06-15T00:00:00Z"
IdentifierReturns
HKCharacteristicTypeIdentifierDateOfBirthISO 8601 date string
HKCharacteristicTypeIdentifierBiologicalSexRaw integer from HKBiologicalSex
HKCharacteristicTypeIdentifierBloodTypeRaw integer from HKBloodType
HKCharacteristicTypeIdentifierFitzpatrickSkinTypeRaw integer from HKFitzpatrickSkinType

Write

Use writehealthkit:// to save a value back to HealthKit. This is useful when your app collects health data that should also live in Apple Health - for example, a weight logging form, a manual step entry, or a calorie tracker. Writing adds a new sample to HealthKit without removing or replacing any existing records. The URL format is writehealthkit://IdentifierString//Value. The unit is resolved automatically from the identifier.
if (isDespiaIOS) {
    despia('writehealthkit://HKQuantityTypeIdentifierBodyMass//74.5')
    despia('writehealthkit://HKQuantityTypeIdentifierStepCount//10000')
}
identifier
string
required
Any writable HKQuantityTypeIdentifier, passed as the URL host (e.g. HKQuantityTypeIdentifierBodyMass)
value
number
required
Numeric value to write. The unit is determined automatically from the identifier - see the Identifier reference.
Write is one-directional. It adds samples to HealthKit but does not delete or modify existing ones. If you need to correct a previously written value, write a new sample - HealthKit stores the history and surfaces the most recent reading.

Observer

Use observers when you need your server to stay current with a user’s health data automatically, without requiring the user to open your app. Observers register a background listener on the device that watches one or more HealthKit types. When new data arrives - from Apple Watch, a connected sensor, or another app - Despia posts a webhook to your server with the latest records. This is fundamentally different from read. Read is a point-in-time fetch that your code initiates. An observer is a persistent subscription that fires on Despia’s side whenever HealthKit tells it something changed. The user does not need to be in the app for a webhook to fire. Common uses for observers include: tracking whether a user hit a daily step goal, alerting a coach when a client logs a workout, syncing sleep data nightly to a backend, and triggering push notifications based on health events.

Registering an observer

if (isDespiaIOS) {
    despia('healthkit://observe?types=HKQuantityTypeIdentifierStepCount,HKCategoryTypeIdentifierSleepAnalysis&frequency=hourly&server=https://your-server.com/webhook')
}
Observers persist across app restarts automatically. You only need to register them once - typically on app load or after the user enables health tracking in your app.
types
string
required
Comma-separated list of HealthKit identifiers to observe
frequency
string
How often Despia delivers batched updates to your server: immediate, hourly, daily, or weekly. Defaults to immediate. Use hourly or daily for high-volume types like steps to avoid unnecessary webhook traffic.
server
string
required
Full URL of the endpoint that receives webhook POSTs. Append your own query parameters here to identify the user on your server.

Identifying users in webhooks

Webhooks include a userId field containing the Despia device ID. If you need to map webhook events to your own user records, append your user identifier as a query parameter on the server URL when registering the observer. Your server receives it as part of the request URL.
const myUserId = "user_abc123"

if (isDespiaIOS) {
    despia(`healthkit://observe?types=HKQuantityTypeIdentifierStepCount&frequency=hourly&server=https://your-server.com/webhook?user=${myUserId}`)
}
Your server receives POST /webhook?user=user_abc123 and can route the event directly to the right user record without any device ID mapping.

Webhook payload

Despia sends a POST with Content-Type: application/json each time an observed type updates. The data object contains one key per observed type, each holding an array of the most recent records (last 1 day).
{
  "event": "update",
  "userId": "abc123-device-id",
  "timestamp": "2025-11-18T08:00:00Z",
  "data": {
    "HKQuantityTypeIdentifierStepCount": [
      { "date": "2025-11-18", "value": 4210, "unit": "count" }
    ]
  }
}
event
string
Always update
userId
string
Despia device ID, consistent across all events from the same device. For your own user IDs, use a query parameter on the server URL instead.
timestamp
string
ISO 8601 timestamp of when the webhook was sent
data
object
One key per observed type, each containing an array of records matching the read response shape for that type

Checking active observers

despia.observingHealthKit holds an array of currently active identifier strings. It is undefined before any observe or unobserve call has been made, and [] once all observers have been stopped. Use this to check whether an observer is already registered before calling observe again.
if (despia.observingHealthKit?.length > 0) {
    console.log('Active observers:', despia.observingHealthKit)
    // ["HKQuantityTypeIdentifierStepCount", "HKCategoryTypeIdentifierSleepAnalysis"]
}

Stopping observers

Pass all to stop every active observer, or a comma-separated list of identifiers to stop specific types. The response contains the updated list of remaining active observers.
if (isDespiaIOS) {
    // Stop all observers
    const data = await despia('healthkit://unobserve?types=all', ['observingHealthKit'])
    console.log(data.observingHealthKit) // []

    // Stop a specific type
    const data = await despia('healthkit://unobserve?types=HKQuantityTypeIdentifierStepCount', ['observingHealthKit'])
    console.log(data.observingHealthKit) // ["HKCategoryTypeIdentifierSleepAnalysis"]
}

Identifier reference

Despia supports all four HealthKit identifier categories. Pass any valid identifier string directly to readhealthkit:// or writehealthkit:// - Despia resolves the type and unit automatically.

Constructing identifiers

Every identifier follows a predictable pattern based on its category:
TypePrefixExample
QuantityHKQuantityTypeIdentifierHKQuantityTypeIdentifierStepCount
CategoryHKCategoryTypeIdentifierHKCategoryTypeIdentifierSleepAnalysis
WorkoutHKWorkoutTypeIdentifierHKWorkoutTypeIdentifier
CharacteristicHKCharacteristicTypeIdentifierHKCharacteristicTypeIdentifierDateOfBirth
Take the type name from Apple’s documentation and prepend the matching prefix. For example, Apple lists StepCount under HKQuantityTypeIdentifier - the full identifier string is HKQuantityTypeIdentifierStepCount.

Apple Documentation

HKQuantityTypeIdentifier

Steps, heart rate, distance, body mass, nutrition, and all other numeric types

HKCategoryTypeIdentifier

Sleep analysis, mindfulness, and other category-based types

HKWorkoutActivityType

All workout activity types returned in the activityType field

HKCharacteristicTypeIdentifier

Date of birth, biological sex, blood type, and skin type

Resources

NPM Package

despia-native