Skip to main content
Need help with your Google Health integration? Pop into our Discord if you have questions or want to discover how Open Wearables can solve your problems.

Overview

Google Health data reaches Open Wearables through two independent paths, each its own provider:
  • Health Connect (mobile SDK) — provider health_connect. Data pushed from an Android device via the Mobile SDK. See the Android SDK guide.
  • Google Health API (cloud OAuth) — provider google_health, this guide. A server-side OAuth 2.0 flow against the Google Health API that lets you connect a user’s Google account and pull their data over REST, plus receive notify-only webhooks.
They have separate scopes, connections and lifecycles: connecting one leaves the other untouched, and each reports its own connection.created / connection.revoked events.
google_health is this provider’s name everywhere — in payloads, in ?provider= filters, and in the URLs new integrations should use. Before the split it was reachable at /google/; the OAuth and webhook endpoints still serve that path so anything already registered with Google keeps working, but it is a compatibility path that goes away in 1.0.
The Google Health API is an aggregation layer: it surfaces data from every source connected to the user’s Google Health account — the phone’s Health Connect store, Fitbit, Google Fit, and other apps — not just one device.

Fitbit devices

This integration is the successor to the Fitbit integration. Google turns the legacy Fitbit Web API off on November 2, 2026 and points integrations to the Google Health API (migration guide). Our Fitbit integration is feature-frozen and only syncs workouts; sleep, heart rate, HRV, SpO2 and the other data types below come from Fitbit devices through google_health. Users connect with their Google account and have to consent again - Fitbit tokens cannot be carried over. Existing Fitbit users must first move their Fitbit account to their Google Account. Until they do, OAuth consent still succeeds but Google Health API calls fail with ACCOUNT_NOT_LINKED, so syncs for that connection fail.

Supported data types

Data delivery

Data granularity and fetch modes

The Health API exposes the same underlying data through three different operations. Open Wearables reads every 24/7 metric at native resolution, picking the operation per the GOOGLE_USE_RECONCILE flag.
Windowed dataPoints:rollUp aggregates are currently disabled. A rollUp window starts wherever the requested range does, so the same reading came back under a different timestamp on a later sync and was inserted as a duplicate rather than updated.Because hourly and daily cannot be honoured without it, Google Health refuses them rather than storing raw data under an aggregating setting: a scheduled pull is reported as a failed data_247 sync, and a webhook notification is reported to the error tracker and dropped (it is not answered with a 5xx, which Google would only retry). Sleep sessions and the dailyRollUp totals are unaffected and keep syncing. Set the provider’s granularity to raw to resume the rest.
Daily basal energy and active minutes are the exceptions: they come from dataPoints:dailyRollUp, whose windows are anchored to civil days and so do not drift.

Reconcile vs. list — why it matters

A user commonly has the same activity reported by multiple sources (e.g. the phone’s pedometer and the Fitbit app both counting steps). The two modes handle that overlap differently:
  • reconcile (default) returns one merged stream, deduplicated across all sources at the interval level — exactly what the native Google Health / Fitbit app displays. There is no single device behind a merged value, so reconciled points carry no device attribution (device_model and device_type are empty).
  • list returns the raw per-source points, each tagged with its originating device; device_type comes from the data source’s formFactor. This preserves device attribution but stores overlapping sources separately; Open Wearables then deduplicates on read by source priority (it does not sum them).
A concrete example — steps for one day for a user tracked by both their phone (Health Connect) and Fitbit MobileTrack: Reconcile (2560) matches the app exactly — it keeps intervals that either source captured while removing the overlap. Read-time dedup of the list data would instead pick a single source (1919), which is why reconcile is the default: it produces app-accurate totals.
Choose list (GOOGLE_USE_RECONCILE=false) only when you specifically need per-device attribution (e.g. to know which watch recorded a reading). For app-matching totals, keep the default.

What you need by the end

  • A Google Cloud project with the Health API enabled
  • OAuth client credentials (Client ID + Secret) with the Google Health scopes
  • Redirect URI registered in your OAuth client
  • (Webhooks only) A service account for project-level subscriber registration
  • (Production only) A verified OAuth app and a CASA Letter of Validation — see Verification and security assessment

Prerequisites

  • A Google Cloud project
  • Access to the Google Health API for that project (gcloud services enable health.googleapis.com)

Application walkthrough

1

Enable the Health API and create an OAuth client

In the Google Cloud Console, select your project and enable the Health API:
Then create an OAuth 2.0 Client ID (Web application) under APIs & Services → Credentials, and add your server-side callback as an Authorized redirect URI:
  • http://localhost:8000/api/v1/oauth/google_health/callback
Then set GOOGLE_LEGACY_OAUTH_PATH=false so Open Wearables sends that URI. New integrations should always use this path.
Upgrading from before the provider split? This provider was reachable at /oauth/google/ back then, and the flag defaults to true so your registered OAuth client keeps working untouched. That default is a compatibility patch, not the intended configuration — it is removed in 1.0. Register the google_health URI above, set the flag to false, and you are on the supported path. Google accepts several redirect URIs, so both can be registered while you migrate.
Store the Client Secret securely — Google shows it once.
2

Configure credentials in Open Wearables

Add the following to your .env file:
Configuration details:
The redirect URI is derived from API_BASE_URL and must match an Authorized redirect URI on your OAuth client exactly: {API_BASE_URL}/api/v1/oauth/google_health/callback.
3

Connect a user via OAuth

With credentials configured and your instance running, initiate the OAuth flow.
Using the Open Wearables frontend? You don’t need any of the curls below — open the connect view and click Connect on Google Health. The frontend runs this whole authorize → consent → callback → verify flow for you. The steps below are for integrating directly against the API.
1. Get the authorization URL:
2. Redirect the user to the returned authorization_url. They log in to Google and grant consent.3. Google redirects back to {API_BASE_URL}/api/v1/oauth/google_health/callback; Open Wearables exchanges the code for tokens and stores the connection. It also resolves the user’s stable healthUserId (via the Health API identity endpoint) as the connection’s provider user id — this is what inbound webhooks are matched against.4. Verify the connection:
You should see a connection with "provider": "google_health" and "status": "active".
4

Sync data

An initial sync runs automatically after a successful OAuth connection. To trigger one manually:
5

Verify the integration

Fetch a synced 24/7 series (e.g. steps):
If data is returned, your Google Health API integration is working end-to-end.

Verification and security assessment

Every googlehealth.* scope Open Wearables requests is a restricted scope, and Google gates those behind two independent reviews. Both run against your Google Cloud project — the OAuth client, consent screen and privacy policy are yours, so nobody can be verified on your behalf. Start early: the security assessment alone runs weeks, and it has to be redone every year.
Google publishes no end-to-end figure. The parts it does publish — days for branding, “several weeks” for the scope review, 2-6 weeks for the assessment — put a realistic first pass at one to two months, so start well before you need it. The assessment is annual, so set a reminder a month before each anniversary rather than letting restricted-scope access lapse.

Does this apply to you?

Verification attaches to the Google Cloud project — its consent screen and branding — not to an individual OAuth client, so every client in a verified project rides on the same submission. Keep a separate project for staging: Google exempts apps in the development, testing or staging phases, so that one stays in Testing and is never reviewed even though it requests the same restricted scopes.
Most rejections happen on branding and policy grounds, before anyone looks at your scopes. Have all four ready first.Verified domain ownership. Every domain used in your consent screen links must be verified in Google Search Console by an account with owner permission — the same account that submits the app. Google verifies the top private domain, so example.com covers https://app.example.com/connect.A public home page. Reachable without logging in, and clearly about the app being verified. A parked domain or a generic company landing page is rejected.A privacy policy that names Google user data. Hosted on a verified domain, linked from the consent screen, and explicit about how you access, use, store and share Google user data. It must also commit to the Limited Use requirements: the data only powers user-facing features, is not transferred except as permitted, is not read by humans without the user’s affirmative agreement, and is never used for advertising, retargeting, data brokerage or credit decisions.An in-app prominent disclosure. A separate requirement from the privacy policy, and a common reason for a second review round. Google requires that it:
  • is in the app itself, not only in the app description or on a website
  • appears in normal usage, without the user navigating into a menu or settings
  • describes the data being accessed or collected, and how it is used or shared
  • is not bundled with disclosures unrelated to Google Health API data
Google’s suggested shape: {App name} collects health and fitness data to enable {feature}, {feature}, and {feature}.
1

Publish your branding

Fill in the Branding page in the Cloud Console (app name, logo, support email, home page, privacy policy, authorized domains), click Verify branding, then Publish branding.
A passing branding result is valid for 7 days. If you don’t publish inside that window you have to run the whole check again.
2

Declare and justify every scope

In the Verification Center, declare each scope and say specifically what breaks without it. Vague justifications are the main cause of delay.
Trim GOOGLE_DEFAULT_SCOPE to the data types you actually surface before you submit. Every extra restricted scope is another justification to defend, and unused ones are reviewed just as hard as the rest.
3

Record the demonstration video

An unlisted YouTube video, narrated or captioned in English, showing the full OAuth grant flow: your app name on the consent screen, your OAuth client ID visible in the browser address bar, and what each requested scope enables once consent is given.
Record the Open Wearables connect view end to end — Connect on Google Health → Google consent → the callback landing back on the user page → synced data appearing. That single take covers the grant flow and the per-scope functionality in one pass.
4

Submit and answer follow-ups

Trust and Safety reviews your app’s identity, scopes and justifications. Expect at least one round of questions; restricted-scope submissions are not finished until the security assessment below also lands.
Any app that can reach restricted data from or through a third-party server must be assessed — which is exactly what a self-hosted Open Wearables deployment is, since it holds the OAuth tokens and stores the synced data server-side. There is no configuration that avoids this.The assessment follows the App Defense Alliance’s CASA framework, built on the OWASP Application Security Verification Standard. Your assurance level is assigned from your scopes, user count and other risk signals, and it can rise year over year as you grow.
1

Pick an authorized lab

The free tier-2 self-scan no longer exists; assessments go through an authorized lab — Bishop Fox, DEKRA, Eydle, Leviathan Security, NCC Group, Netsentries, NowSecure, Prescient Security, TAC Security or ValueMentor. Prices vary; developers have reported the low end of the tier-2 range at a few hundred dollars a year.
2

Pass the assessment

Beyond the usual application-security checks, the assessor confirms that you handle user data securely and can delete a user’s data on request. Open Wearables covers that last point out of the box:Retention is configurable too — see Data lifecycle.
3

Submit the Letter of Validation

Passing earns a Letter of Validation (LOV) from the assessor. Send it to Google’s Trust and Safety team to close out the security assessment.
Google does not run the assessment and cannot speed it up. If it stalls, escalate to your assessor, not to Google.
An unverified app is capped at 100 users, which is enough for development and a closed pilot. The sharper limit is token lifetime: with the publishing status set to Testing and an external user type, Google issues refresh tokens that expire after 7 days.When that happens, Open Wearables’ next token refresh gets a 400/401 from Google, marks the connection revoked, and emits a connection.revoked webhook with reason refresh_failed. The user has to run the OAuth flow again. That is workable for a demo and unusable in production — which is the practical deadline for finishing verification.

Webhooks

Google Health webhooks are notify-only: each notification names the changed dataType, operation (UPSERT/DELETE), and the physical-time intervals that changed — but carries no data. Open Wearables verifies the request, then fetches the changed data over REST for those intervals. Two distinct secrets are involved:
  • GOOGLE_WEBHOOK_SECRET — a bearer token you register with Google that it echoes back in the Authorization header of every notification, so Open Wearables can verify the ping is genuine. Defaults to SECRET_KEY if unset.
  • Service-account credentials — used only to register the subscriber (a project-level admin call). Not involved in receiving notifications.
Receiving and processing webhooks needs only GOOGLE_WEBHOOK_SECRET. The service account is required only to register the subscriber programmatically — you can also register it manually via gcloud/the API console.

Webhook subscriber registration

Subscribers live at the project level (POST /v4/projects/{project}/subscribers), so registration authenticates as the project via a service account, not a user’s OAuth token.
gcloud commands need the project ID (e.g. open-wearables-prod). The GOOGLE_PROJECT_ID env var, however, needs the project number (e.g. 123456789012) — the subscriber API requires the number in its path.
1

Create a service account

2

Provide the credentials

Either mount a JSON key and point to it, or rely on Application Default Credentials (ADC) if running on GCP:
Add to .env:
Subscriber registration runs in the Celery worker, not the backend/API service. The key file (or credentials) and the GOOGLE_* env vars must be present on the worker service — a key mounted only into the backend container won’t be seen.
A JSON key never expires until deleted — prefer ADC / Workload Identity in production, and keep key files out of version control.
3

Register the subscriber

Switching the provider’s live sync mode to webhook registers the subscriber automatically. Google then performs an endpoint-verification handshake (POST {"type":"verification"}) against {API_BASE_URL}/api/v1/providers/google_health/webhooks, which must return 2xx when authenticated. Once verified, notifications begin flowing.

Managing subscribers without app credentials (gcloud / console)

If you’d rather not give the app a service account, create and manage the subscriber yourself and leave GOOGLE_SERVICE_ACCOUNT_FILE/GOOGLE_PROJECT_ID unset. The app then needs only GOOGLE_WEBHOOK_SECRET to verify inbound notifications — it never calls the project-level subscriber API.
In this mode the in-app .../webhooks/subscriptions management endpoints (list / register / update / delete) won’t work — they require project credentials. Use the commands below instead.
The endpointAuthorization.secret you set here must equal "Bearer " + the app’s GOOGLE_WEBHOOK_SECRET (which defaults to SECRET_KEY). If they don’t match, every notification fails signature verification and Open Wearables returns 401.
All calls authenticate with your own gcloud identity, which must hold the Health API subscriber role on the project. You can also run these from the Cloud Console API Explorer, but the CLI steps below are easier to copy end-to-end. Use the same subscriberId (open-wearables) the app uses, so it stays interchangeable.
dataTypes above is an abbreviated example — use the set the app supports for webhooks (steps, distance, hydration-log, heart-rate, run-vo2-max, daily-resting-heart-rate, heart-rate-variability, daily-heart-rate-variability, weight, body-fat, blood-glucose, daily-respiratory-rate, daily-oxygen-saturation, sleep, exercise, nutrition-log). Registering a data type the app doesn’t handle just means its notifications are ignored.

Next Steps

API Reference

Explore the Open Wearables API endpoints.

Supported Providers

See all supported providers.

Support

Need Help?