웹훅 구독

Google Health API를 사용하면 사용자의 건강 데이터가 변경될 때 애플리케이션에서 실시간 알림을 받을 수 있습니다. 변경사항을 폴링하는 대신 Google Health API에서 데이터를 사용할 수 있게 되는 즉시 서버에서 HTTPS POST 요청 웹훅을 수신합니다.

지원되는 데이터 유형

웹훅 알림은 다음 데이터 유형에 지원됩니다.

표: 웹훅을 지원하는 Google Health API 데이터 유형
데이터 유형 사용 가능한
작업
범위
활동적 시간(분)
dataType: active-minutes
filter parameter: active_minutes
레코드 유형: 인터벌
스토리지 해상도: 1분

호환 기기

  • Fitbit Air
  • Fitbit Alta
  • Fitbit Alta HR
  • Fitbit Blaze
  • Fitbit Charge 2
  • Fitbit Charge 3
  • Fitbit Flex 2
  • Fitbit Inspire
  • Fitbit Inspire HR
  • Pixel Watch 4
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
레코드 유형: 인터벌
list, reconcile .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
혈당
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
레코드 유형: 인터벌
스토리지 해상도: 1분
rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
일일 심박 변이도
dataType: daily-heart-rate-variability
filter parameter: daily_heart_rate_variability
기록 유형: 일일

호환 기기

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
일일 심박수 구간
dataType: daily-heart-rate-zones
filter parameter: daily_heart_rate_zones
기록 유형: 일일
list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
일일 산소 포화도
dataType: daily-oxygen-saturation
filter parameter: daily_oxygen_saturation
기록 유형: 일일

호환 기기

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
일일 호흡수
dataType: daily-respiratory-rate
filter parameter: daily_respiratory_rate
기록 유형: 일일

호환 기기

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
일일 안정 시 심박수
dataType: daily-resting-heart-rate
filter parameter: daily_resting_heart_rate
기록 유형: 일일

호환 기기

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
일일 수면 온도 편차
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
레코드 유형: 인터벌
스토리지 해상도: 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
레코드 유형: 샘플
스토리지 해상도: 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
레코드 유형: 인터벌

호환 기기

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
레코드 유형: 샘플

호환 기기

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: 웹훅 서비스 계정이 생성된 프로젝트 번호입니다.
  • subscriberId: 구독자에게 제공하는 고유 식별자입니다. 이 ID는 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에 포함된 경우 해당 구독자의 저장된 모든 구성이 요청 본문에 제공된 목록으로 덮어쓰기됩니다. 구성을 추가하거나 삭제하려면 전체 구성 집합을 제공해야 합니다. 다른 필드를 업데이트하고 현재 구성을 유지하려면 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"}과 함께 엔드포인트 URI에 자동화된 POST 요청을 두 개 전송합니다.

  • 승인된 핸드셰이크: 첫 번째 요청은 구성된 Authorization 헤더와 함께 전송됩니다. 서버는 200 OK 또는 201 Created 상태로 응답해야 합니다.
  • 승인되지 않은 챌린지: 두 번째 요청은 사용자 인증 정보 없이 전송됩니다. 서버는 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는 구독자 엔드포인트 URL로 HTTPS POST 요청을 전송합니다.

알림 형식

알림 페이로드에는 업데이트된 데이터를 쿼리하는 데 사용할 수 있는 사용자 ID, 데이터 유형, 시간 간격을 비롯한 데이터 변경사항에 관한 세부정보가 포함됩니다. 여러 업데이트가 발생하거나 업데이트가 집계되면 알림이 일괄 처리되고 알림 객체의 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"
  }
}

operation 필드는 알림을 트리거한 변경 유형을 나타냅니다.

  • UPSERT: 데이터 추가 또는 수정 시 전송됩니다.
  • DELETE: 사용자가 명시적으로 데이터를 삭제할 때 전송됩니다 (예: Fitbit 앱을 사용하여 dataPoints를 삭제하거나 API를 사용하여 일괄 삭제를 실행하는 경우). 사용자가 애플리케이션 권한을 취소하거나 Fitbit 계정을 삭제하는 등의 시스템 수준 삭제 이벤트에 대해서는 웹훅 알림이 전송되지 않습니다.

재시도로 인해 중복 알림이 전송될 수 있으므로 알림 처리 로직을 멱등성으로 만드는 것이 좋습니다(특히 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>은 특정 데이터 유형을 나타내는 토큰입니다. 가능한 모든 토큰은 웹훅 데이터 유형 토큰의 어휘를 참조하세요.

수동 및 자동 정책 모두에 clientProvidedSubscriptionName를 포함하면 모든 정기 결제 유형에서 일관된 알림 페이로드 형식이 보장됩니다.

사용자 ID 매핑

healthUserId는 데이터가 변경된 사용자의 Google Health API 식별자입니다. 애플리케이션이 여러 사용자를 지원하는 경우 애플리케이션에 동의를 부여한 모든 사용자에 대한 알림을 받을 수 있습니다. 알림을 받으면 healthUserId를 사용하여 데이터가 변경된 사용자를 식별하여 OAuth 사용자 인증 정보를 사용하여 데이터를 쿼리할 수 있습니다.

사용자의 OAuth 사용자 인증 정보를 healthUserId에 매핑하려면 getIdentity 엔드포인트를 사용합니다. 사용자 온보딩 중에 사용자의 사용자 인증 정보로 이 엔드포인트를 호출하여 healthUserId를 검색하고 이 매핑을 저장합니다. 이 매핑은 시간이 지나도 변경되지 않으므로 무기한 캐시할 수 있습니다. 예시는 사용자 ID 가져오기를 참고하세요. 이렇게 하면 알림의 healthUserId를 기반으로 데이터를 쿼리할 때 올바른 사용자 인증 정보를 선택할 수 있습니다.

시간 간격

intervals 배열에는 수정된 데이터의 시간 간격을 나타내는 객체가 하나 이상 포함됩니다.

필드 유형 필수 설명
physicalTimeInterval object 아니요 수정된 데이터의 UTC 물리적 시간 간격입니다 (YYYY-MM-DDTHH:MM:SSZ 형식). 일중 측정항목에 채워지고 일일 롤업 및 하루 종일 활동에는 생략됩니다.
civilDateTimeInterval object 예 수정된 데이터의 민간(현지) 달력 날짜 및 시간 간격입니다(UTC 또는 시간대 오프셋 없음).
civilIso8601TimeInterval object 예 UTC 또는 시간대 오프셋이 없는 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"
        }
      }
    ]
  }
}

웹훅 데이터 유형 토큰의 어휘

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에서 업데이트된 데이터를 가져옵니다.

큐에 추가 전략을 사용하면 알림이 폭주하는 경우에도 (예: 서비스 중단으로 인해 알림 백로그가 한꺼번에 전송되는 경우) 거의 실시간으로 데이터를 처리할 수 있습니다. 리소스를 절약하기 위해 긴 간격의 일괄 일정 (예: 3시간마다)으로 이러한 큐를 처리하도록 선택할 수 있지만, 사용자는 일반적으로 데이터가 거의 실시간으로 업데이트되기를 기대한다는 단점이 있습니다.

권장사항

데이터 업데이트를 안정적이고 효율적으로 처리하려면 다음 권장사항을 따르세요.

  • Subscription API 사용: 엔드포인트를 폴링하는 대신 Subscription API를 사용하여 사용자에게 다운로드할 새 데이터가 있을 때 알림을 받습니다.
  • 알림을 별도로 큐에 추가하고 처리: 정기 결제 알림을 수신하면 큐에 배치하고 시스템 리소스에 따라 별도로 처리합니다. 요청을 즉시 확인하여 웹훅 엔드포인트를 가볍게 유지합니다.

서명 확인

웹훅 알림의 신뢰성을 보장하기 위해 모든 발신 웹훅 알림의 원시 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 프리미티브를 인스턴스화하고, 원시 웹훅 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와 일치하는 키를 찾고 직렬화된 EcdsaPublicKey 프로토콜 버퍼가 포함된 값 필드를 Base64로 디코딩합니다.
  4. 이 바이너리 페이로드에서 빅엔디안 x 및 y 좌표 (Protobuf 태그 3 및 4)를 추출합니다.
  5. 추출된 x 및 y 좌표를 사용하여 기본 제공 암호화 라이브러리에서 표준 ECDSA P-256 공개 키를 인스턴스화합니다.
  6. SHA-256 알고리즘을 사용하여 추출된 DER 서명과 비교하여 원시 웹훅 JSON 페이로드를 확인합니다.

구독자 상태 및 복구

구독자 엔드포인트를 사용할 수 없거나 오류 상태 코드(204 이외의 값)를 반환하는 경우 Google Health API는 대기 중인 알림을 최대 7일 동안 저장하고 지수 백오프를 사용하여 전송을 재시도합니다.

엔드포인트가 다시 온라인 상태가 되고 204로 응답하면 API가 저장된 메시지의 백로그를 자동으로 전송합니다. 7일이 지난 알림은 삭제되며 복구할 수 없습니다.

일반적인 오류

오류 코드 메시지 설명 추천
400 잘못된 요청 리소스 이름의 프로젝트 번호가 잘못되었습니다. 요청 URL에서 프로젝트 번호 대신 Google Cloud 프로젝트 ID를 사용하여 구독자를 삭제하거나 업데이트하는 경우 이는 projects.subscribers 엔드포인트를 사용하는 웹훅 구독에 적용됩니다. 요청 URL에 프로젝트 ID가 아닌 Google Cloud 프로젝트 번호를 사용합니다.
403 금지됨 호출자에게 권한이 없습니다 요청 URL에서 프로젝트 번호 대신 Google Cloud 프로젝트 ID를 사용하여 구독자를 만들거나 나열하는 경우 이는 projects.subscribers 엔드포인트를 사용하는 웹훅 구독에 적용됩니다. 요청 URL에 프로젝트 ID가 아닌 Google Cloud 프로젝트 번호를 사용합니다.