Webhook 訂閱項目

Google Health API 可讓應用程式在使用者健康資料變更時收到通知。Google Health API 有資料可用時,伺服器會立即收到 HTTPS POST 要求 Webhook,不必輪詢變更。

支援的資料類型

Webhook 通知支援下列資料類型:

表格:支援 Webhook 的 Google Health API 資料類型
資料類型 可用的
作業
範圍
活動分鐘數
dataType:active-minutes
篩選器參數:active_minutes
記錄類型:間隔
儲存空間解析度:1 分鐘

相容裝置

list、reconcile、rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
活動區間分鐘數
dataType:active-zone-minutes
篩選器參數:active_zone_minutes
記錄類型:間隔
儲存空間解析度:1 分鐘

相容裝置

list、reconcile、rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
活動強度
dataType:activity-level
篩選器參數:activity_level
記錄類型:間隔
list、reconcile .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Altitude
dataType:altitude
篩選器參數:altitude
記錄類型:間隔
儲存空間解析度:1 分鐘
list、reconcile、rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
血糖
dataType:blood-glucose
篩選器參數:blood_glucose
記錄類型:範例
list、get、reconcile、rollup、dailyRollup .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
體脂肪
dataType:body-fat
篩選器參數: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
篩選器參數:calories_in_heart_rate_zone
記錄類型:間隔
儲存空間解析度:1 分鐘
rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
每日心率變異
dataType:daily-heart-rate-variability
篩選器參數:daily_heart_rate_variability
記錄類型:每日

相容裝置

list、reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
每日心率區間
dataType:daily-heart-rate-zones
篩選器參數:daily_heart_rate_zones
記錄類型:每日
list、reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
每日血氧濃度
dataType:daily-oxygen-saturation
篩選器參數:daily_oxygen_saturation
記錄類型:每日

相容裝置

list、reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
每日呼吸速率
dataType:daily-respiratory-rate
篩選器參數:daily_respiratory_rate
記錄類型:每日

相容裝置

list、reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
每日靜止心率
dataType:daily-resting-heart-rate
篩選器參數:daily_resting_heart_rate
記錄類型:每日

相容裝置

list、reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
每日睡眠體溫變化
dataType:daily-sleep-temperature-derivations
篩選器參數:daily_sleep_temperature_derivations
記錄類型:每日

相容裝置

list、reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
距離
dataType:distance
篩選器參數:distance
記錄類型:間隔
儲存空間解析度:1 分鐘

相容裝置

list、reconcile、rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
運動
dataType:exercise
篩選器參數:exercise
記錄類型:工作階段

相容裝置

list、get、reconcile、create、update、batchDelete .activity_and_fitness.readonly
.activity_and_fitness.writeonly
樓層數
dataType:floors
篩選器參數:floors
記錄類型:間隔
儲存空間解析度:1 分鐘

相容裝置

reconcile、rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
心率
dataType:heart-rate
篩選器參數:heart_rate
記錄類型:範例
儲存空間解析度:1 秒

相容裝置

list、reconcile、rollup、dailyRollup .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
心率變異
dataType:heart-rate-variability
篩選器參數:heart_rate_variability
記錄類型:範例

相容裝置

list、reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
身高
dataType:height
篩選器參數:height
記錄類型:範例
list、get、reconcile、create、update、batchDelete .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
飲水量記錄
dataType:hydration-log
篩選器參數:hydration_log
記錄類型:工作階段
list、get、reconcile、rollup、dailyRollup、create、update、batchDelete .nutrition.readonly
.nutrition.writeonly
營養記錄
dataType:nutrition-log
篩選器參數:nutrition_log
記錄類型:工作階段
list、get、reconcile、rollup、dailyRollup、create、update、batchDelete .nutrition.readonly
.nutrition.writeonly
睡眠呼吸速率摘要
dataType:respiratory-rate-sleep-summary
篩選器參數:respiratory_rate_sleep_summary
記錄類型:範例

相容裝置

list、reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
跑步最大攝氧量
dataType:run-vo2-max
篩選器參數:run_vo2_max
記錄類型:範例

相容裝置

list、reconcile、rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
久坐時間
dataType:sedentary-period
篩選器參數:sedentary_period
記錄類型:間隔

相容裝置

list、reconcile、rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
睡眠
dataType:sleep
篩選器參數:sleep
記錄類型:工作階段

相容裝置

list、get、reconcile、create、update、batchDelete .sleep.readonly
.sleep.writeonly
步驟
dataType:steps
篩選器參數:steps
記錄類型:間隔
儲存空間解析度:1 分鐘

相容裝置

list、reconcile、rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
心率區間時間
dataType:time-in-heart-rate-zone
篩選器參數:time_in_heart_rate_zone
記錄類型:間隔
儲存空間解析度:1 分鐘
list、reconcile、rollup、dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
權重
dataType:weight
篩選器參數: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) 時,服務帳戶會自動取得及輪替安全的短期憑證。這樣一來,您就不必承擔管理及儲存永久靜態金鑰的風險。
  • 最小權限原則:服務帳戶會為工作負載提供專屬身分。您可以只授予管理訂閱者端點所需的特定權限,避免他們取得 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。這個 ID 的長度必須介於 4 到 36 個字元之間,且符合規則運算式 ([a-z]([a-z0-9-]{2,34}[a-z0-9]))。
  • endpointUri:Webhook 通知的到達網頁網址。
  • subscriberConfigs:您想接收通知的資料類型, 以及各類型的訂閱政策。
  • endpointAuthorization::端點的授權機制。這必須包含您提供的 secret。secret 的值會與每則通知訊息一起在 Authorization 標頭中傳送。您可以使用這個權杖,驗證傳入的要求是否來自 Google Health API。舉例來說,您可以將 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 時,請注意這是「完整替換」,而非合併。如果 updateMask 包含 subscriberConfigs,系統會以要求主體中提供的清單,覆寫該訂閱者的所有儲存設定。如要新增或移除設定,您必須提供完整的設定。如果您要更新其他欄位,並想保留目前的設定,請從 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」。
{}

端點驗證

為確保通知傳送安全可靠,每當您建立訂閱者或更新其端點設定 (endpointUri 或 endpointAuthorization) 時,Google Health API 都會執行強制性的兩步驟驗證。這項程序會在 API 呼叫期間同步執行。服務會使用使用者代理程式 Google-Health-API-Webhooks,將兩個自動 POST 要求傳送至端點 URI,並附上 JSON 主體 {"type": "verification"}。

  • 授權交握:第一個要求會與您設定的 Authorization 標頭一起傳送。伺服器必須傳回 200 OK 或 201 Created 狀態。
  • 未授權驗證:第二項要求在未提供憑證的情況下傳送。伺服器必須傳回 401 Unauthorized 或 403 Forbidden 狀態。

這項交握程序可確認端點是否有效,以及是否正確執行安全性。如果任一步驟失敗,API 要求就會失敗,並傳回 FAILED_PRECONDITION 錯誤。只有在握手成功後,系統才會儲存並啟用訂閱者,以便接收健康資料通知。

金鑰輪替

如要輪替 endpointAuthorization 的金鑰,請按照下列步驟操作:

  1. 將端點設為接受新舊 endpointAuthorization 值。
  2. 使用 patch 要求和 ?updateMask=endpointAuthorization,以新的 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 要求。

通知格式

通知酬載包含資料變更的詳細資料,包括使用者 ID、資料類型和時間間隔,您可以使用這些資料查詢更新後的資料。如果有多項更新或更新項目經過匯總,系統會將通知批次處理,並以通知物件的 JSON 陣列形式傳送。

觸發 Webhook 通知時,Google Health API 會向訂閱者端點傳送 HTTPS POST 要求,格式如下:

新增或更新 (時間序列資料)

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 欄位是專屬 ID:

  • 如果是採用 MANUAL 政策的訂閱項目,這個欄位會包含建立訂閱項目時,由開發人員提供的永久訂閱項目名稱。這可提供穩定的 ID,用於管理手動訂閱項目。
  • 對於使用 AUTOMATIC 政策建立的訂閱項目,Google Health API 會自動產生並指派格式為 auto-<project-number>-<subscriber-id>-<WEBHOOK_DATA_TYPE_TOKEN> 的 ID,其中:
    • <project-number> 是開發人員的 Google Cloud 專案編號 (例如 362978535920)。
    • <subscriber-id> 是訂閱者的註冊 ID/訂閱者名稱 (例如 google-health-sync)。
    • <WEBHOOK_DATA_TYPE_TOKEN> 是代表特定資料類型的權杖。如要查看所有可能的權杖,請參閱「Webhook 資料類型權杖詞彙」。

無論是手動或自動政策,都應加入 clientProvidedSubscriptionName,確保所有訂閱類型的通知酬載格式一致。

使用者 ID 對應

healthUserId 是 Google Health API ID,用於識別資料有異動的使用者。如果應用程式支援多位使用者,只要使用者同意授權,您就能收到通知。收到通知時,請使用 healthUserId 找出資料有變動的使用者,以便使用他們的 OAuth 憑證查詢資料。

如要將使用者的 OAuth 憑證對應至其 healthUserId,請使用 getIdentity 端點。在使用者新手上路期間,使用使用者憑證呼叫這個端點,擷取其 healthUserId,並儲存這項對應關係。這項對應關係不會隨時間變更,因此可以無限期快取。如需範例,請參閱「取得使用者 ID」。這樣一來,您就能根據通知中的 healthUserId 查詢資料,並選取正確的使用者憑證。

時間間隔

intervals 陣列包含一或多個物件,代表修改資料的時間間隔:

欄位 類型 必要 說明
physicalTimeInterval object 否 修改資料的世界標準時間實際時間間隔 (YYYY-MM-DDTHH:MM:SSZ 格式)。系統會填入當日指標,但會省略每日匯總和全天活動。
civilDateTimeInterval object 是 修改資料的民用 (當地) 日曆日期和時間間隔,不含世界標準時間或時區偏移。
civilIso8601TimeInterval object 是 以 ISO 8601 字串 (YYYY-MM-DDTHH:MM:SS 格式) 表示的民用 (當地) 時間間隔,不含世界標準時間或時區偏移。

如果是全天活動和每日匯總資料,通知酬載會刻意省略 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"
        }
      }
    ]
  }
}

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 則訊息,並在訊息可用時自動推送。

批次處理訊息時,酬載會以包含多個通知物件的 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,擷取更新後的資料並提交至資料庫。
  • 延遲更新:將使用者的本機資料庫記錄標示為「dirty」,並在使用者下次造訪應用程式時,才從 Google Health API 擷取更新資料。

佇列策略可讓您及時處理資料,同時保持彈性,因應大量通知 (例如,服務中斷導致大量通知同時傳送)。雖然您可以選擇以較長的時間間隔批次排程 (例如每 3 小時) 處理這些佇列,以節省資源,但缺點是使用者通常希望看到資料能及時更新。

最佳做法

請遵循下列最佳做法,確保能可靠且有效率地處理資料更新:

  • 使用 Subscription API:使用 Subscription API,在使用者有新資料可供下載時接收通知,而不是輪詢端點。
  • 分別排入佇列並處理通知:收到訂閱通知時,請將通知放入佇列,並根據系統資源分別處理。立即確認要求,讓 Webhook 端點保持輕量。

驗證簽章

為確保 Webhook 通知真實可靠,每個傳出 Webhook 通知的原始 JSON 酬載都會使用 Tink 的 PublicKeySign 以私密金鑰簽署,並在要求中的 GOOGLE-HEALTH-API-SIGNATURE HTTP 標頭提供 Base64 編碼簽章。這些簽署金鑰每 30 天會自動輪替,對應的官方公開金鑰組則會以 JSON 檔案的形式,發布至永久網址 https://www.gstatic.com/googlehealthapi/webhooks/webhooks_public_keyset.json。

如何驗證簽章

使用 Tink (建議):開發人員可以使用 Tink 的 PublicKeyVerify 基元驗證簽章。從永久網址擷取公開金鑰集,使用金鑰集例項化 PublicKeyVerify 原始物件,並根據原始 Webhook JSON 酬載驗證解碼的 GOOGLE-HEALTH-API-SIGNATURE 標頭。

手動驗證 (不使用 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 Bad Request 資源名稱中的專案編號無效 在要求網址中使用 Google Cloud 專案 ID (而非專案編號) 刪除或更新訂閱者時,這項規定適用於使用 projects.subscribers 端點的 Webhook 訂閱項目。 在要求網址中使用 Google Cloud 專案編號,而非專案 ID。
403 禁止存取 呼叫者沒有權限 使用 Google Cloud 雲端專案 ID (而非專案編號) 在要求網址中建立或列出訂閱端時。這項規定適用於使用 projects.subscribers 端點的 Webhook 訂閱項目。 在要求網址中使用 Google Cloud 專案編號,而非專案 ID。