Webhook サブスクリプション

Google Health API を使用すると、ユーザーの健康に関するデータが変更されたときに、アプリケーションでリアルタイム通知を受信できます。変更をポーリングする代わりに、Google Health API でデータが利用可能になるとすぐに、サーバーは HTTPS POST リクエストの Webhook を受信します。

サポートされるデータタイプ

Webhook 通知は、次のデータ型でサポートされています。

表: Webhook をサポートする Google Health API のデータタイプ
データ型 使用可能な
オペレーション
スコープ
アクティブな時間(分)
dataType: active-minutes
filter parameter: active_minutes
レコードタイプ: Interval
ストレージの解像度: 1 分

対応デバイス

  • Fitbit Air
  • Fitbit Alta
  • Fitbit Alta HR
  • Fitbit Blaze
  • Fitbit Charge 2
  • Fitbit Charge 3
  • Fitbit Flex 2
  • Fitbit Inspire
  • Fitbit Inspire HR
  • Google Pixel Watch 4
list、reconcile、rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
アクティブ ゾーン時間
dataType: active-zone-minutes
filter parameter: active_zone_minutes
レコードタイプ: Interval
ストレージの解像度: 1 分

対応デバイス

list、reconcile、rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
アクティビティ レベル
dataType: activity-level
filter parameter: activity_level
レコードタイプ: Interval
list、reconcile .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Altitude
dataType: altitude
filter parameter: altitude
レコードタイプ: Interval
ストレージの解像度: 1 分
list、reconcile、rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
血糖値
dataType: blood-glucose
filter parameter: blood_glucose
レコードタイプ: サンプル
list、get、reconcile、rollup、dailyRollup .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
体脂肪
dataType: body-fat
filter parameter: body_fat
レコードタイプ: サンプル

対応デバイス

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
レコードタイプ: Interval
ストレージの解像度: 1 分
rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
1 日の心拍変動
dataType: daily-heart-rate-variability
filter parameter: daily_heart_rate_variability
レコードタイプ: 日単位

対応デバイス

list、reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
1 日の心拍ゾーン
dataType: daily-heart-rate-zones
filter parameter: daily_heart_rate_zones
レコードタイプ: 日単位
list、reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
1 日の血中酸素ウェルネス
dataType: daily-oxygen-saturation
filter parameter: daily_oxygen_saturation
レコードタイプ: 日単位

対応デバイス

list、reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
1 日の呼吸数
dataType: daily-respiratory-rate
filter parameter: daily_respiratory_rate
レコードタイプ: 日単位

対応デバイス

list、reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
1 日の安静時の心拍数
dataType: daily-resting-heart-rate
filter parameter: daily_resting_heart_rate
レコードタイプ: 日単位

対応デバイス

list、reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
睡眠時の体温の 1 日の変動
dataType: daily-sleep-temperature-derivations
filter parameter: daily_sleep_temperature_derivations
レコードタイプ: 日単位

対応デバイス

list、reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
距離
dataType: distance
filter parameter: distance
レコードタイプ: Interval
ストレージの解像度: 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
レコードタイプ: Interval
ストレージの解像度: 1 分
reconcile、rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
心拍数
dataType: heart-rate
filter parameter: heart_rate
レコードタイプ: サンプル
ストレージの解像度: 1 秒

対応デバイス

list、reconcile、rollup、dailyRollup .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
心拍変動
dataType: heart-rate-variability
filter parameter: heart_rate_variability
レコードタイプ: サンプル

対応デバイス

list、reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
身長
dataType: height
filter parameter: height
レコードタイプ: サンプル
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
レコードタイプ: サンプル

対応デバイス

list、reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
ランニング時の最大酸素摂取量
dataType: run-vo2-max
filter parameter: run_vo2_max
レコードタイプ: サンプル

対応デバイス

list、reconcile、rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
座っている時間が長めの期間
dataType: sedentary-period
filter parameter: sedentary_period
レコードタイプ: Interval

対応デバイス

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
レコードタイプ: Interval
ストレージの解像度: 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
レコードタイプ: Interval
ストレージの解像度: 1 分
list、reconcile、rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
重量
dataType: weight
filter parameter: weight
レコードタイプ: サンプル

対応デバイス

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 など)に接続すると、安全で有効期間の短い認証情報が自動的に取得され、ローテーションされます。これにより、永続的な静的キーの管理と保存のリスクがなくなります。
  • 最小権限の原則: サービス アカウントは、ワークロードに専用の ID を提供します。サブスクライバー エンドポイントの管理に必要な特定の権限のみを付与することで、Google Cloud リソースへの広範なアクセスを回避できます。
  • ライフサイクルの独立性: サービス アカウントは、個々のユーザーのアカウントとは独立して動作するため、人事異動が長期的な認証の安定性に影響することはありません。

サービス アカウントを設定する

サービス アカウントを使用して認証するようにサブスクライバー アプリケーションを構成するには:

  1. サービス アカウントを作成する: Google Cloud コンソールで、プロジェクトの [IAM と管理] ページに移動して、新しいユーザー管理サービス アカウントを作成します。
  2. 必要な IAM ロールを付与する: Google Cloud プロジェクトでサブスクライバーを管理するために必要な適切なロールをサービス アカウントに割り当てます。
  3. サービス アカウントをワークロードに接続する: サブスクライバー ロジックをホストする環境を、新しいサービス アカウントとして実行するように構成します。これにより、アプリケーション コード(Google API クライアント ライブラリなど)は、projects.subscribers REST API を呼び出すときに、サービス アカウントの有効期間の短い認証情報を自動的に検出して使用できます。

CPE のロール

Google Health API のサブスクライバーまたはサブスクリプションを管理するには、API 呼び出しを行う権限を借用したサービス アカウントに適切なロールを付与する必要があります。必要なアクセスレベルに応じて、次のいずれかのロールを割り当てます。

  • Google Health API 読み取り
  • Google Health API 編集者
  • Google Health API 管理者

Google Health API の IAM のロールと権限の詳細を確認する。

サブスクライバーを管理する

通知を受信するには、アプリケーションの通知エンドポイントを表すサブスクライバーを登録する必要があります。サブスクライバーは、projects.subscribers で利用可能な REST API を使用して管理できます。

サブスクライバー エンドポイントは HTTPS(TLSv1.2+)を使用し、一般公開されている必要があります。サブスクライバーの作成と更新時に、Google Health API は検証チャレンジを実行して、エンドポイント URI を所有していることを確認します。検証が失敗すると、サブスクライバーの作成と更新のオペレーションは FailedPreconditionException で失敗します。

サブスクライバーを作成する

プロジェクトに新しいサブスクライバーを登録するには、create エンドポイントを使用します。以下の情報を提供する必要があります。

  • project-id: Webhook サービス アカウントが作成されたプロジェクトのプロジェクト番号。
  • subscriberId: サブスクライバーに指定する固有識別子。この ID は 4 ~ 36 文字で、正規表現([a-z]([a-z0-9-]{2,34}[a-z0-9]))と一致する必要があります。
  • endpointUri: Webhook 通知のリンク先 URL。
  • subscriberConfigs: 通知を受け取るデータタイプと、それぞれの定期購入に関するポリシー。
  • endpointAuthorization: エンドポイントの認可メカニズム。これには、指定した secret が含まれている必要があります。secret の値は、各通知メッセージとともに Authorization ヘッダーで送信されます。このトークンを使用して、受信リクエストが Google Health API からのものであることを確認できます。たとえば、secret を Bearer 認証の場合は Bearer R4nd0m5tr1ng123、Basic 認証の場合は 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 を更新すると、エンドポイントの検証が実行されます。詳しくは、Endpoint Verification をご覧ください。

subscriberConfigs を更新する際は、マージではなく完全な置換であることに注意してください。subscriberConfigs が updateMask に含まれている場合、そのサブスクライバーの保存されているすべての構成が、リクエスト本文で指定されたリストで上書きされます。構成を追加または削除するには、構成の完全なセットを指定する必要があります。他のフィールドを更新し、現在の構成を保持する場合は、updateMask から subscriberConfigs を省略します。

リクエスト

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)を更新するたびに、必須の 2 段階認証プロセス ハンドシェイクを実行します。このプロセスは、API 呼び出し中に同期的に実行されます。このサービスは、ユーザー エージェント Google-Health-API-Webhooks を使用して、JSON 本文 {"type": "verification"} を含む 2 つの自動 POST リクエストをエンドポイント URI に送信します。

  • 承認済みハンドシェイク: 構成済みの Authorization ヘッダーを使用して最初のリクエストが送信されます。サーバーは 200 OK または 201 Created ステータスで応答する必要があります。
  • 未承認のチャレンジ: 2 番目のリクエストは認証情報なしで送信されます。サーバーは 401 Unauthorized または 403 Forbidden ステータスで応答する必要があります。

このハンドシェイクにより、エンドポイントがアクティブであり、セキュリティが正しく適用されていることが確認されます。いずれかのステップが失敗すると、API リクエストは FAILED_PRECONDITION エラーで失敗します。このハンドシェイクが成功した後にのみ、サブスクライバーが保存され、健康に関するデータの通知を受信できるようになります。

鍵のローテーション

endpointAuthorization の鍵をローテーションする必要がある場合は、次の手順を行います。

  1. 古い 値と新しい endpointAuthorization 値の両方を受け入れるようにエンドポイントを構成します。
  2. ?updateMask=endpointAuthorization を含む patch リクエストを使用して、新しい endpointAuthorization 値でサブスクライバー構成を更新します。
  3. ステップ 2 が成功したことを確認したら、新しい endpointAuthorization 値のみを受け入れるようにエンドポイントを構成します。

ユーザーの登録

Google Health API を使用すると、ユーザーのサブスクリプションを効率的に管理できるため、ユーザーのオンボーディング時に手動で登録する必要性が軽減されます。

自動サブスクリプション

自動サブスクリプションを使用することをおすすめします。この機能を有効にするには、特定のデータ型の subscriberConfigs で subscriptionCreatePolicy を AUTOMATIC に設定します。AUTOMATIC ポリシーで指定する dataTypes は、Google Health API が通知を送信するデータタイプと同じです。ただし、これらのデータタイプについてもユーザーの同意が得られている必要があります。

ユーザーが AUTOMATIC ポリシーを持つデータ型に対応するスコープにアプリケーションの同意を付与すると、Google Health API は、ユーザーが同意したデータ型と、そのユーザーの自動サブスクライバー構成データ型の共通部分から得られるデータ型を自動的に追跡し、通知を送信します。その後、そのユーザーがそれらのタイプの新しいデータを生成するたびに、エンドポイントに通知が送信されます。これは、サブスクライバーの作成前または作成後に同意を付与したユーザーに適用されます。サブスクライバーが作成される前に生成されたデータについては、通知はバックフィルされません。

ユーザーが同意を取り消すと、対応するデータタイプの通知が停止します。自動サブスクリプションは Google によって管理され、個別に一覧表示したり削除したりすることはできません。親サブスクライバーが削除された場合にのみ削除されます。

手動サブスクリプション

特定のデータタイプに対して MANUAL subscription_create_policy を使用するようにサブスクライバーが構成されている場合は、ユーザーごとにサブスクリプションを明示的に作成して管理する必要があります。サブスクリプションは、定義された一連のデータタイプについて、特定のユーザーをサブスクライバー エンドポイントにリンクします。デベロッパーは、特定の API を使用して次のことができます。

  • healthUserId ごとに(手動で)サブスクリプションを作成する - 特定のユーザーの新しいサブスクリプションを作成します。このメソッドでは、リクエストされたデータタイプに対して、サブスクライバーの SubscriptionCreatePolicy が MANUAL に設定されている必要があります。
  • 定期購入の更新(手動)- 既存のユーザーの定期購入のデータタイプを更新します。
  • サブスクリプションを削除(手動)- 特定のユーザーのサブスクリプションを削除します。削除すると、サブスクライバー エンドポイントは、関連するデータタイプについて、このユーザーの通知を受信しなくなります。
  • サブスクリプションを一覧表示する(手動)- 特定のサブスクライバーのすべてのアクティブなサブスクリプションを一覧表示します。結果はユーザーまたはデータタイプでフィルタできます。

通知

登録されたデータタイプのユーザーのデータが変更されると、Google Health API は HTTPS POST リクエストをサブスクライバー エンドポイント URL に送信します。

通知形式

通知ペイロードには、ユーザー ID、データ型、時間間隔など、データ変更に関する詳細が含まれています。これらを使用して、更新されたデータをクエリできます。複数の更新が発生した場合や、更新が集約された場合、通知はバッチ処理され、通知オブジェクトの JSON 配列として配信されます。

Webhook 通知がトリガーされると、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: ユーザーが明示的にデータを削除したときに送信されます(Fitbit アプリを使用して dataPoints を削除したり、API を使用して一括削除を実行したりした場合など)。ユーザーがアプリケーションの権限を取り消したり、Fitbit アカウントを削除したりするなど、システムレベルの削除イベントでは、Webhook 通知は送信されません。

再試行によって重複した通知が送信される可能性があるため、通知処理ロジックをべき等にすることをおすすめします。特に UPSERT オペレーションでは、このことが重要になります。

clientProvidedSubscriptionName フィールドは固有識別子です。

  • MANUAL ポリシーが適用されているサブスクリプションの場合、このフィールドには、サブスクリプションの作成時に指定された、デベロッパーが指定した永続的なサブスクリプション名が含まれます。これにより、手動サブスクリプションを管理するための安定した ID が提供されます。
  • AUTOMATIC ポリシーで作成されたサブスクリプションの場合、Google Health API は auto-<project-number>-<subscriber-id>-<WEBHOOK_DATA_TYPE_TOKEN> 形式の識別子を自動的に生成して割り当てます。ここで、
    • <project-number> は、デベロッパーの Google Cloud プロジェクト番号です(例: 362978535920)。
    • <subscriber-id> は、サブスクライバーの登録 ID / サブスクライバー名です(例: google-health-sync)。
    • <WEBHOOK_DATA_TYPE_TOKEN> は、特定のデータ型を表すトークンです。使用可能なすべてのトークンについては、Webhook データ型トークンの語彙をご覧ください。

手動ポリシーと自動ポリシーの両方に clientProvidedSubscriptionName を含めると、すべてのサブスクリプション タイプで一貫した通知ペイロード形式が保証されます。

ユーザー ID のマッピング

healthUserId は、データが変更されたユーザーの Google Health API 識別子です。アプリケーションが複数のユーザーをサポートしている場合は、アプリケーションに同意したユーザーの通知を受け取ることができます。通知を受け取ったら、healthUserId を使用して、どのユーザーのデータが変更されたかを特定し、そのユーザーの OAuth 認証情報を使用してデータをクエリします。

ユーザーの OAuth 認証情報を healthUserId にマッピングするには、getIdentity エンドポイントを使用します。ユーザーのオンボーディング中にユーザーの認証情報を使用してこのエンドポイントを呼び出し、healthUserId を取得して、このマッピングを保存します。このマッピングは時間の経過とともに変化しないため、無期限にキャッシュに保存できます。例については、ユーザー ID を取得するをご覧ください。これにより、通知の healthUserId に基づいてデータをクエリするときに、正しいユーザー認証情報を選択できます。

時間間隔

intervals 配列には、変更されたデータの時間間隔を表す 1 つ以上のオブジェクトが含まれています。

フィールド 型 必須 / 省略可 説明
physicalTimeInterval object いいえ 変更されたデータの UTC 物理時間間隔(YYYY-MM-DDTHH:MM:SSZ 形式)。イントラデイ指標の場合は入力されます。1 日の集計と 1 日のアクティビティの場合は省略されます。
civilDateTimeInterval object ○ 変更されたデータの市民(現地)カレンダーの日付と時間間隔。UTC またはタイムゾーン オフセットは含まれません。
civilIso8601TimeInterval object ○ UTC またはタイムゾーン オフセットのない ISO 8601 文字列(YYYY-MM-DDTHH:MM:SS 形式)として表される、市民(ローカル)時間の間隔。

1 日のアクティビティと 1 日の集計の場合、physicalTimeInterval フィールドは通知ペイロードから意図的に省略されます。これは、終日イベントが名目上の市民日付でバケット化され、タイムゾーンをまたぐと物理的な時間の長さが不正確になる可能性があるためです。終日イベントを処理する場合は、ベスト プラクティスとして civilDateTimeInterval フィールドまたは civilIso8601TimeInterval フィールドを使用する必要があります。常用時フィルタを使用してこれらのデータポイントをクエリする方法については、常用時の開始時間の間隔でデータをフィルタするをご覧ください。

physicalTimeInterval フィールドが省略されている、1 日のアクティビティの通知ペイロードの例を次に示します。

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

Webhook データ型トークンの語彙

AUTOMATIC ポリシーを使用する場合、生成される clientProvidedSubscriptionName には、データ型に応じて、次のいずれかの大文字の WEBHOOK_DATA_TYPE_* サフィックス トークンが含まれます。

Webhook データ型トークン 対応するデータ型
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 件のメッセージを送信するように構成されています。これらのメッセージは、利用可能になると自動的に push されます。

メッセージがバッチ処理されると、ペイロードは複数の通知オブジェクトを含む JSON 配列として配信されます。1 つのバッチに、複数の登録済みデータタイプにわたる複数のユーザーの更新を含めることができます。ペイロードの例については、通知形式セクションのバッチ(複数のユーザーとデータタイプ)の例をご覧ください。

通知に応答する

サーバーは、Authorization ヘッダーを検証し、同期エンドポイント検証 handshake を処理して、データ変更通知に 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 エンドポイントを呼び出します。これにより、接続されたデバイスから重複する間隔が自動的に重複除去されます。詳細と例については、インターバル データの調整済みビューを取得するをご覧ください。

データ変更通知を受け取った後にデータベースを更新する一般的な戦略は次の 3 つです。

  • キューイング(推奨): 受信した通知をメッセージ キューに配置し、システム リソースに基づいてキューから処理します。
  • 即時取得: バックエンドに Google Health API をすぐに呼び出して、更新されたデータを取得し、データベースに commit するように指示します。
  • 遅延更新: ユーザーのローカル データベース レコードをダーティとしてマークし、ユーザーが次回アプリにアクセスしたときにのみ、更新されたデータを Google Health API から取得します。

キューイング戦略を使用すると、大量の通知(たとえば、サービス停止により通知のバックログが一度に配信される場合など)に対する復元力を維持しながら、準リアルタイムでデータを処理できます。リソースを節約するために、これらのキューを長い間隔のバッチ スケジュール(3 時間ごとなど)で処理することもできますが、ユーザーは通常、データが準リアルタイムで更新されることを期待しているというデメリットがあります。

ベスト プラクティス

データの更新を確実かつ効率的に処理するには、次のベスト プラクティスに沿って対応してください。

  • Subscription API を使用する: エンドポイントをポーリングするのではなく、Subscription API を使用して、ユーザーがダウンロードする新しいデータがあるときに通知を受け取ります。
  • 通知をキューに入れて個別に処理する: 定期購入の通知を受信したら、キューに入れて、システムのリソースに基づいて個別に処理します。リクエストをすぐに確認することで、Webhook エンドポイントを軽量に保ちます。

署名の検証

Webhooks 通知の信頼性を確保するため、送信されるすべての Webhook 通知の未加工の JSON ペイロードは、Tink の PublicKeySign を使用して秘密鍵で署名され、リクエストの GOOGLE-HEALTH-API-SIGNATURE HTTP ヘッダーに Base64 エンコードされた署名が提供されます。これらの署名鍵は 30 日ごとに自動的にローテーションされ、対応する公式の公開鍵セットは、永続 URL https://www.gstatic.com/googlehealthapi/webhooks/webhooks_public_keyset.json で JSON ファイルとして配布されます。

署名を検証する方法

Tink を使用する(推奨): デベロッパーは、Tink の PublicKeyVerify プリミティブを使用して署名を検証できます。永続的な URL から公開鍵セットを取得し、その鍵セットを使用して PublicKeyVerify プリミティブをインスタンス化し、デコードされた GOOGLE-HEALTH-API-SIGNATURE ヘッダーを未加工の Webhook JSON ペイロードと照合して検証します。

手動検証(Tink を使用しない場合): Tink を使用しない場合は、次の手順に沿って手動で署名を検証できます。

  1. GOOGLE-HEALTH-API-SIGNATURE ヘッダーを Base64 でデコードして、5 バイトの Tink プレフィックス(1 バイトのバージョン プレフィックスと 4 バイトの keyId を含む)を実際の DER エンコード署名から分離します。
  2. https://www.gstatic.com/googlehealthapi/webhooks/webhooks_public_keyset.json から JSON 鍵セットを取得します。
  3. 解析された keyId と一致する鍵を見つけ、その値フィールドを Base64 でデコードします。このフィールドには、シリアル化された EcdsaPublicKey プロトコル バッファが含まれています。
  4. このバイナリ ペイロードからビッグエンディアンの x 座標と y 座標(Protobuf タグ 3 と 4)を抽出します。
  5. 抽出した x 座標と y 座標を使用して、組み込みの暗号ライブラリで標準の ECDSA P-256 公開鍵をインスタンス化します。
  6. SHA-256 アルゴリズムを使用して、抽出された DER 署名に対して未加工の Webhook JSON ペイロードを検証します。

サブスクライバーのステータスと復元

サブスクライバー エンドポイントが使用できなくなった場合や、エラーステータス コード(204 以外)が返された場合、Google Health API は保留中の通知を最大 7 日間保存し、指数バックオフを使用して配信を再試行します。

エンドポイントがオンラインに戻り、204 で応答すると、API は保存されたメッセージのバックログを自動的に配信します。7 日以上経過した通知は破棄され、復元できません。

一般的なエラー

エラーコード メッセージ 説明 推奨事項
400 無効なリクエスト リソース名のプロジェクト番号が無効です リクエスト URL でプロジェクト番号ではなく Google Cloud プロジェクト ID を使用してサブスクライバーを削除または更新する場合。これは、projects.subscribers エンドポイントを使用する Webhook サブスクリプションに適用されます。 リクエスト URL には、プロジェクト ID ではなく Google Cloud プロジェクト番号を使用します。
403 禁止 The caller does not have permission リクエスト URL でプロジェクト番号の代わりに Google Cloud プロジェクト ID を使用してサブスクライバーを作成または一覧表示する場合。これは、projects.subscribers エンドポイントを使用する Webhook サブスクリプションに適用されます。 リクエスト URL には、プロジェクト ID ではなく Google Cloud プロジェクト番号を使用します。