Subskrypcje webhooków

Interfejs Google Health API umożliwia aplikacji otrzymywanie powiadomień w czasie rzeczywistym, gdy zmieniają się dane dotyczące zdrowia użytkownika. Zamiast odpytywać o zmiany, Twój serwer otrzymuje żądanie HTTPS POST webhook, gdy tylko dane są dostępne w interfejsie 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

  • 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
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, uzgadnianie .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: Sample
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: Sample

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, uzgadnianie .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, uzgadnianie .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, uzgadnianie .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, uzgadnianie .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Odchylenia temperatury podczas snu w ciągu dnia
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
reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Tętno
dataType: heart-rate
filter parameter: heart_rate
Typ rekordu: Sample
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: Sample

Zgodne urządzenia

lista, uzgadnianie .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Wysokość
dataType: height
filter parameter: height
Typ rekordu: Sample
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

Zgodne urządzenia

list, get, reconcile, rollup, dailyRollup, create, update, batchDelete .nutrition.readonly
.nutrition.writeonly
Podsumowanie dotyczące częstości oddechów podczas snu
dataType: respiratory-rate-sleep-summary
filter parameter: respiratory_rate_sleep_summary
Typ rekordu: Sample

Zgodne urządzenia

lista, uzgadnianie .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: Sample

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: Sample

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: gdy konta usługi są dołączone do środowiska wykonawczego Google Cloud (takiego jak Compute Engine, Cloud Run lub Google Kubernetes Engine), 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. Przyznaj niezbędne role Uprawnień: przypisz do konta usługi odpowiednie role wymagane do zarządzania subskrybentami w projekcie w chmurze Google.
  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 używane do wywoływania 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 w 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ć publicznie dostępny. Podczas tworzenia i aktualizowania subskrybentów 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ę 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 artykułach o subskrypcjach automatycznych i subskrypcjach ręcznych.

Żą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 Twoim 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 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 aktualizacja subscriberConfigs to pełne zastąpienie, a nie scalenie. Jeśli parametr subscriberConfigs jest uwzględniony w parametrze 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ć stanem 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 pomyślnie, skonfiguruj punkt końcowy tak, aby akceptował tylko nową wartość endpointAuthorization.

Subskrypcje użytkownika

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

Subskrypcje automatyczne

Zalecamy korzystanie z automatycznych subskrypcji. 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ę na dostęp aplikacji do zakresów odpowiadających 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 typów danych, na które użytkownik wyraził zgodę, i typów danych 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 trybem MANUAL subscription_create_policy w przypadku określonych typów danych, musisz jawnie utworzyć 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.
  • Usuwanie subskrypcji (ręczne) – usuwa określoną subskrypcję 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 do punktu końcowego subskrybenta żądanie HTTPS POST 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"
  }
}

Grupowo (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 stałą, 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 artykule Słownik tokenów typów danych webhooka.

Uwzględnienie parametru 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ć, których użytkowników dane uległy zmianie, i użyć ich danych logowania OAuth do wysłania zapytania o ich 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 uzyskiwanych w 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 wartości zagregowanych pole physicalTimeInterval jest celowo pomijane w ładunku powiadomienia. Wynika to z tego, że 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 artykule Filtrowanie danych według czasu rozpoczęcia interwału.

Poniżej znajdziesz przykładowy ładunek powiadomienia o 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 grupę, 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 Grupa (wielu użytkowników i typów danych).

Podejmowanie działania w związku z powiadomieniem

Serwer musi zweryfikować nagłówek Authorization, obsłużyć synchroniczne potwierdzenie 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 powiadomionego interwału, wywołaj punkt końcowy reconcile z filtrem dla zakresu czasu interwału. 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 sekcji 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 z kolejki 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 pobieraj zaktualizowane dane z interfejsu Google Health API tylko wtedy, gdy użytkownik ponownie odwiedzi Twoją aplikację.

Strategia kolejkowania umożliwia przetwarzanie danych w czasie zbliżonym do rzeczywistego, 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 przetwarzać te kolejki 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ą, że ich dane będą aktualizowane w czasie zbliżonym do rzeczywistego.

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 otrzymasz powiadomienia o subskrypcji, umieść 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 formacie Base64 w nagłówku HTTP GOOGLE-HEALTH-API-SIGNATURE w żądaniu. Te klucze podpisu są automatycznie rotowane 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 w 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 subskrybenta i przywracanie

Jeśli punkt końcowy subskrybenta 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 wykorzystaniem wzrastającego czasu do ponowienia.

Gdy Twój punkt końcowy znów będzie 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 w chmurze Google, a nie identyfikatora projektu.
403 Dostęp zabroniony The caller does not have permission podczas tworzenia lub wyświetlania listy 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 w chmurze Google, a nie identyfikatora projektu.