Skip to content
AF

Google Health API webhooks: subscribers, verification and notifications

Read from Google’s documentation on 2026-10-17 · discovery document revision 20261014 · Google’s webhooks guide last updated 2026-09-24

Google’s webhooks guide: “The Google Health API allows your application to receive real-time notifications when a user's health data changes.” It lists 28 data types that notify. A subscriber’s endpoint must pass a two-step verification handshake when it is created or its endpoint changes, answer every notification with 204 No Content, and can verify a signature on each; undelivered notifications are retried for up to 7 days.

The 28 data types that send notifications

The webhooks guide’s list, with the token an automatic subscription’s name carries for each. The same 28 types carry the Webhook support badge in Google's data-types table.

Data typeToken (automatic subscriptions)
active-minutesWEBHOOK_DATA_TYPE_ACTIVE_MINUTES
active-zone-minutesWEBHOOK_DATA_TYPE_ACTIVE_ZONE_MINUTES
activity-levelWEBHOOK_DATA_TYPE_ACTIVITY_LEVEL
altitudeWEBHOOK_DATA_TYPE_ALTITUDE
blood-glucoseWEBHOOK_DATA_TYPE_BLOOD_GLUCOSE
body-fatWEBHOOK_DATA_TYPE_BODY_FAT
calories-in-heart-rate-zoneWEBHOOK_DATA_TYPE_CALORIES_IN_HEART_RATE_ZONE
daily-heart-rate-variabilityWEBHOOK_DATA_TYPE_DAILY_HEART_RATE_VARIABILITY
daily-heart-rate-zonesWEBHOOK_DATA_TYPE_DAILY_HEART_RATE_ZONES
daily-oxygen-saturationWEBHOOK_DATA_TYPE_DAILY_OXYGEN_SATURATION
daily-respiratory-rateWEBHOOK_DATA_TYPE_DAILY_RESPIRATORY_RATE
daily-resting-heart-rateWEBHOOK_DATA_TYPE_DAILY_RESTING_HEART_RATE
daily-sleep-temperature-derivationsWEBHOOK_DATA_TYPE_DAILY_SLEEP_TEMPERATURE_DERIVATIONS
distanceWEBHOOK_DATA_TYPE_DISTANCE
exerciseWEBHOOK_DATA_TYPE_EXERCISE
floorsWEBHOOK_DATA_TYPE_FLOORS
heart-rateWEBHOOK_DATA_TYPE_HEART_RATE
heart-rate-variabilityWEBHOOK_DATA_TYPE_HEART_RATE_VARIABILITY
heightWEBHOOK_DATA_TYPE_HEIGHT
hydration-logWEBHOOK_DATA_TYPE_HYDRATION_LOG
nutrition-logWEBHOOK_DATA_TYPE_NUTRITION_LOG
respiratory-rate-sleep-summaryWEBHOOK_DATA_TYPE_RESPIRATORY_RATE_SLEEP_SUMMARY
run-vo2-maxWEBHOOK_DATA_TYPE_RUN_VO2_MAX
sedentary-periodWEBHOOK_DATA_TYPE_SEDENTARY_PERIOD
sleepWEBHOOK_DATA_TYPE_SLEEP
stepsWEBHOOK_DATA_TYPE_STEPS
time-in-heart-rate-zoneWEBHOOK_DATA_TYPE_TIME_IN_HEART_RATE_ZONE
weightWEBHOOK_DATA_TYPE_WEIGHT
user-deleted (system event)WEBHOOK_DATA_TYPE_USER_DELETED
user-revoked-access (system event)WEBHOOK_DATA_TYPE_USER_REVOKED_ACCESS

Which scopes a notification needs

Notifications are sent for these data types only when a user has granted consent for one of the corresponding scopes:

  • Activity, which covers active minutes, steps, altitude, distance, and floors data types:
    • https://www.googleapis.com/auth/googlehealth.activity_and_fitness.readonly
    • https://www.googleapis.com/auth/googlehealth.activity_and_fitness.writeonly
  • Health Metrics, which covers the weight data type:
    • https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.readonly
    • https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.writeonly
  • Sleep, which covers the sleep data type:
    • https://www.googleapis.com/auth/googlehealth.sleep.readonly
    • https://www.googleapis.com/auth/googlehealth.sleep.writeonly

Quoted from Google's webhooks guide, last updated 2026-09-24, read 2026-10-17.

  • Release note and webhook pages differ

    Google's release notes for April 14, 2026 list totalCalories under webhook notification support; the data-types table and the webhooks page do not list total-calories as webhook-supported.

    • Google's release notes: “Notification support for the data types activeZoneMinutes, altitude, bodyFat, caloriesInHeartRateZone, dailyHeartRateVariability, dailyHeartRateZones, dailyOxygenSaturation, dailyRestingHeartRate, dailySleepTemperatureDerivations, distance, exercise, floors, heartRate, sleep, steps, totalCalories, and weight.”

Subscribers

Managing subscribers

Before you can receive notifications, you must register a Subscriber, which represents your application's notification endpoint. You can manage subscribers using the REST API available at projects.subscribers.

Your subscriber endpoint must use HTTPS (TLSv1.2+) and be publicly accessible. During subscriber creation and updates, the Google Health API performs a verification challenge to ensure you own the endpoint URI. If verification fails, subscriber creation and update operations fail with a FailedPreconditionException.

Create a subscriber

To register a new subscriber for your project, use the create endpoint. You need to provide:

  • project-id: The project number where the webhook service account was created.
  • subscriberId: A unique identifier that you provide for the subscriber. This ID must be between 4 and 36 characters, and match the regular expression ([a-z]([a-z0-9-]{2,34}[a-z0-9])).
  • endpointUri: The destination URL for webhook notifications.
  • subscriberConfigs: The data types you want to receive notifications for, and the subscription policy for each.
  • endpointAuthorization: The authorization mechanism for your endpoint. This must contain a secret that you provide. The value of secret is sent in the Authorization header with each notification message. You can use this token to verify that incoming requests are from the Google Health API. For example, you can set secret to Bearer R4nd0m5tr1ng123 for Bearer authentication, or Basic dXNlcjpwYXNzd29yZA== for Basic authentication.

In subscriberConfigs you must set subscriptionCreatePolicy for each data type. Set it to AUTOMATIC to use automatic subscriptions, or MANUAL if you intend to manage user subscriptions yourself. See automatic subscriptions and manual subscriptions for more details on each option.

Request
POST https://health.googleapis.com/v4/projects/project-id/subscribers?subscriberId=subscriber-id
{
  "endpointUri": "https://myapp.com/webhooks/health",
  "subscriberConfigs": [
    {
      "dataTypes": ["steps", "altitude", "distance", "floors", "weight"],
      "subscriptionCreatePolicy": "AUTOMATIC"
    },
    {
      "dataTypes": ["sleep"],
      "subscriptionCreatePolicy": "MANUAL"
    }
  ],
  "endpointAuthorization": {
    "secret": "Bearer example-secret-token"
  }
}
Response
{
  "name": "projects/project-id/subscribers/subscriber-id",
  "endpointUri": "https://myapp.com/webhooks/health",
  "subscriberConfigs": [
    {
      "dataTypes": ["steps", "altitude", "distance", "floors", "weight"],
      "subscriptionCreatePolicy": "AUTOMATIC"
    },
    {
      "dataTypes": ["sleep"],
      "subscriptionCreatePolicy": "MANUAL"
    }
  ]
}

List subscribers

Use the list endpoint to retrieve all subscribers registered for your project.

Request
GET https://health.googleapis.com/v4/projects/project-id/subscribers
Response
{
  "subscribers": [
    {
      "name": "projects/project-id/subscribers/subscriber-id",
      "endpointUri": "https://myapp.com/webhooks/health",
      "subscriberConfigs": [
        {
          "dataTypes": ["steps", "altitude", "distance", "floors", "weight"],
          "subscriptionCreatePolicy": "AUTOMATIC"
        },
        {
          "dataTypes": ["sleep"],
          "subscriptionCreatePolicy": "MANUAL"
        }
      ],
      "endpointAuthorization": {
        "authorizationTokenSet": true
      }
    }
  ],
  "totalSize": 1
}

Update a subscriber

Use the patch endpoint to update a subscriber in your project. The fields that can be updated are endpointUri, subscriberConfigs, and endpointAuthorization.

You update fields by providing an updateMask query parameter and a request body. The updateMask must contain a comma-separated list of field names that you want to update, using camel case for field names (for example, endpointUri). The request body must contain a partial Subscriber object with the new values for fields you want to update. Only fields specified in updateMask are updated. If you provide fields in the request body that are not in updateMask, they are ignored.

If you update endpointUri or endpointAuthorization, endpoint verification is performed. See Endpoint verification for details.

When updating subscriberConfigs, note that it's a full replacement, not a merge. If subscriberConfigs is included in updateMask, all stored configurations for that subscriber are overwritten with list provided in request body. To add or remove a configuration, you must provide the complete set of configurations. If you are updating other fields and want to keep your current configurations, omit subscriberConfigs from updateMask.

Request
PATCH https://health.googleapis.com/v4/projects/project-id/subscribers/subscriber-id?updateMask=endpointUri
{
  "endpointUri": "https://myapp.com/new-webhooks/health"
}
Response
{
  "name": "projects/project-id/subscribers/subscriber-id",
  "endpointUri": "https://myapp.com/new-webhooks/health",
  "subscriberConfigs": [
    {
      "dataTypes": ["steps", "altitude", "distance", "floors", "weight"],
      "subscriptionCreatePolicy": "AUTOMATIC"
    },
    {
      "dataTypes": ["sleep"],
      "subscriptionCreatePolicy": "MANUAL"
    }
  ]
}

Delete a subscriber

Use the delete endpoint to remove a subscriber from your project. Once deleted, the subscriber will no longer receive notifications.

Request
DELETE https://health.googleapis.com/v4/projects/project-id/subscribers/subscriber-id

Response

An empty response body with HTTP status 200 OK is returned if deletion is successful.

{}

Quoted from Google's webhooks guide, last updated 2026-09-24, read 2026-10-17.

IAM service accounts

While not required, we recommend using an IAM Service Account when configuring subscribers for the Google Health API. Service accounts provide better security for application workloads compared to standard user accounts through the following features:

  • Automated short-lived credentials: When attached to a Google Cloud execution environment (such as Compute Engine, Cloud Run, or Google Kubernetes Engine), service accounts automatically obtain and rotate secure, short-lived credentials. This removes the risks of managing and storing persistent static keys.
  • Principle of least privilege: Service accounts provide dedicated identities for workloads. You can grant them only the specific permissions needed to manage subscriber endpoints, avoiding broader access to your Google Cloud resources.
  • Lifecycle independence: Service accounts operate independently of any individual user's account, ensuring that personnel changes don't impact long-term authentication stability.

Quoted from Google's webhooks guide, last updated 2026-09-24, read 2026-10-17.

Roles

To manage Google Health API Subscribers or Subscriptions, you must grant the appropriate role to the impersonated Service Account making the API calls. Depending on the level of access needed, assign one of the following roles:

  • Google Health API Read
  • Google Health API Editor
  • Google Health API Admin

Learn more about Google Health API IAM roles and permissions.

Quoted from Google's webhooks guide, last updated 2026-09-24, read 2026-10-17.

Endpoint verification

The handshake

To ensure the security and reliability of your notification delivery, the Google Health API performs a mandatory two-step verification handshake whenever you create a subscriber or update its endpoint configuration (endpointUri or endpointAuthorization). This process is performed synchronously during the API call. The service sends two automated POST requests to your endpoint URI, using the User-Agent Google-Health-API-Webhooks, with the JSON body {"type": "verification"}.

  • Authorized Handshake: The first request is sent with your configured Authorization header. Your server must respond with a 200 OK or 201 Created status.
  • Unauthorized Challenge: The second request is sent without credentials. Your server must respond with a 401 Unauthorized or 403 Forbidden status.

This handshake confirms that your endpoint is active and correctly enforcing security. If either step fails, the API request fails with a FAILED_PRECONDITION error. Only after this handshake succeeds is your subscriber saved and activated to receive health data notifications.

Key rotation

If you need to rotate keys for endpointAuthorization, follow these steps:

  1. Configure your endpoint to accept both old and new endpointAuthorization values.
  2. Update the subscriber configuration with new endpointAuthorization value using a patch request with ?updateMask=endpointAuthorization.
  3. Configure your endpoint to accept only new endpointAuthorization value after confirming step 2 was successful.

Quoted from Google's webhooks guide, last updated 2026-09-24, read 2026-10-17.

Automatic and manual subscriptions

User subscriptions

The Google Health API helps you manage user subscriptions efficiently, reducing the need for manual registration during user onboarding.

Automatic subscriptions

We recommend using automatic subscriptions. To enable this feature, set subscriptionCreatePolicy to AUTOMATIC in your subscriberConfigs for the specific data types. The dataTypes you specify with an AUTOMATIC policy are the same data types for which the Google Health API sends notifications, provided user consent is also granted for those data types.

When a user grants application consent for scopes that correspond to data types with an AUTOMATIC policy, the Google Health API automatically tracks and sends out notifications for the data types resulting from the intersection between user consented data types and the automatic subscriber config data types for that user. Notifications are then sent to your endpoint whenever that user generates new data for those types. This works for users who grant consent either before or after you create the subscriber. Notifications are not backfilled for data generated before the subscriber was created.

If a user revokes consent, notifications for the corresponding data types will stop. Automatic subscriptions are managed by Google and cannot be listed or deleted individually; they are removed only when the parent subscriber is deleted.

Manual subscriptions

If your subscriber is configured with a MANUAL subscription_create_policy for specific data types, you must explicitly create and manage subscriptions for each user. A Subscription links a specific user to your subscriber endpoint for a defined set of data types. Developers can use specific APIs to:

  • Create (manual) subscriptions per healthUserId - Creates a new subscription for a specific user. This method requires the subscriber to have a SubscriptionCreatePolicy set to MANUAL for the requested data types.
  • Update (manual) subscription - Updates the data types for an existing user subscription.
  • Delete (manual) subscription - Deletes a specific user subscription. Once deleted, your subscriber endpoint will no longer receive notifications for this user for the associated data types.
  • List (manual) subscriptions - Lists all active subscriptions for a given subscriber. You can filter the results by user or data type.

Quoted from Google's webhooks guide, last updated 2026-09-24, read 2026-10-17.

Notifications

When a user's data changes for a subscribed data type, the Google Health API sends an HTTPS POST request to the subscriber endpoint URL.

The payload

The notification payload contains details about the data change, including the user ID, data type, and time intervals, which you can use to query the updated data. When multiple updates occur or updates are aggregated, notifications are batched together and delivered as a JSON array of notification objects.

When a webhook notification is triggered, the Google Health API sends an HTTPS POST request to your subscriber endpoint with the following format:

Upsert (time-series data)
POST https://your-endpoint-uri
Authorization: Bearer example-secret-token
GOOGLE-HEALTH-API-SIGNATURE: signature-value
Content-Type: application/json

{
  "data": {
    "version": "1",
    "clientProvidedSubscriptionName": "subscription-name",
    "healthUserId": "health-user-id",
    "operation": "UPSERT",
    "dataType": "steps",
    "intervals": [
      {
        "physicalTimeInterval": {
          "startTime": "2026-03-08T01:29:00Z",
          "endTime": "2026-03-08T01:34:00Z"
        },
        "civilDateTimeInterval": {
          "startDateTime": {
            "date": {
              "year": 2026,
              "month": 3,
              "day": 7
            },
            "time": {
              "hours": 17,
              "minutes": 29
            }
          },
          "endDateTime": {
            "date": {
              "year": 2026,
              "month": 3,
              "day": 7
            },
            "time": {
              "hours": 17,
              "minutes": 34
            }
          }
        },
        "civilIso8601TimeInterval": {
          "startTime": "2026-03-07T17:29:00",
          "endTime": "2026-03-07T17:34:00"
        }
      }
    ]
  }
}
Delete (time-series data)
POST https://your-endpoint-uri
Authorization: Bearer example-secret-token
GOOGLE-HEALTH-API-SIGNATURE: signature-value
Content-Type: application/json

[
  {
    "data": {
      "version": "1",
      "clientProvidedSubscriptionName": "subscription-name",
      "healthUserId": "health-user-id",
      "operation": "DELETE",
      "dataType": "steps",
      "intervals": [
        {
          "physicalTimeInterval": {
            "startTime": "2026-08-12T12:00:00Z",
            "endTime": "2026-08-12T12:30:00Z"
          },
          "civilIso8601TimeInterval": {
            "startTime": "2026-08-12T12:00:00",
            "endTime": "2026-08-12T12:30:00"
          }
        }
      ]
    }
  }
]
Delete (identifiable data)
POST https://your-endpoint-uri
Authorization: Bearer example-secret-token
GOOGLE-HEALTH-API-SIGNATURE: signature-value
Content-Type: application/json

{
  "data": {
    "version": "1",
    "clientProvidedSubscriptionName": "subscription-name",
    "healthUserId": "health-user-id",
    "operation": "DELETE",
    "dataType": "sleep",
    "intervals": [
      {
        "physicalTimeInterval": {
          "startTime": "2026-08-12T22:00:00Z",
          "endTime": "2026-08-13T06:00:00Z"
        },
        "civilIso8601TimeInterval": {
          "startTime": "2026-08-12T22:00:00",
          "endTime": "2026-08-13T06:00:00"
        }
      }
    ],
    "recordId": "9875412"
  }
}

The operation field indicates the type of change that triggered the notification:

  • UPSERT: Sent for any data addition or modification.
  • DELETE: Sent when a user explicitly deletes data (such as deleting dataPoints using the Fitbit app or performing a batch delete using the API). Webhook notifications are not sent for system-level deletion events, such as a user revoking application permissions or deleting their Fitbit account.

We recommend making your notification handling logic idempotent, especially for UPSERT operations, as retries can cause duplicate notifications to be sent.

The clientProvidedSubscriptionName field is a unique identifier:

  • For subscriptions with a MANUAL policy, this field contains the persistent, developer-provided subscription name specified when the subscription is created. This provides a stable ID for managing manual subscriptions.
  • For subscriptions created with an AUTOMATIC policy, the Google Health API automatically generates and assigns an identifier in the format: auto-<project-number>-<subscriber-id>-<WEBHOOK_DATA_TYPE_TOKEN>, where:
    • <project-number> is the developer's Google Cloud project number (for example, 362978535920).
    • <subscriber-id> is the subscriber's registration ID / subscriber name (for example, google-health-sync).
    • <WEBHOOK_DATA_TYPE_TOKEN> is the token representing the specific data type. See Vocabulary of webhook data type tokens for all possible tokens.

Including clientProvidedSubscriptionName for both manual and automatic policies ensures a consistent notification payload format across all subscription types.

Quoted from Google's webhooks guide, last updated 2026-09-24, read 2026-10-17.

Mapping healthUserId to a user

The healthUserId is a Google Health API identifier for the user whose data has changed. If your application supports multiple users, you can receive notifications for any user who has granted your application consent. When you receive a notification, use healthUserId to identify which user's data has changed, so you can use their OAuth credentials to query their data.

To map a user's OAuth credentials to their healthUserId, use the getIdentity endpoint. Call this endpoint with a user's credentials during user onboarding to retrieve their healthUserId, and store this mapping. This mapping doesn't change over time, so it can be cached indefinitely. For an example, see Get user ID. This lets you select the correct user credentials when querying data based on the healthUserId in a notification.

Quoted from Google's webhooks guide, last updated 2026-09-24, read 2026-10-17.

Time intervals

The intervals array contains one or more objects representing the time intervals for the modified data:

FieldTypeRequiredDescription
physicalTimeIntervalobjectNoThe UTC physical time interval of the modified data (YYYY-MM-DDTHH:MM:SSZ format). Populated for intraday metrics; omitted for daily rollups and full-day activities.
civilDateTimeIntervalobjectYesThe civil (local) calendar date and time interval of the modified data, without UTC or timezone offsets.
civilIso8601TimeIntervalobjectYesThe civil (local) time interval represented as an ISO 8601 string (YYYY-MM-DDTHH:MM:SS format) without UTC or timezone offsets.

For full-day activities and daily aggregates, the physicalTimeInterval field is intentionally omitted from the notification payload. This is because full-day events are bucketed by their nominal civil date, and physical time durations can be inaccurate when crossing timezones. When processing full-day events, you should rely on the civilDateTimeInterval or civilIso8601TimeInterval fields as a best practice. For details on how to query these data points using civil time filters, see Filter data by an interval civil start time.

The following is an example notification payload for a full-day activity where the physicalTimeInterval field is omitted:

{
  "data": {
    "version": "1",
    "clientProvidedSubscriptionName": "auto-503461148219-lindasubscriber-WEBHOOK_DATA_TYPE_STEPS",
    "healthUserId": "343864966078399341",
    "operation": "UPSERT",
    "dataType": "steps",
    "intervals": [
      {
        "civilDateTimeInterval": {
          "startDateTime": {
            "date": {
              "year": 2026,
              "month": 7,
              "day": 14
            },
            "time": {}
          },
          "endDateTime": {
            "date": {
              "year": 2026,
              "month": 7,
              "day": 15
            },
            "time": {}
          }
        },
        "civilIso8601TimeInterval": {
          "startTime": "2026-07-14T00:00:00",
          "endTime": "2026-07-15T00:00:00"
        }
      }
    ]
  }
}

Quoted from Google's webhooks guide, last updated 2026-09-24, read 2026-10-17.

Batching

To minimize network traffic, the Google Health API batches messages together. The system is configured to send out a maximum of 99 messages per batch, which are automatically pushed as they become available.

When messages are batched, the payload is delivered as a JSON array containing multiple notification objects. A single batch can include updates for multiple users across multiple subscribed data types. For an example payload, see the Batch (multiple users and data types) example in the Notification format section.

Quoted from Google's webhooks guide, last updated 2026-09-24, read 2026-10-17.

Responding

Your server must respond to notifications with an HTTP 204 No Content status code immediately. To avoid timeouts, process the notification payload asynchronously after sending the response. If the Google Health API receives any other status code or the request times out, it retries sending the notification later.

Node.js (Express) Example:

app.post('/webhook-receiver', (req, res) => {
    // 1. Immediately acknowledge the notification batch
    res.status(204).send();

    // 2. Process the data asynchronously in the background
    const notification = req.body;
    setImmediate(() => {
        for (const notification of notifications) {
            console.log(`Update for user ${notification.data.healthUserId} of type ${notification.data.dataType}`);
            // Trigger your data retrieval logic here
        }
    });
});

There are three common strategies for updating your database after receiving a data change notification:

  • Queuing (recommended): Place the incoming notifications into a message queue and process them off the queue based on system resources.
  • Immediate retrieval: Direct your backend to immediately call the Google Health API to fetch the updated data and commit it to your database.
  • Lazy updates: Mark the user's local database records as dirty and only fetch the updated data from the Google Health API when the user next visits your application.

The queueing strategy lets you process data in near real-time while remaining resilient to a flood of notifications (for example, if a service outage causes a backlog of notifications to be delivered all at once). While you can choose to process these queues on a long-interval batch schedule (for example, every 3 hours) to save resources, the drawback is that users typically expect to see their data updated in near real-time.

Quoted from Google's webhooks guide, last updated 2026-09-24, read 2026-10-17.

Google’s best practices

Follow these best practices to ensure reliable and efficient handling of data updates:

  • Use the Subscription API: Use the Subscription API to be notified when a user has new data to download rather than polling endpoints.
  • Queue and process notifications separately: When receiving subscription notifications, place them into a queue and process them separately based on your system's resources. Keep the webhook endpoint lightweight by acknowledging the request immediately.

Quoted from Google's webhooks guide, last updated 2026-09-24, read 2026-10-17.

Signature verification

To ensure the authenticity of Webhooks notifications, the raw JSON payload of every outgoing webhook notification is signed with a private key using Tink's PublicKeySign, providing the Base64-encoded signature in the GOOGLE-HEALTH-API-SIGNATURE HTTP header in the request. These signing keys are automatically rotated every 30 days, and the corresponding official public keyset is distributed as a JSON file at the permanent URL https://www.gstatic.com/googlehealthapi/webhooks/webhooks_public_keyset.json.

How to verify the signature

Using Tink (Recommended): Developers can verify the signature using Tink's PublicKeyVerify primitive. Fetch the public keyset from the permanent URL, instantiate the PublicKeyVerify primitive with the keyset, and verify the decoded GOOGLE-HEALTH-API-SIGNATURE header against the raw webhook JSON payload.

Manual Verification (Without Tink): If developers choose not to use Tink, they can manually verify the signature by following these steps:

  1. Base64-decodes the GOOGLE-HEALTH-API-SIGNATURE header to separate the 5-byte Tink prefix (which contains a 1-byte version prefix and a 4-byte keyId) from the actual DER-encoded signature.
  2. Fetch the JSON keyset from https://www.gstatic.com/googlehealthapi/webhooks/webhooks_public_keyset.json.
  3. Locate the key matching the parsed keyId and Base64-decode its value field, which contains a serialized EcdsaPublicKey Protocol Buffer.
  4. Extract the big-endian x and y coordinates (Protobuf tags 3 and 4) from this binary payload.
  5. Instantiate a standard ECDSA P-256 public key in a built-in cryptography library using the extracted x and y coordinates.
  6. Verify the raw webhook JSON payload against the extracted DER signature using the SHA-256 algorithm.

Quoted from Google's webhooks guide, last updated 2026-09-24, read 2026-10-17.

When the endpoint is down

Subscriber status and recovery

If your subscriber endpoint becomes unavailable or returns an error status code (anything other than 204), the Google Health API stores pending notifications for up to 7 days and retries delivery with exponential backoff.

Once your endpoint is back online and responds with 204, the API automatically delivers the backlog of stored messages. Notifications older than 7 days are discarded and cannot be recovered.

Tip: Have questions about the content on this page? Use the AI assistance and developer tools to get explanations and clarify your understanding. See AI assistance and developer tools to learn more.

Quoted from Google's webhooks guide, last updated 2026-09-24, read 2026-10-17.

Common errors

Error codeMessageDescriptionRecommendation
400 Bad RequestInvalid project number in resource nameWhen deleting or updating a subscriber using your Google Cloud project ID in the request URL instead of project number. This applies to webhook subscriptions using the projects.subscribers endpoint.Use your Google Cloud project number in the request URL, not the project ID.
403 ForbiddenThe caller does not have permissionWhen creating or listing subscribers using your Google Cloud project ID in the request URL instead of project number. This applies to webhook subscriptions using the projects.subscribers endpoint.Use your Google Cloud project number in the request URL, not the project ID.

Quoted from Google's webhooks guide, last updated 2026-09-24, read 2026-10-17.

Quoted from Google's webhooks guide, last updated 2026-09-24, read 2026-10-17.

The subscriber methods in the discovery document, with the rest of the API’s methods: the methods overview. Every error reason Google catalogues: Google Health API errors.

Questions

Which data types does the Google Health API send webhooks for?

28, per Google's webhooks guide (last updated 2026-09-24): active-minutes, active-zone-minutes, activity-level, altitude, blood-glucose, body-fat, calories-in-heart-rate-zone, daily-heart-rate-variability, daily-heart-rate-zones, daily-oxygen-saturation, daily-respiratory-rate, daily-resting-heart-rate, daily-sleep-temperature-derivations, distance, exercise, floors, heart-rate, heart-rate-variability, height, hydration-log, nutrition-log, respiratory-rate-sleep-summary, run-vo2-max, sedentary-period, sleep, steps, time-in-heart-rate-zone and weight. The same 28 carry the Webhook support badge in Google's data-types table. The guide's token vocabulary adds 2 system events: user-deleted and user-revoked-access.

How does Google Health API webhook endpoint verification work?

To ensure the security and reliability of your notification delivery, the Google Health API performs a mandatory two-step verification handshake whenever you create a subscriber or update its endpoint configuration (endpointUri or endpointAuthorization). Authorized Handshake: The first request is sent with your configured Authorization header. Your server must respond with a 200 OK or 201 Created status. Unauthorized Challenge: The second request is sent without credentials. Your server must respond with a 401 Unauthorized or 403 Forbidden status.

What status code should a Google Health API webhook endpoint return?

204. Google's webhooks guide: "Your server must respond to notifications with an HTTP 204 No Content status code immediately."

How long does the Google Health API retry an undelivered webhook?

Up to 7 days. Google's webhooks guide: "If your subscriber endpoint becomes unavailable or returns an error status code (anything other than 204), the Google Health API stores pending notifications for up to 7 days and retries delivery with exponential backoff." Notifications older than 7 days are discarded and cannot be recovered.

How do you verify a Google Health API webhook signature?

To ensure the authenticity of Webhooks notifications, the raw JSON payload of every outgoing webhook notification is signed with a private key using Tink's PublicKeySign, providing the Base64-encoded signature in the GOOGLE-HEALTH-API-SIGNATURE HTTP header in the request. These signing keys are automatically rotated every 30 days, and the corresponding official public keyset is distributed as a JSON file at the permanent URL https://www.gstatic.com/googlehealthapi/webhooks/webhooks_public_keyset.json. Google recommends verifying it with Tink's PublicKeyVerify primitive and documents a manual path on the same page.

Sources checked

Google’s text is quoted for identification; each guide page’s footer reads: “Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License.”. Generated from Google’s pages by scripts/fetch-google-health-api.mjs, not typed by hand; Google’s documentation remains the authority.