Подписки на вебхуки

Google Health API позволяет вашему приложению получать уведомления, когда данные о здоровье пользователя меняются. Вместо того чтобы опрашивать сервер на предмет изменений, ваш сервер получает запрос HTTPS POST вебхука, как только данные становятся доступны в Google Health API.

Поддерживаемые типы данных

Уведомления вебхуков поддерживаются для следующих типов данных:

Таблица. Типы данных Google Health API с поддержкой вебхуков
Тип данных Доступные
операции
Область действия
Количество минут активности
dataType: active-minutes
filter parameter: active_minutes
Тип записи: интервал.
Разрешение хранилища: 1 минута.

Совместимые устройства

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Минуты в зоне активности
dataType: active-zone-minutes
filter parameter: active_zone_minutes
Тип записи: интервал.
Разрешение хранилища: 1 минута.

Совместимые устройства

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Уровень активности
dataType: activity-level
filter parameter: activity_level
Тип записи: интервал.
список, сверка .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Высота
dataType: altitude
filter parameter: altitude
Тип записи: интервал.
Разрешение хранилища: 1 минута.
list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Уровень глюкозы в крови
list, get, reconcile, rollup, dailyRollup .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Жировая ткань
dataType: body-fat
filter parameter: body_fat
Тип записи: Sample.

Совместимые устройства

list, get, reconcile, rollup, dailyRollup, create, update, batchDelete .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Калории в зоне ЧСС
dataType: calories-in-heart-rate-zone
filter parameter: calories_in_heart_rate_zone
Тип записи: интервал.
Разрешение хранилища: 1 минута.
rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Ежедневная вариабельность сердечного ритма
dataType: daily-heart-rate-variability
filter parameter: daily_heart_rate_variability
Тип записи: ежедневно.

Совместимые устройства

список, сверка .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Пульсовые зоны за день
dataType: daily-heart-rate-zones
filter parameter: daily_heart_rate_zones
Тип записи: ежедневно.
список, сверка .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Ежедневный уровень кислорода в крови
dataType: daily-oxygen-saturation
filter parameter: daily_oxygen_saturation
Тип записи: ежедневно.

Совместимые устройства

список, сверка .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Ежедневная частота дыхания
dataType: daily-respiratory-rate
filter parameter: daily_respiratory_rate
Тип записи: ежедневно.

Совместимые устройства

список, сверка .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Ежедневная частота сердечных сокращений в покое
dataType: daily-resting-heart-rate
filter parameter: daily_resting_heart_rate
Тип записи: ежедневно.

Совместимые устройства

список, сверка .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Ежедневные колебания температуры во время сна
dataType: daily-sleep-temperature-derivations
filter parameter: daily_sleep_temperature_derivations
Тип записи: ежедневно.

Совместимые устройства

список, сверка .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Расстояние
dataType: distance
filter parameter: distance
Тип записи: интервал.
Разрешение хранилища: 1 минута.

Совместимые устройства

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Тренировка
dataType: exercise
filter parameter: exercise
Тип записи: сеанс.

Совместимые устройства

list, get, reconcile, create, update, batchDelete .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Этажи
dataType: floors
filter parameter: floors
Тип записи: интервал.
Разрешение хранилища: 1 минута.

Совместимые устройства

reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Пульс
dataType: heart-rate
filter parameter: heart_rate
Тип записи: Sample.
Разрешение хранилища: 1 секунда.

Совместимые устройства

list, reconcile, rollup, dailyRollup .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Вариабельность сердечного ритма
dataType: heart-rate-variability
filter parameter: heart_rate_variability
Тип записи: Sample.

Совместимые устройства

список, сверка .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Высота
dataType: height
filter parameter: height
Тип записи: Sample.
list, get, reconcile, create, update, batchDelete .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Журнал потребления жидкости
dataType: hydration-log
filter parameter: hydration_log
Тип записи: сеанс.
list, get, reconcile, rollup, dailyRollup, create, update, batchDelete .nutrition.readonly
.nutrition.writeonly
Журнал питания
dataType: nutrition-log
filter parameter: nutrition_log
Тип записи: сеанс.
list, get, reconcile, rollup, dailyRollup, create, update, batchDelete .nutrition.readonly
.nutrition.writeonly
Отчет о частоте дыхания во время сна
dataType: respiratory-rate-sleep-summary
filter parameter: respiratory_rate_sleep_summary
Тип записи: Sample.

Совместимые устройства

список, сверка .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
МПК при беге
dataType: run-vo2-max
filter parameter: run_vo2_max
Тип записи: Sample.

Совместимые устройства

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Период без активности
dataType: sedentary-period
filter parameter: sedentary_period
Тип записи: интервал.

Совместимые устройства

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Сон
dataType: sleep
filter parameter: sleep
Тип записи: сеанс.

Совместимые устройства

list, get, reconcile, create, update, batchDelete .sleep.readonly
.sleep.writeonly
Инструкции
dataType: steps
filter parameter: steps
Тип записи: интервал.
Разрешение хранилища: 1 минута.

Совместимые устройства

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Время в пульсовой зоне
dataType: time-in-heart-rate-zone
filter parameter: time_in_heart_rate_zone
Тип записи: интервал.
Разрешение хранилища: 1 минута.
list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Вес
dataType: weight
filter parameter: weight
Тип записи: Sample.

Совместимые устройства

list, get, reconcile, rollup, dailyRollup, create, update, batchDelete .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly

Области действия уведомлений

Уведомления отправляются для этих типов данных, только если пользователь дал согласие для одной из следующих областей действия:

  • Активность. Сюда входят минуты активности, шаги, высота, расстояние и этажи.
    • https://www.googleapis.com/auth/googlehealth.activity_and_fitness.readonly
    • https://www.googleapis.com/auth/googlehealth.activity_and_fitness.writeonly
  • Показатели здоровья, которые включают тип данных о весе:
    • https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.readonly
    • https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.writeonly
  • Сон – данные о сне:
    • https://www.googleapis.com/auth/googlehealth.sleep.readonly
    • https://www.googleapis.com/auth/googlehealth.sleep.writeonly

Сервисные аккаунты IAM

При настройке подписчиков для Google Health API мы рекомендуем использовать сервисный аккаунт IAM, хотя это и не обязательно. Сервисные аккаунты обеспечивают более высокий уровень безопасности для рабочих нагрузок приложений по сравнению со стандартными аккаунтами пользователей благодаря следующим функциям:

  • Автоматизированные краткосрочные учетные данные. При подключении к среде выполнения Google Cloud (например, Compute Engine, Cloud Run или Google Kubernetes Engine) сервисные аккаунты автоматически получают и меняют безопасные краткосрочные учетные данные. Это позволяет избежать рисков, связанных с управлением и хранением постоянных статических ключей.
  • Принцип минимальных привилегий. Сервисные аккаунты предоставляют выделенные идентификационные данные для рабочих нагрузок. Вы можете предоставить им только определенные разрешения, необходимые для управления конечными точками подписчиков, избегая более широкого доступа к вашим ресурсам Google Cloud.
  • Независимость жизненного цикла. Сервисные аккаунты работают независимо от аккаунтов отдельных пользователей, поэтому кадровые изменения не влияют на стабильность аутентификации.

Как настроить сервисный аккаунт

Чтобы настроить аутентификацию приложения-подписчика с помощью сервисного аккаунта, выполните следующие действия:

  1. Создайте сервисный аккаунт. В консоли Google Cloud перейдите на страницу IAM и администрирование и создайте новый управляемый пользователем сервисный аккаунт.
  2. Предоставьте необходимые роли IAM. Назначьте сервисному аккаунту роли, необходимые для управления подписчиками в облачном проекте Google Cloud.
  3. Привяжите сервисный аккаунт к рабочей нагрузке. Настройте среду, в которой размещена логика подписчика, для запуска в качестве нового сервисного аккаунта. Это позволяет коду приложения (например, клиентским библиотекам Google API) автоматически обнаруживать и использовать краткосрочные учетные данные сервисного аккаунта при вызове REST API projects.subscribers.

Роли CPE

Чтобы управлять подписчиками или подписками Google Health API, необходимо предоставить соответствующую роль сервисному аккаунту, от имени которого выполняются вызовы API. В зависимости от необходимого уровня доступа назначьте одну из следующих ролей:

  • Google Health API Read
  • Редактор Google Health API
  • Администратор Google Health API

Подробнее о ролях и разрешениях IAM в Google Health API…

Как управлять списком подписчиков

Чтобы получать уведомления, необходимо зарегистрировать подписчика, который представляет конечную точку уведомлений вашего приложения. Управлять подписчиками можно с помощью REST API, доступного по адресу projects.subscribers.

Конечная точка подписчика должна использовать HTTPS (TLSv1.2+) и быть общедоступной. При создании и обновлении подписчика Google Health API выполняет проверку, чтобы убедиться, что вы являетесь владельцем URI конечной точки. Если проверка не пройдена, то операции по созданию и обновлению подписчиков завершаются ошибкой FailedPreconditionException.

Создать подписчика

Чтобы зарегистрировать нового подписчика для проекта, используйте конечную точку create. Вам потребуется указать:

  • project-id: номер проекта, в котором был создан сервисный аккаунт вебхука.
  • subscriberId – уникальный идентификатор, который вы предоставляете подписчику. Идентификатор должен содержать от 4 до 36 символов и соответствовать регулярному выражению ([a-z]([a-z0-9-]{2,34}[a-z0-9])).
  • endpointUri – целевой URL для уведомлений вебхука.
  • subscriberConfigs: типы данных, для которых вы хотите получать уведомления, и правила подписки для каждого из них.
  • endpointAuthorization: механизм авторизации для конечной точки. Он должен содержать предоставленный вами объект secret. Значение secret отправляется в заголовке Authorization с каждым уведомлением. С помощью этого токена можно проверять, что входящие запросы поступают от Google Health API. Например, для аутентификации Bearer можно задать для secret значение Bearer R4nd0m5tr1ng123, а для базовой аутентификации – Basic dXNlcjpwYXNzd29yZA==.

В subscriberConfigs необходимо задать subscriptionCreatePolicy для каждого типа данных. Установите значение AUTOMATIC, чтобы использовать автоматические подписки, или MANUAL, если вы хотите управлять подписками пользователей самостоятельно. Подробнее об автоматических и ручных подписках…

Запрос

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"
  }
}

Ответ

{
  "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, чтобы получить список всех подписчиков, зарегистрированных в вашем проекте.

Запрос

GET https://health.googleapis.com/v4/projects/project-id/subscribers

Ответ

{
  "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
}

Обновить подписчика

Чтобы обновить подписчика в проекте, используйте конечную точку patch. Обновить можно поля endpointUri, subscriberConfigs и endpointAuthorization.

Чтобы обновить поля, укажите параметр запроса updateMask и тело запроса. В параметре updateMask необходимо перечислить через запятую названия полей, которые нужно обновить. Названия должны быть написаны в верблюжьем регистре (например, endpointUri). В теле запроса должен быть указан частичный объект Subscriber с новыми значениями для полей, которые нужно обновить. Обновляются только поля, указанные в updateMask. Если в теле запроса указаны поля, которых нет в updateMask, они игнорируются.

Если вы обновите endpointUri или endpointAuthorization, будет выполнена проверка конечных точек. Подробнее о проверке конечных точек…

При обновлении subscriberConfigs обратите внимание, что это полная замена, а не объединение. Если subscriberConfigs включен в updateMask, все сохраненные конфигурации для этого подписчика перезаписываются списком, указанным в теле запроса. Чтобы добавить или удалить конфигурацию, необходимо предоставить полный набор конфигураций. Если вы обновляете другие поля и хотите сохранить текущие настройки, не указывайте subscriberConfigs в updateMask.

Запрос

PATCH https://health.googleapis.com/v4/projects/project-id/subscribers/subscriber-id?updateMask=endpointUri
{
  "endpointUri": "https://myapp.com/new-webhooks/health"
}

Ответ

{
  "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. После удаления подписчик больше не будет получать уведомления.

Запрос

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

Ответ

Если удаление выполнено успешно, возвращается пустое тело ответа со статусом HTTP `200 OK`.
{}

Проверка конечных точек

Чтобы обеспечить безопасность и надежность доставки уведомлений, Google Health API выполняет обязательную двухэтапную аутентификацию при создании подписчика или обновлении конфигурации его конечной точки (endpointUri или endpointAuthorization). Этот процесс выполняется синхронно во время вызова API. Сервис отправляет два автоматических запроса POST на URI конечной точки, используя агент пользователя Google-Health-API-Webhooks с телом JSON {"type": "verification"}.

  • Авторизованное рукопожатие. Первый запрос отправляется с настроенным заголовком Authorization. Сервер должен возвращать статус 200 OK или 201 Created.
  • Неавторизованный запрос. Второй запрос отправляется без учетных данных. Сервер должен возвращать статус 401 Unauthorized или 403 Forbidden.

Это подтверждает, что конечная точка активна и правильно обеспечивает безопасность. Если на любом из этих этапов произойдет сбой, запрос к API не будет выполнен и появится сообщение об ошибке FAILED_PRECONDITION. Только после успешного рукопожатия подписчик сохраняется и активируется для получения уведомлений о данных о здоровье.

Ротация ключей

Чтобы выполнить ротацию ключей для endpointAuthorization, выполните следующие действия:

  1. Настройте конечную точку так, чтобы она принимала и старые, и новые значения endpointAuthorization.
  2. Обновите конфигурацию подписчика, указав новое значение endpointAuthorization с помощью запроса patch с ?updateMask=endpointAuthorization.
  3. Настройте конечную точку так, чтобы она принимала только новое значение endpointAuthorization, после того как убедитесь, что шаг 2 выполнен успешно.

Подписки пользователей

Google Health API помогает эффективно управлять подписками пользователей, снижая необходимость в ручной регистрации во время адаптации пользователей.

Автоматические подписки

Рекомендуем использовать автоматические подписки. Чтобы включить эту функцию, задайте для параметра subscriptionCreatePolicy значение AUTOMATIC в subscriberConfigs для определенных типов данных. Типы данных (dataTypes), которые вы указываете в правилах AUTOMATIC, совпадают с типами данных, для которых Google Health API отправляет уведомления, если согласие пользователя также предоставлено для этих типов данных.

Когда пользователь предоставляет приложению согласие на доступ к областям, соответствующим типам данных с правилом AUTOMATIC, Google Health API автоматически отслеживает и отправляет уведомления для типов данных, которые соответствуют как типам данных, на доступ к которым пользователь дал согласие, так и типам данных, указанным в конфигурации автоматического подписчика для этого пользователя. После этого на вашу конечную точку будут отправляться уведомления, когда пользователь генерирует новые данные этих типов. Это работает для пользователей, которые дают согласие до или после того, как вы создаете подписчика. Уведомления не заполняются данными, созданными до создания подписчика.

Если пользователь отзовет согласие, уведомления для соответствующих типов данных перестанут поступать. Автоматическими подписками управляет Google. Их нельзя просматривать или удалять по отдельности. Они удаляются только вместе с родительским подписчиком.

Подписки, оформленные вручную

Если для подписчика настроен режим MANUAL subscription_create_policy для определенных типов данных, вы должны явным образом создавать подписки для каждого пользователя и управлять ими. Подписка связывает определенного пользователя с вашей конечной точкой подписчика для определенного набора типов данных. Разработчики могут использовать специальные API, чтобы:

  • Create (manual) subscriptions per healthUserId – создает подписку для определенного пользователя. Для этого метода требуется, чтобы у подписчика было установлено значение SubscriptionCreatePolicy, равное MANUAL, для запрашиваемых типов данных.
  • Обновление подписки (вручную) – обновление типов данных для существующей подписки пользователя.
  • Delete (manual) subscription – удаляет подписку определенного пользователя. После удаления конечная точка подписчика больше не будет получать уведомления для этого пользователя по связанным типам данных.
  • List (manual) subscriptions – список всех активных подписок для определенного подписчика. Результаты можно отфильтровать по пользователю или типу данных.

Уведомления

Когда данные пользователя меняются для подписанного типа данных, Google Health API отправляет запрос HTTPS POST на URL конечной точки подписчика.

Формат уведомления

Полезная нагрузка уведомления содержит сведения об изменении данных, включая идентификатор пользователя, тип данных и временные интервалы, которые можно использовать для запроса обновленных данных. Если происходит несколько обновлений или они агрегируются, уведомления объединяются в пакет и доставляются в виде массива JSON объектов уведомлений.

Когда срабатывает уведомление вебхука, Google Health API отправляет запрос HTTPS POST конечной точке подписчика в следующем формате:

Upsert (данные временного ряда)

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"
        }
      }
    ]
  }
}

Удалить (данные временного ряда)

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"
          }
        }
      ]
    }
  }
]

Удалить (идентифицируемые данные)

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"
  }
}

Пакетный перенос (несколько пользователей и типов данных)

POST https://your-endpoint-uri
Authorization: Bearer example-secret-token
GOOGLE-HEALTH-API-SIGNATURE: signature-value
Content-Type: application/json

[
  {
    "data": {
      "version": "1",
      "clientProvidedSubscriptionName": "auto-project-number-subscriber-id-WEBHOOK_DATA_TYPE_STEPS",
      "healthUserId": "health-user-id-1",
      "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"
          }
        }
      ]
    }
  },
  {
    "data": {
      "version": "1",
      "clientProvidedSubscriptionName": "auto-project-number-subscriber-id-WEBHOOK_DATA_TYPE_WEIGHT",
      "healthUserId": "health-user-id-1",
      "operation": "UPSERT",
      "dataType": "weight",
      "intervals": [
        {
          "physicalTimeInterval": {
            "startTime": "2026-03-07T15:14:00Z",
            "endTime": "2026-03-07T15:16:00Z"
          },
          "civilDateTimeInterval": {
            "startDateTime": {
              "date": {
                "year": 2026,
                "month": 3,
                "day": 7
              },
              "time": {
                "hours": 7,
                "minutes": 14
              }
            },
            "endDateTime": {
              "date": {
                "year": 2026,
                "month": 3,
                "day": 7
              },
              "time": {
                "hours": 7,
                "minutes": 16
              }
            }
          },
          "civilIso8601TimeInterval": {
            "startTime": "2026-03-07T07:14:00",
            "endTime": "2026-03-07T07:16:00"
          }
        }
      ]
    }
  },
  {
    "data": {
      "version": "1",
      "clientProvidedSubscriptionName": "auto-project-number-subscriber-id-WEBHOOK_DATA_TYPE_STEPS",
      "healthUserId": "health-user-id-2",
      "operation": "UPSERT",
      "dataType": "steps",
      "intervals": [
        {
          "civilDateTimeInterval": {
            "startDateTime": {
              "date": {
                "year": 2026,
                "month": 8,
                "day": 20
              },
              "time": {
                "hours": 0,
                "minutes": 0
              }
            },
            "endDateTime": {
              "date": {
                "year": 2026,
                "month": 8,
                "day": 21
              },
              "time": {
                "hours": 0,
                "minutes": 0
              }
            }
          },
          "civilIso8601TimeInterval": {
            "startTime": "2026-08-20T00:00:00",
            "endTime": "2026-08-21T00:00:00"
          }
        }
      ]
    }
  },
  {
    "data": {
      "version": "1",
      "clientProvidedSubscriptionName": "subscription-name",
      "healthUserId": "health-user-id-2",
      "operation": "UPSERT",
      "dataType": "sleep",
      "intervals": [
        {
          "physicalTimeInterval": {
            "startTime": "2026-08-12T22:00:00Z",
            "endTime": "2026-08-13T06:00:00Z"
          },
          "civilDateTimeInterval": {
            "startDateTime": {
              "date": {
                "year": 2026,
                "month": 8,
                "day": 12
              },
              "time": {
                "hours": 22,
                "minutes": 0
              }
            },
            "endDateTime": {
              "date": {
                "year": 2026,
                "month": 8,
                "day": 13
              },
              "time": {
                "hours": 6,
                "minutes": 0
              }
            }
          },
          "civilIso8601TimeInterval": {
            "startTime": "2026-08-12T22:00:00",
            "endTime": "2026-08-13T06:00:00"
          }
        }
      ],
      "recordId": "9875412"
    }
  }
]

Поле operation указывает на тип изменения, которое вызвало уведомление:

  • UPSERT – отправляется при любом добавлении или изменении данных.
  • DELETE – отправляется, когда пользователь удаляет данные (например, удаляет dataPoints в приложении Fitbit или выполняет пакетное удаление с помощью API). Уведомления вебхука не отправляются для событий удаления на системном уровне, например когда пользователь отзывает разрешения приложения или удаляет свой аккаунт Fitbit.

Рекомендуем сделать логику обработки уведомлений идемпотентной, особенно для операций UPSERT, поскольку повторные попытки могут привести к отправке дублирующихся уведомлений.

Поле clientProvidedSubscriptionName – это уникальный идентификатор.

  • Для подписок с правилами MANUAL это поле содержит постоянное название подписки, указанное разработчиком при ее создании. Это позволяет получить стабильный идентификатор для управления подписками, оформленными вручную.
  • Для подписок, созданных с помощью правила AUTOMATIC, Google Health API автоматически генерирует и назначает идентификатор в формате auto-<project-number>-<subscriber-id>-<WEBHOOK_DATA_TYPE_TOKEN>, где:
    • <project-number> – номер облачного проекта Google Cloud разработчика (например, 362978535920).
    • <subscriber-id> – идентификатор регистрации или имя подписчика (например, google-health-sync).
    • <WEBHOOK_DATA_TYPE_TOKEN> – токен, представляющий определенный тип данных. Все возможные токены перечислены в словаре токенов типов данных вебхуков.

Включение clientProvidedSubscriptionName для правил, применяемых вручную и автоматически, обеспечивает согласованный формат полезной нагрузки уведомлений для всех типов подписок.

Сопоставление идентификаторов пользователей

healthUserId – это идентификатор Google Health API для пользователя, чьи данные изменились. Если ваше приложение поддерживает несколько пользователей, вы можете получать уведомления для любого пользователя, который предоставил согласие вашему приложению. Получив уведомление, используйте healthUserId, чтобы определить, чьи данные изменились, и запросить их с помощью учетных данных OAuth этого пользователя.

Чтобы сопоставить учетные данные OAuth пользователя с его healthUserId, используйте конечную точку getIdentity. Вызовите эту конечную точку с учетными данными пользователя во время его регистрации, чтобы получить его healthUserId, и сохраните это сопоставление. Это сопоставление не меняется со временем, поэтому его можно кешировать на неопределенный срок. Пример приведен в разделе Получение идентификатора пользователя. Это позволяет выбрать правильные учетные данные пользователя при запросе данных на основе healthUserId в уведомлении.

Временные интервалы

Массив intervals содержит один или несколько объектов, представляющих временные интервалы для измененных данных:

Поле Тип Обязательно Описание
physicalTimeInterval object Нет Физический интервал времени по UTC для измененных данных (формат YYYY-MM-DDTHH:MM:SSZ). Заполняется для показателей за день; опускается для ежедневных сводок и действий за весь день.
civilDateTimeInterval object Да Дата и временной интервал измененных данных по местному календарю без учета UTC или часового пояса.
civilIso8601TimeInterval object Да Интервал гражданского (местного) времени, представленный в виде строки ISO 8601 (формат YYYY-MM-DDTHH:MM:SS) без UTC или смещений часового пояса.

Для действий, выполняемых в течение всего дня, и ежедневных агрегированных данных поле physicalTimeInterval намеренно опущено в полезной нагрузке уведомления. Это связано с тем, что мероприятия на весь день группируются по номинальной гражданской дате, а физическая продолжительность времени может быть неточной при пересечении часовых поясов. При обработке мероприятий на весь день рекомендуется использовать поля civilDateTimeInterval или civilIso8601TimeInterval. Подробнее о том, как запрашивать эти элементы данных с помощью фильтров гражданского времени…

Ниже приведен пример полезной нагрузки уведомления для мероприятия, которое длится весь день. Поле physicalTimeInterval опущено.

{
  "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"
        }
      }
    ]
  }
}

Словарь токенов типов данных вебхука

При использовании правила AUTOMATIC сгенерированный clientProvidedSubscriptionName содержит один из следующих суффиксов WEBHOOK_DATA_TYPE_*, написанных заглавными буквами, в зависимости от типа данных:

Токен типа данных вебхука Тип данных
WEBHOOK_DATA_TYPE_ACTIVE_MINUTES active-minutes
WEBHOOK_DATA_TYPE_ACTIVE_ZONE_MINUTES active-zone-minutes
WEBHOOK_DATA_TYPE_ACTIVITY_LEVEL activity-level
WEBHOOK_DATA_TYPE_ALTITUDE altitude
WEBHOOK_DATA_TYPE_BLOOD_GLUCOSE blood-glucose
WEBHOOK_DATA_TYPE_BODY_FAT body-fat
WEBHOOK_DATA_TYPE_CALORIES_IN_HEART_RATE_ZONE calories-in-heart-rate-zone
WEBHOOK_DATA_TYPE_DAILY_HEART_RATE_VARIABILITY daily-heart-rate-variability
WEBHOOK_DATA_TYPE_DAILY_HEART_RATE_ZONES daily-heart-rate-zones
WEBHOOK_DATA_TYPE_DAILY_OXYGEN_SATURATION daily-oxygen-saturation
WEBHOOK_DATA_TYPE_DAILY_RESPIRATORY_RATE daily-respiratory-rate
WEBHOOK_DATA_TYPE_DAILY_RESTING_HEART_RATE daily-resting-heart-rate
WEBHOOK_DATA_TYPE_DAILY_SLEEP_TEMPERATURE_DERIVATIONS daily-sleep-temperature-derivations
WEBHOOK_DATA_TYPE_DISTANCE distance
WEBHOOK_DATA_TYPE_EXERCISE exercise
WEBHOOK_DATA_TYPE_FLOORS floors
WEBHOOK_DATA_TYPE_HEART_RATE heart-rate
WEBHOOK_DATA_TYPE_HEART_RATE_VARIABILITY heart-rate-variability
WEBHOOK_DATA_TYPE_HEIGHT height
WEBHOOK_DATA_TYPE_HYDRATION_LOG hydration-log
WEBHOOK_DATA_TYPE_NUTRITION_LOG nutrition-log
WEBHOOK_DATA_TYPE_RESPIRATORY_RATE_SLEEP_SUMMARY respiratory-rate-sleep-summary
WEBHOOK_DATA_TYPE_RUN_VO2_MAX run-vo2-max
WEBHOOK_DATA_TYPE_SEDENTARY_PERIOD sedentary-period
WEBHOOK_DATA_TYPE_SLEEP sleep
WEBHOOK_DATA_TYPE_STEPS steps
WEBHOOK_DATA_TYPE_TIME_IN_HEART_RATE_ZONE time-in-heart-rate-zone
WEBHOOK_DATA_TYPE_WEIGHT weight
WEBHOOK_DATA_TYPE_USER_DELETED user-deleted (системное событие)
WEBHOOK_DATA_TYPE_USER_REVOKED_ACCESS user-revoked-access (системное событие)

Агрегирование обновлений

Чтобы минимизировать сетевой трафик, Google Health API объединяет сообщения в пакеты. Система настроена на отправку не более 99 сообщений в пакете, которые автоматически отправляются по мере их появления.

При пакетной отправке сообщений полезная нагрузка доставляется в виде массива JSON, содержащего несколько объектов уведомлений. Один пакет может включать обновления для нескольких пользователей по нескольким типам данных, на которые оформлена подписка. Пример полезной нагрузки приведен в разделе Пакет (несколько пользователей и типов данных).

Реакция на уведомления

Ваш сервер должен проверить заголовок Authorization, обработать синхронное рукопожатие для проверки конечной точки и немедленно ответить на уведомления об изменении данных кодом статуса HTTP 204 No Content. Чтобы избежать тайм-аутов, обрабатывайте полезную нагрузку уведомления асинхронно после отправки ответа. Если Google Health API получает любой другой код статуса или время ожидания запроса истекает, он пытается отправить уведомление позже.

Пример Node.js (Express):

app.post('/webhook-receiver', (req, res) => {
    if (req.headers.authorization !== EXPECTED_SECRET) {
        return res.status(401).send();
    }
    if (req.body && !Array.isArray(req.body) && req.body.type === 'verification') {
        return res.status(200).send();
    }

    // 1. Immediately acknowledge the notification batch
    res.status(204).send();

    // 2. Normalize single-object or batched array notifications and reconcile asynchronously
    const notifications = Array.isArray(req.body) ? req.body : [req.body];
    setImmediate(async () => {
        for (const notification of notifications) {
            const { healthUserId, dataType, intervals = [] } = notification.data || {};
            console.log(`Update for user ${healthUserId} of type ${dataType}`);
            // Query /v4/users/${healthUserId}/dataTypes/${dataType}/dataPoints:reconcile?filter=...
            // for each interval in `intervals` (using >= and < comparators; for sleep, filter on
            // sleep.interval.end_time or sleep.interval.civil_end_time).
        }
    });
});

Чтобы получить обновленные данные для каждого интервала, указанного в уведомлении, вызовите конечную точку reconcile с фильтром для диапазона дат интервала. При этом автоматически удаляются дублирующиеся интервалы с подключенных устройств. Подробную информацию и примеры можно найти в статье Как получить согласованное представление данных за интервал.

Существует три распространенных стратегии обновления базы данных после получения уведомления об изменении данных:

  • Постановка в очередь (рекомендуется). Входящие уведомления помещаются в очередь сообщений и обрабатываются по мере доступности системных ресурсов.
  • Немедленное получение. Настройте серверную часть так, чтобы она сразу вызывала Google Health API для получения обновленных данных и их сохранения в базе данных.
  • Ленивые обновления. Помечайте записи в локальной базе данных пользователя как устаревшие и получайте обновленные данные из Google Health API только при следующем посещении пользователем вашего приложения.

Стратегия очередей позволяет оперативно обрабатывать данные, не допуская перегрузки из-за большого количества уведомлений (например, если из-за сбоя в работе сервиса все уведомления доставляются одновременно). Вы можете обрабатывать эти очереди по расписанию с длинными интервалами (например, каждые три часа), чтобы экономить ресурсы, но пользователи обычно ожидают, что их данные будут обновляться быстрее.

Рекомендации

Чтобы обеспечить надежную и эффективную обработку обновлений данных, следуйте приведенным ниже рекомендациям.

  • Используйте Subscription API. С помощью Subscription API вы можете получать уведомления о том, что у пользователя появились новые данные для скачивания, вместо того чтобы опрашивать конечные точки.
  • Помещайте уведомления в очередь и обрабатывайте их отдельно. Получив уведомление о подписке, поместите его в очередь и обработайте отдельно в зависимости от ресурсов системы. Чтобы не перегружать конечную точку вебхука, подтверждайте запрос сразу.

Подтверждение подписи

Чтобы обеспечить подлинность уведомлений вебхуков, необработанный полезный объем JSON каждого исходящего уведомления вебхука подписывается закрытым ключом с помощью PublicKeySign Tink, предоставляя подпись в кодировке Base64 в HTTP-заголовке GOOGLE-HEALTH-API-SIGNATURE в запросе. Эти ключи подписи автоматически меняются каждые 30 дней, а соответствующий официальный набор открытых ключей распространяется в виде JSON-файла по постоянному URL https://www.gstatic.com/googlehealthapi/webhooks/webhooks_public_keyset.json.

Как подтвердить подпись

С помощью Tink (рекомендуется). Разработчики могут проверить подпись, используя примитив PublicKeyVerify в Tink. Получите набор открытых ключей по постоянному URL, создайте экземпляр примитива PublicKeyVerify с набором ключей и проверьте декодированный заголовок GOOGLE-HEALTH-API-SIGNATURE по необработанной полезной нагрузке JSON веб-хука.

Проверка вручную (без Tink). Если разработчики не хотят использовать Tink, они могут проверить подпись вручную, выполнив следующие действия:

  1. Декодирует заголовок GOOGLE-HEALTH-API-SIGNATURE в формате Base64, чтобы отделить пятибайтовый префикс Tink (содержит однобайтовый префикс версии и четырехбайтовый идентификатор keyId) от фактической подписи в кодировке DER.
  2. Получите набор ключей JSON по адресу https://www.gstatic.com/googlehealthapi/webhooks/webhooks_public_keyset.json.
  3. Найдите ключ, соответствующий обработанному keyId, и декодируйте его поле value в формате Base64. В этом поле содержится сериализованный буфер протокола EcdsaPublicKey.
  4. Извлеките координаты x и y с обратным порядком байтов (теги Protobuf 3 и 4) из этих двоичных данных.
  5. Создайте экземпляр стандартного открытого ключа ECDSA P-256 во встроенной криптографической библиотеке, используя извлеченные координаты x и y.
  6. Проверьте необработанную полезную нагрузку JSON веб-хука по извлеченной подписи DER с помощью алгоритма SHA-256.

Статус подписки и восстановление доступа

Если конечная точка подписчика станет недоступна или вернет код статуса ошибки (любой, кроме 204), Google Health API будет хранить ожидающие уведомления до семи дней и повторно отправлять их с экспоненциальной выдержкой.

Когда конечная точка снова заработает и вернет код 204, API автоматически доставит все сохраненные сообщения. Уведомления, полученные более семи дней назад, удаляются без возможности восстановления.

Распространенные ошибки

Код ошибки Сообщение Описание Рекомендация
400 Bad Request Недопустимый номер проекта в названии ресурса. При удалении или обновлении подписчика с использованием идентификатора проекта Google Cloud в URL запроса вместо номера проекта. Это относится к подпискам на вебхуки, использующим конечную точку projects.subscribers. В URL запроса используйте номер проекта Google Cloud, а не его идентификатор.
403 Forbidden У звонящего нет нужных прав При создании или получении списка подписчиков с использованием идентификатора проекта Google Cloud в URL запроса вместо номера проекта. Это относится к подпискам на вебхуки, использующим конечную точку projects.subscribers. В URL запроса используйте номер проекта Google Cloud, а не его идентификатор.