Subskrypcje webhooków

Interfejs Google Health API umożliwia aplikacji otrzymywanie powiadomień, gdy zmieniają się dane użytkownika dotyczące zdrowia. Zamiast sprawdzać, czy nastąpiły zmiany, Twój serwer otrzymuje żądanie HTTPS POST webhook, gdy tylko dane są dostępne w Google Health API.

Obsługiwane typy danych

Powiadomienia webhook są obsługiwane w przypadku tych typów danych:

Tabela: typy danych interfejsu Google Health API z obsługą webhooków
Typ danych Dostępne operacje
Zakres
Aktywne minuty
dataType: active-minutes
filter parameter: active_minutes
Typ rekordu: Interwał
Rozdzielczość pamięci masowej: 1 minuta

Zgodne urządzenia

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Aktywne minuty w strefie
dataType: active-zone-minutes
filter parameter: active_zone_minutes
Typ rekordu: Interwał
Rozdzielczość pamięci masowej: 1 minuta

Zgodne urządzenia

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Poziom aktywności
dataType: activity-level
filter parameter: activity_level
Typ rekordu: Interwał
lista, uzgodnij .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Altitude
dataType: altitude
filter parameter: altitude
Typ rekordu: Interwał
Rozdzielczość pamięci masowej: 1 minuta
list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Glukoza we krwi
dataType: blood-glucose
filter parameter: blood_glucose
Typ rekordu: Próbka
list, get, reconcile, rollup, dailyRollup .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Tkanka tłuszczowa
dataType: body-fat
filter parameter: body_fat
Typ rekordu: Próbka

Zgodne urządzenia

list, get, reconcile, rollup, dailyRollup, create, update, batchDelete .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Kalorie w strefie tętna
dataType: calories-in-heart-rate-zone
filter parameter: calories_in_heart_rate_zone
Typ rekordu: Interwał
Rozdzielczość pamięci masowej: 1 minuta
rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Dzienna zmienność rytmu serca
dataType: daily-heart-rate-variability
filter parameter: daily_heart_rate_variability
Typ rekordu: dzienny

Zgodne urządzenia

lista, uzgodnij .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Dzienne strefy tętna
dataType: daily-heart-rate-zones
filter parameter: daily_heart_rate_zones
Typ rekordu: dzienny
lista, uzgodnij .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Dzienne nasycenie tlenem
dataType: daily-oxygen-saturation
filter parameter: daily_oxygen_saturation
Typ rekordu: dzienny

Zgodne urządzenia

lista, uzgadnianie .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Dzienna częstość oddychania
dataType: daily-respiratory-rate
filter parameter: daily_respiratory_rate
Typ rekordu: dzienny

Zgodne urządzenia

lista, uzgodnij .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Dzienne tętno spoczynkowe
dataType: daily-resting-heart-rate
filter parameter: daily_resting_heart_rate
Typ rekordu: dzienny

Zgodne urządzenia

lista, uzgodnij .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Dzienne odchylenia temperatury podczas snu
dataType: daily-sleep-temperature-derivations
filter parameter: daily_sleep_temperature_derivations
Typ rekordu: dzienny

Zgodne urządzenia

lista, uzgadnianie .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Dystans
dataType: distance
filter parameter: distance
Typ rekordu: Interwał
Rozdzielczość pamięci masowej: 1 minuta

Zgodne urządzenia

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Ćwiczenia
dataType: exercise
filter parameter: exercise
Typ rekordu: sesja

Zgodne urządzenia

list, get, reconcile, create, update, batchDelete .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Piętra
dataType: floors
filter parameter: floors
Typ rekordu: Interwał
Rozdzielczość pamięci masowej: 1 minuta

Zgodne urządzenia

reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Tętno
dataType: heart-rate
filter parameter: heart_rate
Typ rekordu: Próbka
Rozdzielczość pamięci: 1 sekunda

Zgodne urządzenia

list, reconcile, rollup, dailyRollup .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Zmienność rytmu serca
dataType: heart-rate-variability
filter parameter: heart_rate_variability
Typ rekordu: Próbka

Zgodne urządzenia

lista, uzgodnij .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Wysokość
dataType: height
filter parameter: height
Typ rekordu: Próbka
list, get, reconcile, create, update, batchDelete .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Zapis nawodnienia
dataType: hydration-log
filter parameter: hydration_log
Typ rekordu: sesja
list, get, reconcile, rollup, dailyRollup, create, update, batchDelete .nutrition.readonly
.nutrition.writeonly
Dziennik odżywiania
dataType: nutrition-log
filter parameter: nutrition_log
Typ rekordu: sesja
list, get, reconcile, rollup, dailyRollup, create, update, batchDelete .nutrition.readonly
.nutrition.writeonly
Informacje o częstości oddychania podczas snu
dataType: respiratory-rate-sleep-summary
filter parameter: respiratory_rate_sleep_summary
Typ rekordu: Próbka

Zgodne urządzenia

lista, uzgodnij .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Pułap tlenowy podczas biegu
dataType: run-vo2-max
filter parameter: run_vo2_max
Typ rekordu: Próbka

Zgodne urządzenia

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Okres braku aktywności
dataType: sedentary-period
filter parameter: sedentary_period
Typ rekordu: Interwał

Zgodne urządzenia

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Sen
dataType: sleep
filter parameter: sleep
Typ rekordu: sesja

Zgodne urządzenia

list, get, reconcile, create, update, batchDelete .sleep.readonly
.sleep.writeonly
Kroki
dataType: steps
filter parameter: steps
Typ rekordu: Interwał
Rozdzielczość pamięci masowej: 1 minuta

Zgodne urządzenia

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Czas w strefie tętna
dataType: time-in-heart-rate-zone
filter parameter: time_in_heart_rate_zone
Typ rekordu: Interwał
Rozdzielczość pamięci masowej: 1 minuta
list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Waga
dataType: weight
filter parameter: weight
Typ rekordu: Próbka

Zgodne urządzenia

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

Zakresy powiadomień

Powiadomienia dotyczące tych typów danych są wysyłane tylko wtedy, gdy użytkownik wyraził zgodę na jeden z odpowiednich zakresów:

  • Aktywność, która obejmuje aktywne minuty, kroki, wysokość, dystans i piętra:
    • https://www.googleapis.com/auth/googlehealth.activity_and_fitness.readonly
    • https://www.googleapis.com/auth/googlehealth.activity_and_fitness.writeonly
  • Wskaźniki zdrowotne, które obejmują typ danych dotyczących wagi:
    • https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.readonly
    • https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.writeonly
  • Sen, który obejmuje typ danych o śnie:
    • https://www.googleapis.com/auth/googlehealth.sleep.readonly
    • https://www.googleapis.com/auth/googlehealth.sleep.writeonly

Konta usługi używane przez Uprawnienia

Podczas konfigurowania subskrybentów interfejsu Google Health API zalecamy używanie konta usługi uprawnień, choć nie jest to wymagane. Konta usługi zapewniają lepsze bezpieczeństwo zadań aplikacji w porównaniu ze standardowymi kontami użytkowników dzięki tym funkcjom:

  • Zautomatyzowane dane logowania o krótkim okresie ważności: po dołączeniu do środowiska wykonawczego Google Cloud (takiego jak Compute Engine, Cloud Run lub Google Kubernetes Engine) konta usługi automatycznie uzyskują i rotują bezpieczne dane logowania o krótkim okresie ważności. Eliminuje to ryzyko związane z zarządzaniem trwałymi kluczami statycznymi i ich przechowywaniem.
  • Zasada jak najmniejszych uprawnień: konta usługi zapewniają dedykowane tożsamości dla zadań. Możesz przyznać im tylko określone uprawnienia potrzebne do zarządzania punktami końcowymi subskrybentów, unikając szerszego dostępu do zasobów Google Cloud.
  • Niezależność cyklu życia: konta usługi działają niezależnie od kont poszczególnych użytkowników, dzięki czemu zmiany personelu nie wpływają na długoterminową stabilność uwierzytelniania.

skonfigurować konto usługi,

Aby skonfigurować aplikację subskrybenta do uwierzytelniania za pomocą konta usługi:

  1. Utwórz konto usługi: w konsoli Google Cloud otwórz stronę Administracja w projekcie, aby utworzyć nowe konto usługi zarządzane przez użytkownika.
  2. Przypisz niezbędne role uprawnień: przypisz do konta usługi odpowiednie role wymagane do zarządzania subskrybentami w projekcie Google Cloud.
  3. Dołącz konto usługi do zadania: skonfiguruj środowisko, w którym działa logika subskrybenta, tak aby działało jako nowe konto usługi. Dzięki temu kod aplikacji (np. biblioteki klienta interfejsu API Google) może automatycznie wykrywać i używać krótkotrwałych danych logowania konta usługi podczas wywoływania interfejsu API REST projects.subscribers.

Role CPE

Aby zarządzać subskrybentami lub subskrypcjami interfejsu Google Health API, musisz przyznać odpowiednią rolę kontu usługi, które jest personifikowane i wykonuje wywołania interfejsu API. W zależności od wymaganego poziomu dostępu przypisz jedną z tych ról:

  • Odczyt interfejsu Google Health API
  • Edytujący interfejs Google Health API
  • Administrator interfejsu Google Health API

Więcej informacji o rolach i uprawnieniach Google Health API

Zarządzanie subskrybentami

Zanim zaczniesz otrzymywać powiadomienia, musisz zarejestrować subskrybenta, który reprezentuje punkt końcowy powiadomień aplikacji. Subskrybentami możesz zarządzać za pomocą interfejsu API typu REST dostępnego na stronie projects.subscribers.

Punkt końcowy subskrybenta musi używać protokołu HTTPS (TLSv1.2+) i być dostępny publicznie. Podczas tworzenia i aktualizowania subskrybenta interfejs Google Health API przeprowadza weryfikację, aby upewnić się, że jesteś właścicielem identyfikatora URI punktu końcowego. Jeśli weryfikacja się nie powiedzie, operacje tworzenia i aktualizowania subskrybenta zakończą się niepowodzeniem z błędem FailedPreconditionException.

Tworzenie subskrybenta

Aby zarejestrować nowego subskrybenta w projekcie, użyj punktu końcowego create. Musisz podać te dane:

  • project-id: numer projektu, w którym utworzono konto usługi webhooka.
  • subscriberId: unikalny identyfikator subskrybenta, który podajesz. Ten identyfikator musi mieć od 4 do 36 znaków i pasować do wyrażenia regularnego ([a-z]([a-z0-9-]{2,34}[a-z0-9])).
  • endpointUri: docelowy adres URL powiadomień webhooka.
  • subscriberConfigs: typy danych, o których chcesz otrzymywać powiadomienia, oraz zasady dotyczące subskrypcji dla każdego z nich.
  • endpointAuthorization: mechanizm autoryzacji punktu końcowego. Musi on zawierać podany przez Ciebie element secret. Wartość secret jest wysyłana w nagłówku Authorization z każdą wiadomością z powiadomieniem. Możesz użyć tego tokena, aby sprawdzić, czy żądania przychodzące pochodzą z interfejsu Google Health API. Możesz na przykład ustawić secret na Bearer R4nd0m5tr1ng123 w przypadku uwierzytelniania przy użyciu tokena okaziciela lub na Basic dXNlcjpwYXNzd29yZA== w przypadku uwierzytelniania podstawowego.

W subscriberConfigs musisz ustawić subscriptionCreatePolicy dla każdego typu danych. Ustaw wartość AUTOMATIC, aby używać automatycznych subskrypcji, lub MANUAL, jeśli chcesz samodzielnie zarządzać subskrypcjami użytkowników. Więcej informacji o każdej z tych opcji znajdziesz w sekcjach Subskrypcje automatyczne i Subskrypcje ręczne.

Żądanie

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

Odpowiedź

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

Subskrybenci listy

Użyj punktu końcowego list, aby pobrać wszystkich subskrybentów zarejestrowanych w projekcie.

Żądanie

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

Odpowiedź

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

Aktualizowanie subskrybenta

Aby zaktualizować subskrybenta w projekcie, użyj punktu końcowego patch. Pola, które można zaktualizować, to endpointUri, subscriberConfigs i endpointAuthorization.

Pola aktualizuje się, podając parametr zapytania updateMask i treść żądania. Parametr updateMask musi zawierać rozdzieloną przecinkami listę nazw pól, które chcesz zaktualizować, przy czym nazwy pól muszą być zapisane w formacie camelCase (np. endpointUri). Treść żądania musi zawierać częściowy obiekt Subscriber z nowymi wartościami pól, które chcesz zaktualizować. Aktualizowane są tylko pola określone w parametrze updateMask. Jeśli w treści żądania podasz pola, których nie ma w updateMask, zostaną one zignorowane.

Jeśli zaktualizujesz endpointUri lub endpointAuthorization, zostanie przeprowadzona weryfikacja punktów końcowych. Więcej informacji znajdziesz w artykule Weryfikacja punktów końcowych.

Pamiętaj, że podczas aktualizowania subscriberConfigs następuje pełne zastąpienie, a nie scalenie. Jeśli subscriberConfigs jest uwzględniony w updateMask, wszystkie zapisane konfiguracje tego subskrybenta zostaną zastąpione listą podaną w treści żądania. Aby dodać lub usunąć konfigurację, musisz podać pełny zestaw konfiguracji. Jeśli aktualizujesz inne pola i chcesz zachować bieżące konfiguracje, pomiń subscriberConfigs w updateMask.

Żądanie

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

Odpowiedź

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

Usuwanie subskrybenta

Aby usunąć subskrybenta z projektu, użyj punktu końcowego delete. Po usunięciu subskrybent nie będzie już otrzymywać powiadomień.

Żądanie

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

Odpowiedź

Jeśli usunięcie się powiedzie, zwracana jest pusta treść odpowiedzi ze stanem HTTP `200 OK`.
{}

Weryfikacja punktów końcowych

Aby zapewnić bezpieczeństwo i niezawodność dostarczania powiadomień, interfejs Google Health API przeprowadza obowiązkową weryfikację dwuetapową za każdym razem, gdy tworzysz subskrybenta lub aktualizujesz konfigurację jego punktu końcowego (endpointUri lub endpointAuthorization). Ten proces jest wykonywany synchronicznie podczas wywołania interfejsu API. Usługa wysyła 2 automatyczne żądania POST do identyfikatora URI punktu końcowego, używając klienta użytkownika Google-Health-API-Webhooks z treścią JSON {"type": "verification"}.

  • Autoryzowane powitanie: pierwsze żądanie jest wysyłane ze skonfigurowanym nagłówkiem Authorization. Serwer musi odpowiedzieć stanem 200 OK lub 201 Created.
  • Unauthorized Challenge: drugie żądanie jest wysyłane bez danych logowania. Serwer musi odpowiadać kodem stanu 401 Unauthorized lub 403 Forbidden.

To uzgadnianie potwierdza, że punkt końcowy jest aktywny i prawidłowo egzekwuje zabezpieczenia. Jeśli którykolwiek z tych kroków się nie powiedzie, żądanie do interfejsu API zakończy się błędem FAILED_PRECONDITION. Dopiero po pomyślnym zakończeniu tego procesu subskrybent zostanie zapisany i aktywowany, aby otrzymywać powiadomienia o danych dotyczących zdrowia.

Rotacja kluczy

Jeśli chcesz dokonać rotacji kluczy dla endpointAuthorization, wykonaj te czynności:

  1. Skonfiguruj punkt końcowy tak, aby akceptował zarówno stare, jak i nowe wartości endpointAuthorization.
  2. Zaktualizuj konfigurację subskrybenta, podając nową wartość endpointAuthorization, za pomocą żądania patch z ?updateMask=endpointAuthorization.
  3. Po potwierdzeniu, że krok 2 został wykonany, skonfiguruj punkt końcowy tak, aby akceptował tylko nową wartość endpointAuthorization.

Subskrypcje użytkownika

Interfejs Google Health API pomaga efektywnie zarządzać subskrypcjami użytkowników, dzięki czemu nie trzeba ich rejestrować ręcznie podczas wprowadzania.

Subskrypcje automatyczne

Zalecamy korzystanie z subskrypcji automatycznych. Aby włączyć tę funkcję, ustaw subscriptionCreatePolicy na AUTOMATIC w subscriberConfigs dla określonych typów danych. Typy danych dataTypes określone w zasadach AUTOMATIC są takie same jak typy danych, dla których interfejs Google Health API wysyła powiadomienia, pod warunkiem że użytkownik wyraził zgodę na te typy danych.

Gdy użytkownik wyrazi zgodę aplikacji na zakresy odpowiadające typom danych z zasadą AUTOMATIC, interfejs Google Health API automatycznie śledzi i wysyła powiadomienia dotyczące typów danych, które wynikają z przecięcia danych użytkowników, którzy wyrazili zgodę, i typów danych z automatycznej konfiguracji subskrybenta dla tego użytkownika. Powiadomienia są wysyłane do Twojego punktu końcowego, gdy użytkownik wygeneruje nowe dane tych typów. Działa to w przypadku użytkowników, którzy wyrażą zgodę przed utworzeniem subskrybenta lub po jego utworzeniu. Powiadomienia nie są uzupełniane w przypadku danych wygenerowanych przed utworzeniem subskrybenta.

Jeśli użytkownik wycofa zgodę, powiadomienia dotyczące odpowiednich typów danych zostaną zatrzymane. Subskrypcjami automatycznymi zarządza Google i nie można ich wyświetlać ani usuwać pojedynczo. Są usuwane tylko wtedy, gdy zostanie usunięty nadrzędny subskrybent.

Subskrypcje ręczne

Jeśli subskrybent jest skonfigurowany z ustawieniem MANUAL subscription_create_policy w przypadku określonych typów danych, musisz jawnie tworzyć subskrypcje dla każdego użytkownika i nimi zarządzać. Subskrypcja łączy konkretnego użytkownika z punktem końcowym subskrybenta w przypadku określonego zestawu typów danych. Deweloperzy mogą używać określonych interfejsów API, aby:

  • Tworzenie (ręczne) subskrypcji na healthUserId – tworzy nową subskrypcję dla konkretnego użytkownika. Ta metoda wymaga, aby subskrybent miał ustawioną wartość SubscriptionCreatePolicy na MANUAL dla żądanych typów danych.
  • Aktualizowanie subskrypcji (ręczne) – aktualizuje typy danych w przypadku istniejącej subskrypcji użytkownika.
  • Usuń subskrypcję (ręcznie) – usuwa subskrypcję określonego użytkownika. Po usunięciu punkt końcowy subskrybenta nie będzie już otrzymywać powiadomień dotyczących tego użytkownika w przypadku powiązanych typów danych.
  • Wyświetlanie listy subskrypcji (ręcznie) – wyświetla listę wszystkich aktywnych subskrypcji danego subskrybenta. Wyniki możesz filtrować według użytkownika lub typu danych.

Powiadomienia

Gdy dane użytkownika dotyczące subskrybowanego typu danych ulegną zmianie, interfejs Google Health API wysyła żądanie HTTPS POST na adres URL punktu końcowego subskrybenta.

Format powiadomienia

Ładunek powiadomienia zawiera szczegółowe informacje o zmianie danych, w tym identyfikator użytkownika, typ danych i przedziały czasu, których możesz użyć do wysłania zapytania o zaktualizowane dane. Gdy występuje wiele aktualizacji lub są one agregowane, powiadomienia są grupowane i dostarczane jako tablica JSON obiektów powiadomień.

Gdy zostanie wywołane powiadomienie webhooka, interfejs Google Health API wysyła żądanie HTTPS POST do punktu końcowego subskrybenta w tym formacie:

Upsert (dane dotyczące serii czasowej)

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

Usuwanie (dane serii czasowej)

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

Usuwanie (dane umożliwiające identyfikację)

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

Partia (wielu użytkowników i typów danych)

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

Pole operation wskazuje typ zmiany, która wywołała powiadomienie:

  • UPSERT: wysyłane w przypadku dodania lub modyfikacji danych.
  • DELETE: wysyłane, gdy użytkownik wyraźnie usunie dane (np. usunie dataPoints za pomocą aplikacji Fitbit lub wykona usunięcie zbiorcze za pomocą interfejsu API). Powiadomienia webhook nie są wysyłane w przypadku zdarzeń usunięcia na poziomie systemu, takich jak cofnięcie uprawnień aplikacji przez użytkownika lub usunięcie konta Fitbit.

Zalecamy, aby logika obsługi powiadomień była idempotentna, zwłaszcza w przypadku operacji UPSERT, ponieważ ponowne próby mogą spowodować wysłanie zduplikowanych powiadomień.

Pole clientProvidedSubscriptionName to unikalny identyfikator:

  • W przypadku subskrypcji z zasadą MANUAL to pole zawiera trwałą, podaną przez dewelopera nazwę subskrypcji określoną podczas jej tworzenia. Zapewnia to stabilny identyfikator do zarządzania subskrypcjami ręcznymi.
  • W przypadku subskrypcji utworzonych za pomocą zasady AUTOMATIC interfejs Google Health API automatycznie generuje i przypisuje identyfikator w formacie: auto-<project-number>-<subscriber-id>-<WEBHOOK_DATA_TYPE_TOKEN>, gdzie:
    • <project-number> to numer projektu Google Cloud dewelopera (np. 362978535920).
    • <subscriber-id> to identyfikator rejestracji subskrybenta lub nazwa subskrybenta (np. google-health-sync).
    • <WEBHOOK_DATA_TYPE_TOKEN> to token reprezentujący konkretny typ danych. Wszystkie możliwe tokeny znajdziesz w słowniku tokenów typów danych webhooka.

Uwzględnienie clientProvidedSubscriptionName zarówno w przypadku zasad ręcznych, jak i automatycznych zapewnia spójny format danych powiadomień we wszystkich typach subskrypcji.

Mapowanie identyfikatorów użytkowników

healthUserId to identyfikator interfejsu Google Health API użytkownika, którego dane uległy zmianie. Jeśli Twoja aplikacja obsługuje wielu użytkowników, możesz otrzymywać powiadomienia dotyczące każdego użytkownika, który wyraził zgodę na korzystanie z aplikacji. Gdy otrzymasz powiadomienie, użyj healthUserId, aby określić, dane którego użytkownika uległy zmianie. Dzięki temu możesz użyć jego danych logowania OAuth, aby wysłać zapytanie o jego dane.

Aby zmapować dane logowania OAuth użytkownika na jego healthUserId, użyj punktu końcowego getIdentity. Wywołaj ten punkt końcowy z danymi logowania użytkownika podczas jego wprowadzania, aby pobrać jego healthUserId i zapisać to mapowanie. To mapowanie nie zmienia się z czasem, więc może być przechowywane w pamięci podręcznej bezterminowo. Przykład znajdziesz w sekcji Pobieranie identyfikatora użytkownika. Dzięki temu możesz wybrać prawidłowe dane logowania użytkownika podczas wysyłania zapytania o dane na podstawie healthUserId w powiadomieniu.

Przedziały czasu

Tablica intervals zawiera co najmniej 1 obiekt reprezentujący przedziały czasu zmodyfikowanych danych:

Pole Typ Wymagane Opis
physicalTimeInterval object Nie Przedział czasu UTC zmodyfikowanych danych (format YYYY-MM-DDTHH:MM:SSZ). Wypełniane w przypadku danych z ciągu dnia; pomijane w przypadku podsumowań dziennych i aktywności całodniowych.
civilDateTimeInterval object Tak Data i godzina zmodyfikowanych danych według kalendarza cywilnego (lokalnego), bez przesunięć UTC lub strefy czasowej.
civilIso8601TimeInterval object Tak Przedział czasu cywilnego (lokalnego) reprezentowany jako ciąg znaków ISO 8601 (format YYYY-MM-DDTHH:MM:SS) bez przesunięć czasu UTC lub strefy czasowej.

W przypadku aktywności całodniowych i dziennych danych zbiorczych pole physicalTimeInterval jest celowo pomijane w ładunku powiadomienia. Dzieje się tak, ponieważ wydarzenia całodniowe są grupowane według nominalnej daty cywilnej, a czas trwania może być niedokładny, gdy przekracza strefy czasowe. Podczas przetwarzania wydarzeń całodniowych najlepiej jest korzystać z pól civilDateTimeInterval lub civilIso8601TimeInterval. Szczegółowe informacje o tym, jak wysyłać zapytania o te punkty danych za pomocą filtrów czasu cywilnego, znajdziesz w sekcji Filtrowanie danych według czasu rozpoczęcia interwału.

Poniżej znajdziesz przykładowy ładunek powiadomienia dotyczący całodniowej aktywności, w którym pominięto pole 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"
        }
      }
    ]
  }
}

Słownik tokenów typów danych webhooka

Gdy używasz zasady AUTOMATIC, wygenerowany clientProvidedSubscriptionName zawiera jeden z tych tokenów sufiksu WEBHOOK_DATA_TYPE_* pisanych wielkimi literami, w zależności od typu danych:

Token typu danych webhooka Odpowiedni typ danych
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 (zdarzenie systemowe)
WEBHOOK_DATA_TYPE_USER_REVOKED_ACCESS user-revoked-access (zdarzenie systemowe)

Agregacja aktualizacji

Aby zminimalizować ruch w sieci, interfejs Google Health API grupuje wiadomości. System jest skonfigurowany tak, aby wysyłać maksymalnie 99 wiadomości na partię, które są automatycznie przekazywane, gdy tylko staną się dostępne.

Gdy wiadomości są grupowane, ładunek jest dostarczany jako tablica JSON zawierająca wiele obiektów powiadomień. Pojedyncza partia może zawierać aktualizacje dotyczące wielu użytkowników i wielu subskrybowanych typów danych. Przykładowy ładunek znajdziesz w sekcji Format powiadomienia w przykładzie Batch (multiple users and data types) (Zestaw – wielu użytkowników i wiele typów danych).

Podejmowanie działania w związku z powiadomieniem

Serwer musi zweryfikować nagłówek Authorization, obsłużyć synchroniczne uzgadnianie weryfikacji punktu końcowego i natychmiast odpowiedzieć na powiadomienia o zmianie danych za pomocą kodu stanu HTTP 204 No Content. Aby uniknąć przekroczenia limitu czasu, po wysłaniu odpowiedzi przetwórz ładunek powiadomienia asynchronicznie. Jeśli interfejs Google Health API otrzyma inny kod stanu lub upłynie limit czasu żądania, spróbuje ponownie wysłać powiadomienie później.

Przykład w 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).
        }
    });
});

Aby pobrać zaktualizowane dane dla każdego powiadomienia, wywołaj punkt końcowy reconcile z filtrem dla zakresu czasu powiadomienia. Spowoduje to automatyczne usunięcie duplikatów nakładających się interwałów z połączonych urządzeń. Szczegółowe informacje i przykłady znajdziesz w artykule Uzyskiwanie uzgodnionego widoku danych interwałowych.

Istnieją 3 typowe strategie aktualizowania bazy danych po otrzymaniu powiadomienia o zmianie danych:

  • Kolejkowanie (zalecane): umieszczaj przychodzące powiadomienia w kolejce wiadomości i przetwarzaj je poza kolejką na podstawie zasobów systemowych.
  • Natychmiastowe pobieranie: skonfiguruj backend tak, aby natychmiast wywoływał interfejs Google Health API w celu pobrania zaktualizowanych danych i zapisania ich w bazie danych.
  • Leniwe aktualizacje: oznacz rekordy lokalnej bazy danych użytkownika jako nieaktualne i pobierz zaktualizowane dane z interfejsu Google Health API dopiero wtedy, gdy użytkownik ponownie odwiedzi Twoją aplikację.

Strategia kolejkowania umożliwia szybkie przetwarzanie danych, a jednocześnie zapewnia odporność na zalew powiadomień (np. gdy przerwa w działaniu usługi powoduje, że wszystkie zaległe powiadomienia są dostarczane jednocześnie). Możesz zdecydować się na przetwarzanie tych kolejek w długich odstępach czasu (np. co 3 godziny), aby oszczędzać zasoby, ale wadą tego rozwiązania jest to, że użytkownicy zwykle oczekują szybkiej aktualizacji danych.

Sprawdzone metody

Aby zapewnić niezawodne i efektywne przetwarzanie aktualizacji danych, postępuj zgodnie z tymi sprawdzonymi metodami:

  • Używaj interfejsu Subscription API: używaj interfejsu Subscription API, aby otrzymywać powiadomienia, gdy użytkownik ma nowe dane do pobrania, zamiast odpytywać punkty końcowe.
  • Kolejkuj i przetwarzaj powiadomienia osobno: gdy otrzymujesz powiadomienia o subskrypcji, umieszczaj je w kolejce i przetwarzaj osobno na podstawie zasobów systemu. Punkt końcowy webhooka powinien być lekki, aby natychmiast potwierdzać żądanie.

weryfikowaniem podpisu,

Aby zapewnić autentyczność powiadomień z webhooków, nieprzetworzone dane JSON każdego wychodzącego powiadomienia z webhooka są podpisywane kluczem prywatnym za pomocą PublicKeySign Tink, co zapewnia podpis zakodowany w Base64 w nagłówku HTTP GOOGLE-HEALTH-API-SIGNATURE w żądaniu. Te klucze podpisu są automatycznie zmieniane co 30 dni, a odpowiadający im oficjalny zestaw kluczy publicznych jest rozpowszechniany jako plik JSON pod stałym adresem URL https://www.gstatic.com/googlehealthapi/webhooks/webhooks_public_keyset.json.

Jak zweryfikować podpis

Korzystanie z Tink (zalecane): deweloperzy mogą zweryfikować podpis za pomocą elementu podstawowego PublicKeyVerify biblioteki Tink. Pobierz zestaw kluczy publicznych ze stałego adresu URL, utwórz instancję elementu podstawowego PublicKeyVerify z zestawem kluczy i zweryfikuj zdekodowany nagłówek GOOGLE-HEALTH-API-SIGNATURE pod kątem nieprzetworzonego ładunku JSON webhooka.

Weryfikacja ręczna (bez Tink): jeśli deweloperzy nie chcą używać Tink, mogą ręcznie zweryfikować podpis, wykonując te czynności:

  1. Dekoduje nagłówek GOOGLE-HEALTH-API-SIGNATURE w formacie Base64, aby oddzielić 5-bajtowy prefiks Tink (zawierający 1-bajtowy prefiks wersji i 4-bajtowy identyfikator klucza) od rzeczywistego podpisu zakodowanego w formacie DER.
  2. Pobierz zestaw kluczy JSON z https://www.gstatic.com/googlehealthapi/webhooks/webhooks_public_keyset.json.
  3. Znajdź klucz pasujący do przeanalizowanego identyfikatora keyId i zdekoduj w formacie Base64 jego pole wartości, które zawiera zserializowany bufor protokołu EcdsaPublicKey.
  4. Wyodrębnij z tych danych binarnych współrzędne x i y w formacie big endian (tagi Protobuf 3 i 4).
  5. Utwórz instancję standardowego klucza publicznego ECDSA P-256 we wbudowanej bibliotece kryptograficznej, używając wyodrębnionych współrzędnych x i y.
  6. Sprawdź nieprzetworzony ładunek JSON webhooka pod kątem wyodrębnionego podpisu DER za pomocą algorytmu SHA-256.

Stan subskrypcji i jej przywracanie

Jeśli punkt końcowy aplikacji subskrybującej stanie się niedostępny lub zwróci kod stanu błędu (inny niż 204), interfejs Google Health API będzie przechowywać oczekujące powiadomienia przez maksymalnie 7 dni i ponawiać próby dostarczenia z wzrastającym czasem do ponowienia.

Gdy Twój punkt końcowy będzie znów online i odpowie kodem 204, interfejs API automatycznie dostarczy zaległe wiadomości. Powiadomienia starsze niż 7 dni są odrzucane i nie można ich odzyskać.

Typowe błędy

Kod błędu Wiadomość Opis Rekomendacja
400 Nieprawidłowe żądanie Nieprawidłowy numer projektu w nazwie zasobu Podczas usuwania lub aktualizowania subskrybenta za pomocą identyfikatora projektu Google Cloud w adresie URL żądania zamiast numeru projektu. Dotyczy to subskrypcji webhooków korzystających z punktu końcowego projects.subscribers. W adresie URL żądania użyj numeru projektu Google Cloud, a nie jego identyfikatora.
403 Dostęp zabroniony Wywołujący nie ma uprawnień podczas tworzenia lub wyświetlania subskrybentów, gdy w adresie URL żądania używasz identyfikatora projektu Google Cloud zamiast numeru projektu. Dotyczy to subskrypcji webhooków korzystających z punktu końcowego projects.subscribers. W adresie URL żądania użyj numeru projektu Google Cloud, a nie jego identyfikatora.