What wearable app development actually looks like in 2026
Key Takeaways
- OAuth flows differ by provider: token lifetimes, refresh behavior, and authorization endpoints are not standardized across wearable APIs.
- Polling vs. webhooks is not a choice you make once: Garmin delivers data exclusively via webhook; other providers require polling. Your architecture has to handle both.
- Field names and units vary across providers. Heart rate might be
heart_rate,heartRate, orhr_bpm. Without normalization, every query becomes a provider-specific special case. - Cross-provider deduplication is your problem: a user wearing Garmin and Apple Watch produces separate records per provider. OW stores them separately; merging is application-level logic.
- Apple Health has no cloud API: it requires a mobile SDK. Any architecture that assumes all providers work the same way will break here.
- Open Wearables is a self-hosted, MIT-licensed data layer that normalizes these differences at ingestion time, exposing a single set of endpoints regardless of source device.
- Setup takes under five minutes: Docker Compose, one API key header, and you have a running local instance with Swagger docs at
localhost:8000/docs.
Most tutorials about wearable app development start with OAuth and end with a JSON payload containing heart rate data. What they skip is everything in between: the provider-specific token refresh schedules, the fact that "sleep" means different things depending on which device recorded it, the webhook-only providers that never respond to polling, and the quiet chaos of a user who owns two devices from different ecosystems. By the time you have handled all of that cleanly, you have built an integration platform. This article describes what that actually looks like, and where you can take pieces off the shelf instead of building them yourself.
What building the data layer yourself actually involves
The first thing you hit is OAuth. Each provider has its own authorization flow, its own token format, and its own rules about expiry. Strava tokens expire after six hours. Whoop requires an active paid membership before the OAuth flow will succeed. Garmin's authorization model is structured differently from a standard OAuth 2.0 flow. You write one integration, it works, then you write the next one and none of the assumptions transfer cleanly.
Token refresh is its own layer. Some providers return a new refresh token with every access token exchange. Others reuse the same refresh token indefinitely. If you build a refresh handler that assumes one model, it will silently fail on providers that use the other.
Then there is the polling vs. webhook split. Most providers let you pull data on demand. Garmin does not. Garmin pushes data to a registered webhook endpoint; there is no /sync call that works. If your architecture assumes you can pull when you need data, Garmin will not fit into it without a structural change.
Field names and units are the part that takes the most calendar time even if it is not the hardest conceptually. One provider returns heart_rate_bpm, another returns heartRate, another returns a nested object. Distance might be in meters, kilometers, or miles depending on the provider and sometimes on the user's account settings. Timestamps might be Unix epoch, ISO 8601 with a timezone, or ISO 8601 in UTC. None of this is wrong on the provider's part. It is just not standardized.
Deduplication adds another layer. A user who connects both Garmin and Apple Watch will produce two sets of workout records for the same activity. They are not duplicates in the strict sense: they come from different sources and may have different field values. But they represent the same event. Deciding how to handle that is a non-trivial application design question, and it is not one a generic library can answer for you.
Where the complexity hides
The OAuth and field-name issues are visible early. The harder problems surface later.
"Sleep" is not a universal concept across wearable providers. One provider might return a single sleep block with a start and end time. Another returns a breakdown by sleep stage with separate records for light, deep, and REM. A third returns a sleep score and a summary but no raw stage data at all. If your application needs to compare sleep quality across providers, you are writing normalization logic that requires understanding each provider's data model, not just mapping field names.
Garmin's webhook-only architecture means your infrastructure needs an always-available endpoint to receive pushes. During development, that means running something like ngrok or a deployed staging environment. It also means that if your webhook endpoint is down, you miss data. There is no catch-up mechanism built into Garmin's API. You need to implement your own gap detection.
Cross-provider users are where deduplication gets genuinely complex. Imagine a user who records a run on their Garmin watch and then syncs the same run to Strava via Garmin Connect. OW stores a record from Garmin and a separate record from Strava. They are different records from different providers. They may have different GPS traces, different heart rate averages, or different timestamps depending on sync lag. Whether those are "the same workout" is an application-level decision. OW does not auto-merge them, and that is the correct behavior: any auto-merge logic would require assumptions about what "same" means that differ by application.
Apple Health is a special case that affects mobile architecture decisions. There is no cloud sync for Apple Health data. If you need Apple Health data in your backend, you need a mobile SDK that reads from HealthKit on-device and pushes to your API. Any backend-only integration architecture will not reach Apple Health data at all.
What Open Wearables handles
Open Wearables is a self-hosted data layer that sits between your application and the wearable provider APIs. It handles OAuth flows, token refresh, webhook ingestion, and normalization. Your application talks to one API regardless of which providers the user has connected.
The setup is a Docker Compose stack:
git clone https://github.com/the-momentum/open-wearables.git
cd open-wearables
docker compose up -d
The API runs at http://localhost:8000. The admin panel is at localhost:3000. Swagger documentation is at localhost:8000/docs.
Authentication uses a header, not a Bearer token:
X-Open-Wearables-API-Key: YOUR_API_KEY
OAuth connections are initiated through a provider-specific authorize endpoint:
GET /api/v1/oauth/{provider}/authorize?user_id={id}&redirect_uri={url}
OW handles the token exchange, stores credentials, and manages refresh automatically. Strava's six-hour token expiry and Whoop's refresh behavior are handled internally.
Normalized data is available through a consistent set of endpoints regardless of source provider:
GET /api/v1/users/{user_id}/events/workouts
GET /api/v1/users/{user_id}/events/sleep
GET /api/v1/users/{user_id}/timeseries
GET /api/v1/users/{user_id}/summaries/
Canonical units are applied at ingestion: heart rate in bpm, distance in meters, temperature in Celsius, timestamps in UTC. You do not write unit conversion logic in your application.
Webhook delivery to your own services is available through a registered endpoint:
POST /api/v1/webhooks/endpoints
Here is a minimal Python example pulling workout data for a user:
import requests
BASE_URL = "http://localhost:8000"
API_KEY = "YOUR_API_KEY"
USER_ID = "user_123"
headers = {"X-Open-Wearables-API-Key": API_KEY}
response = requests.get(
f"{BASE_URL}/api/v1/users/{USER_ID}/events/workouts",
headers=headers
)
workouts = response.json()
The response structure is the same regardless of whether the workout came from Garmin, Polar, Strava, or Suunto. The provider field tells you where each record originated. Cross-provider deduplication remains your responsibility at the application layer, but the data you are comparing is already normalized.
For mobile integrations: Apple Health, Google Health Connect, and Samsung Health are supported through a mobile SDK. The cloud providers use the API directly.
Getting started
git clone https://github.com/the-momentum/open-wearables.git
cd open-wearables
docker compose up -d
API at http://localhost:8000. Admin at localhost:3000. Swagger at localhost:8000/docs.
Open Wearables is MIT-licensed and self-hosted. The quickstart with provider configuration steps is at docs.openwearables.io/quickstart.
Related articles
- How to sync wearable data from multiple devices
- How to normalize wearable data across providers
- How to build an AI health coaching app with wearable data
- Wearable API integration: comparing SaaS, custom build, and open source
FAQ
Does Open Wearables handle Garmin's webhook-only delivery?
Yes. OW registers and manages Garmin webhook endpoints. You do not need to set up separate webhook infrastructure for Garmin; data arrives through the same normalized API as other providers.
Can I use Open Wearables with Apple Health?
Apple Health requires the mobile SDK. There is no cloud API for HealthKit data, so backend-only integrations cannot reach it. The OW mobile SDK reads from HealthKit on-device and sends data to your OW instance.
How does OW handle token expiry for providers like Strava?
Token refresh is managed internally. Strava tokens expire after six hours; OW refreshes them automatically before expiry. Your application code does not need to handle provider-specific token lifecycle logic.
What happens when a user connects multiple devices that record the same activity?
OW stores records from each provider separately, deduplicated by external_id within a provider. A run recorded on Garmin and synced to Strava will produce two records. Merging them is application-level logic, because the definition of "same activity" depends on your use case.
Is there a managed cloud version?
Open Wearables is designed for self-hosting. The Docker Compose setup runs on any server with Docker installed. For teams that want a managed deployment, contact the Momentum team at the link in the documentation.
Never miss an update. Join our community. No spam ever.