Assinaturas de webhook

A API Google Health permite que seu aplicativo receba notificações em tempo real quando os dados de saúde de um usuário mudam. Em vez de consultar as mudanças, seu servidor recebe um webhook de solicitação HTTPS POST assim que os dados estão disponíveis na API Google Health.

Tipos de dados compatíveis

As notificações de webhook são compatíveis com os seguintes tipos de dados:

Tabela: tipos de dados da API Google Health com suporte a webhook
Tipo de dado Operações
disponíveis
Escopo
Minutos de atividade
dataType:active-minutes
parâmetro de filtro:active_minutes
Tipo de registro: Intervalo
Resolução de armazenamento : 1 minuto

Dispositivos compatíveis

  • 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
Minutos na faixa ativa
dataType:active-zone-minutes
parâmetro de filtro:active_zone_minutes
Tipo de registro: Intervalo
Resolução de armazenamento : 1 minuto

Dispositivos compatíveis

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Nível de atividade
dataType:activity-level
parâmetro de filtro:activity_level
Tipo de registro: Intervalo
list, reconcile .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Altitude
dataType: altitude
parâmetro de filtro: altitude
Tipo de registro: Intervalo
Resolução de armazenamento : 1 minuto
list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Glicose no sangue
dataType:blood-glucose
parâmetro de filtro:blood_glucose
Tipo de registro : amostra
list, get, reconcile, rollup, dailyRollup .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Gordura corporal
dataType:body-fat
parâmetro de filtro:body_fat
Tipo de registro : amostra

Dispositivos compatíveis

list, get, reconcile, rollup, dailyRollup, create, update, batchDelete .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Calorias na faixa de frequência cardíaca
dataType:calories-in-heart-rate-zone
parâmetro de filtro:calories_in_heart_rate_zone
Tipo de registro: Intervalo
Resolução de armazenamento : 1 minuto
rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Variabilidade da frequência cardíaca diária
dataType:daily-heart-rate-variability
parâmetro de filtro:daily_heart_rate_variability
Tipo de registro : diário

Dispositivos compatíveis

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Zonas de frequência cardíaca diárias
dataType:daily-heart-rate-zones
parâmetro de filtro:daily_heart_rate_zones
Tipo de registro : diário
list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Saturação de oxigênio diária
dataType:daily-oxygen-saturation
parâmetro de filtro:daily_oxygen_saturation
Tipo de registro : diário

Dispositivos compatíveis

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Ritmo respiratório diário
dataType:daily-respiratory-rate
parâmetro de filtro:daily_respiratory_rate
Tipo de registro : diário

Dispositivos compatíveis

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Frequência cardíaca diária em repouso
dataType:daily-resting-heart-rate
parâmetro de filtro:daily_resting_heart_rate
Tipo de registro : diário

Dispositivos compatíveis

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Variações diárias da temperatura durante o sono
dataType:daily-sleep-temperature-derivations
parâmetro de filtro:daily_sleep_temperature_derivations
Tipo de registro : diário

Dispositivos compatíveis

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Distância
dataType: distance
parâmetro de filtro: distance
Tipo de registro: Intervalo
Resolução de armazenamento : 1 minuto

Dispositivos compatíveis

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Exercício
dataType: exercise
parâmetro de filtro: exercise
Tipo de registro : sessão

Dispositivos compatíveis

list, get, reconcile, create, update, batchDelete .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Andares
dataType: floors
parâmetro de filtro: floors
Tipo de registro: Intervalo
Resolução de armazenamento : 1 minuto
reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Frequência cardíaca
dataType:heart-rate
parâmetro de filtro:heart_rate
Tipo de registro : amostra
Resolução de armazenamento : 1 segundo

Dispositivos compatíveis

list, reconcile, rollup, dailyRollup .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Variabilidade da frequência cardíaca
dataType:heart-rate-variability
parâmetro de filtro:heart_rate_variability
Tipo de registro : amostra

Dispositivos compatíveis

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Altura
dataType: height
parâmetro de filtro: height
Tipo de registro : amostra
list, get, reconcile, create, update, batchDelete .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Registro de hidratação
dataType:hydration-log
parâmetro de filtro:hydration_log
Tipo de registro : sessão
list, get, reconcile, rollup, dailyRollup, create, update, batchDelete .nutrition.readonly
.nutrition.writeonly
Registro de alimentação
dataType:nutrition-log
parâmetro de filtro:nutrition_log
Tipo de registro : sessão

Dispositivos compatíveis

list, get, reconcile, rollup, dailyRollup, create, update, batchDelete .nutrition.readonly
.nutrition.writeonly
Resumo do sono: ritmo respiratório
dataType:respiratory-rate-sleep-summary
parâmetro de filtro:respiratory_rate_sleep_summary
Tipo de registro : amostra

Dispositivos compatíveis

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
VO₂ máx. da corrida
dataType:run-vo2-max
parâmetro de filtro:run_vo2_max
Tipo de registro : amostra

Dispositivos compatíveis

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Período sedentário
dataType:sedentary-period
parâmetro de filtro:sedentary_period
Tipo de registro: Intervalo

Dispositivos compatíveis

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Sono
dataType: sleep
parâmetro de filtro: sleep
Tipo de registro : sessão

Dispositivos compatíveis

list, get, reconcile, create, update, batchDelete .sleep.readonly
.sleep.writeonly
Etapas
dataType: steps
parâmetro de filtro: steps
Tipo de registro: Intervalo
Resolução de armazenamento : 1 minuto

Dispositivos compatíveis

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Tempo na faixa de frequência cardíaca
dataType:time-in-heart-rate-zone
parâmetro de filtro:time_in_heart_rate_zone
Tipo de registro: Intervalo
Resolução de armazenamento : 1 minuto
list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Peso
dataType: weight
parâmetro de filtro: weight
Tipo de registro : amostra

Dispositivos compatíveis

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

Escopos de notificações

As notificações são enviadas para esses tipos de dados somente quando um usuário concede consentimento para um dos escopos correspondentes:

  • Atividade, que abrange os tipos de dados de minutos de atividade, passos, altitude, distância e andares:
    • https://www.googleapis.com/auth/googlehealth.activity_and_fitness.readonly
    • https://www.googleapis.com/auth/googlehealth.activity_and_fitness.writeonly
  • Métricas de saúde, que abrange o tipo de dados de peso:
    • https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.readonly
    • https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.writeonly
  • Sono, que abrange o tipo de dados de sono:
    • https://www.googleapis.com/auth/googlehealth.sleep.readonly
    • https://www.googleapis.com/auth/googlehealth.sleep.writeonly

Contas de serviço do IAM

Embora não seja obrigatório, recomendamos usar uma conta de serviço do IAM ao configurar assinantes para a API Google Health. As contas de serviço oferecem melhor segurança para cargas de trabalho de aplicativos em comparação com as contas de usuário padrão com os seguintes recursos:

  • Credenciais automatizadas de curta duração:quando anexadas a um ambiente de execução do Google Cloud (como Compute Engine, Cloud Run ou Google Kubernetes Engine), as contas de serviço recebem e alternam automaticamente credenciais seguras de curta duração. Isso elimina os riscos de gerenciar e armazenar chaves estáticas persistentes.
  • Princípio de privilégio mínimo:as contas de serviço fornecem identidades dedicadas para cargas de trabalho. É possível conceder a eles apenas as permissões específicas necessárias para gerenciar endpoints de assinantes, evitando um acesso mais amplo aos recursos do Google Cloud.
  • Independência do ciclo de vida:as contas de serviço operam de forma independente da conta de qualquer usuário individual, garantindo que as mudanças de pessoal não afetem a estabilidade da autenticação a longo prazo.

Configurar uma conta de serviço

Para configurar o aplicativo assinante para autenticação usando uma conta de serviço:

  1. Crie uma conta de serviço:no console do Google Cloud, acesse a página IAM e administrador do projeto para criar uma nova conta de serviço gerenciada pelo usuário.
  2. Conceda os papéis do IAM necessários:atribua à conta de serviço os papéis adequados para gerenciar assinantes no seu projeto do Google Cloud.
  3. Anexe a conta de serviço à carga de trabalho:configure o ambiente que hospeda a lógica do assinante para ser executado como a nova conta de serviço. Isso permite que o código do aplicativo (como as bibliotecas de cliente da API do Google) detecte e use automaticamente as credenciais de curta duração da conta de serviço ao chamar a API REST projects.subscribers.

Papéis de CPE

Para gerenciar assinantes ou assinaturas da API Google Health, é preciso conceder o papel apropriado à conta de serviço personificada que faz as chamadas de API. Dependendo do nível de acesso necessário, atribua um dos seguintes papéis:

  • Leitura da API Google Health
  • Editor da API Google Health
  • Administrador da API Google Health

Saiba mais sobre os papéis e as permissões do IAM da API Google Health.

Gerenciar inscritos

Antes de receber Notificações, é preciso registrar um assinante, que representa o Notification endpoints do aplicativo. É possível gerenciar assinantes usando a API REST disponível em projects.subscribers.

O endpoint do assinante precisa usar HTTPS (TLSv1.2+) e ser acessível publicamente. Durante a criação e as atualizações de assinantes, a API Google Health realiza um desafio de verificação para garantir que você seja o proprietário do URI do endpoint. Se a verificação falhar, as operações de criação e atualização do assinante falharão com um FailedPreconditionException.

Criar um assinante

Para registrar um novo assinante no projeto, use o endpoint create. Você precisa informar:

  • project-id: o número do projeto em que a conta de serviço do webhook foi criada.
  • subscriberId: um identificador exclusivo que você fornece para o assinante. Esse ID precisa ter entre 4 e 36 caracteres e corresponder à expressão regular ([a-z]([a-z0-9-]{2,34}[a-z0-9])).
  • endpointUri: o URL de destino das notificações de webhook.
  • subscriberConfigs: os tipos de dados para os quais você quer receber notificações e a política de assinatura de cada um.
  • endpointAuthorization: o mecanismo de autorização do endpoint. Ele precisa conter um secret fornecido por você. O valor de secret é enviado no cabeçalho Authorization com cada mensagem de notificação. Use esse token para verificar se as solicitações recebidas são da API Google Health. Por exemplo, é possível definir secret como Bearer R4nd0m5tr1ng123 para autenticação de portador ou Basic dXNlcjpwYXNzd29yZA== para autenticação básica.

Em subscriberConfigs, é necessário definir subscriptionCreatePolicy para cada tipo de dados. Defina como AUTOMATIC para usar assinaturas automáticas ou MANUAL se você pretende gerenciar as assinaturas de usuários por conta própria. Consulte assinaturas automáticas e assinaturas manuais para mais detalhes sobre cada opção.

Solicitação

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

Resposta

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

Listar assinantes

Use o endpoint list para recuperar todos os assinantes registrados no seu projeto.

Solicitação

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

Resposta

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

Atualizar um assinante

Use o endpoint patch para atualizar um assinante no seu projeto. Os campos que podem ser atualizados são endpointUri, subscriberConfigs e endpointAuthorization.

Para atualizar campos, forneça um parâmetro de consulta updateMask e um corpo de solicitação. O updateMask precisa conter uma lista separada por vírgulas de nomes de campos que você quer atualizar, usando letras concatenadas para nomes de campos (por exemplo, endpointUri). O corpo da solicitação precisa conter um objeto Subscriber parcial com os novos valores para os campos que você quer atualizar. Somente os campos especificados em updateMask são atualizados. Se você fornecer campos no corpo da solicitação que não estão em updateMask, eles serão ignorados.

Se você atualizar endpointUri ou endpointAuthorization, a verificação de endpoints será realizada. Veja mais detalhes em Verificação de endpoints.

Ao atualizar subscriberConfigs, observe que é uma substituição completa, não uma mesclagem. Se subscriberConfigs estiver incluído em updateMask, todas as configurações armazenadas para esse assinante serão substituídas pela lista fornecida no corpo da solicitação. Para adicionar ou remover uma configuração, é necessário fornecer o conjunto completo de configurações. Se você estiver atualizando outros campos e quiser manter as configurações atuais, omita subscriberConfigs de updateMask.

Solicitação

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

Resposta

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

Excluir um assinante

Use o endpoint delete para remover um assinante do seu projeto. Depois de excluído, o assinante não vai mais receber notificações.

Solicitação

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

Resposta

Um corpo de resposta vazio com o status HTTP "200 OK" será retornado se a exclusão for bem-sucedida.
{}

Verificação de endpoints

Para garantir a segurança e a confiabilidade da entrega de notificações, a API Google Health realiza um handshake de verificação em duas etapas obrigatório sempre que você cria um assinante ou atualiza a configuração do endpoint dele (endpointUri ou endpointAuthorization). Esse processo é realizado de forma síncrona durante a chamada de API. O serviço envia duas solicitações POST automatizadas para o URI do endpoint, usando o User-Agent Google-Health-API-Webhooks, com o corpo JSON {"type": "verification"}.

  • Handshake autorizado: a primeira solicitação é enviada com o cabeçalho Authorization configurado. Seu servidor precisa responder com um status 200 OK ou 201 Created.
  • Desafio não autorizado: a segunda solicitação é enviada sem credenciais. Seu servidor precisa responder com um status 401 Unauthorized ou 403 Forbidden.

Esse handshake confirma que o endpoint está ativo e aplicando a segurança corretamente. Se uma das etapas falhar, a solicitação de API vai falhar com um erro FAILED_PRECONDITION. Somente após esse handshake ser bem-sucedido, o assinante é salvo e ativado para receber notificações de dados de saúde.

Rotação de chaves

Se você precisar alternar as chaves de endpointAuthorization, siga estas etapas:

  1. Configure o endpoint para aceitar os valores antigos e novos de endpointAuthorization.
  2. Atualize a configuração do assinante com o novo valor de endpointAuthorization usando uma solicitação patch com ?updateMask=endpointAuthorization.
  3. Configure o endpoint para aceitar apenas o novo valor endpointAuthorization depois de confirmar que a etapa 2 foi concluída.

Assinaturas do usuário

A API Google Health ajuda a gerenciar as assinaturas de usuários de forma eficiente, reduzindo a necessidade de registro manual durante a integração.

Assinaturas automáticas

Recomendamos o uso de assinaturas automáticas. Para ativar esse recurso, defina subscriptionCreatePolicy como AUTOMATIC no subscriberConfigs para os tipos de dados específicos. Os dataTypes que você especifica com uma política AUTOMATIC são os mesmos tipos de dados para os quais a API Google Health envia notificações, desde que o consentimento do usuário também seja concedido para esses tipos de dados.

Quando um usuário concede consentimento ao aplicativo para escopos que correspondem a tipos de dados com uma política AUTOMATIC, a API Google Health rastreia e envia automaticamente notificações para os tipos de dados resultantes da interseção entre os tipos de dados consentidos pelo usuário e os tipos de dados de configuração de assinante automático para esse usuário. As notificações são enviadas ao seu endpoint sempre que o usuário gera novos dados para esses tipos. Isso funciona para usuários que concedem consentimento antes ou depois de você criar o assinante. As notificações não são preenchidas para dados gerados antes da criação do assinante.

Se um usuário revogar o consentimento, as notificações dos tipos de dados correspondentes vão parar. As assinaturas automáticas são gerenciadas pelo Google e não podem ser listadas ou excluídas individualmente. Elas são removidas apenas quando o assinante pai é excluído.

Assinaturas manuais

Se o assinante estiver configurado com um subscription_create_policy MANUAL para tipos de dados específicos, você precisará criar e gerenciar explicitamente as assinaturas de cada usuário. Uma assinatura vincula um usuário específico ao seu endpoint de assinante para um conjunto definido de tipos de dados. Os desenvolvedores podem usar APIs específicas para:

  • Criar assinaturas (manuais) por healthUserId: cria uma nova assinatura para um usuário específico. Esse método exige que o assinante tenha um SubscriptionCreatePolicy definido como MANUAL para os tipos de dados solicitados.
  • Atualizar assinatura (manual): atualiza os tipos de dados de uma assinatura de usuário existente.
  • Excluir assinatura (manual): exclui uma assinatura de usuário específica. Depois de excluído, o endpoint do assinante não vai mais receber notificações para esse usuário sobre os tipos de dados associados.
  • Listar assinaturas (manual): lista todas as assinaturas ativas de um determinado assinante. É possível filtrar os resultados por usuário ou tipo de dados.

Notificações

Quando os dados de um usuário mudam para um tipo de dado inscrito, a API Google Health envia uma solicitação POST HTTPS para o URL do endpoint do inscrito.

Formato da notificação

O payload da notificação contém detalhes sobre a mudança de dados, incluindo o ID do usuário, o tipo de dados e os intervalos de tempo, que podem ser usados para consultar os dados atualizados. Quando várias atualizações ocorrem ou são agregadas, as notificações são agrupadas em lote e entregues como uma matriz JSON de objetos de notificação.

Quando uma notificação de webhook é acionada, a API Google Health envia uma solicitação HTTPS POST ao endpoint do assinante com o seguinte formato:

Upsert (dados de série temporal)

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

Excluir (dados de série temporal)

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

Excluir (dados identificáveis)

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

Lote (vários usuários e tipos de dados)

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

O campo operation indica o tipo de mudança que acionou a notificação:

  • UPSERT: enviado para qualquer adição ou modificação de dados.
  • DELETE: enviado quando um usuário exclui dados explicitamente (por exemplo, excluindo dataPoints usando o app Fitbit ou realizando uma exclusão em lote usando a API). As notificações de webhook não são enviadas para eventos de exclusão no nível do sistema, como um usuário revogando permissões do aplicativo ou excluindo a conta do Fitbit.

Recomendamos que a lógica de processamento de notificações seja idempotente, especialmente para operações UPSERT, já que as novas tentativas podem causar o envio de notificações duplicadas.

O campo clientProvidedSubscriptionName é um identificador exclusivo:

  • Para assinaturas com uma política MANUAL, esse campo contém o nome persistente da assinatura fornecido pelo desenvolvedor e especificado quando a assinatura é criada. Isso fornece um ID estável para gerenciar assinaturas manuais.
  • Para assinaturas criadas com uma política AUTOMATIC, a API Google Health gera e atribui automaticamente um identificador no formato: auto-<project-number>-<subscriber-id>-<WEBHOOK_DATA_TYPE_TOKEN>, em que:
    • <project-number> é o número do projeto na nuvem do Google Cloud do desenvolvedor (por exemplo, 362978535920).
    • <subscriber-id> é o ID de registro / nome do assinante (por exemplo, google-health-sync).
    • <WEBHOOK_DATA_TYPE_TOKEN> é o token que representa o tipo de dados específico. Consulte Vocabulário de tokens de tipo de dados de webhook para todos os tokens possíveis.

Incluir clientProvidedSubscriptionName para políticas manuais e automáticas garante um formato de payload de notificação consistente em todos os tipos de assinatura.

Mapeamento de ID do usuário

O healthUserId é um identificador da API Google Health para o usuário cujos dados foram alterados. Se o aplicativo for compatível com vários usuários, você poderá receber notificações de qualquer usuário que tenha concedido consentimento ao aplicativo. Ao receber uma notificação, use healthUserId para identificar de qual usuário os dados foram alterados. Assim, você pode usar as credenciais OAuth para consultar os dados dele.

Para mapear as credenciais OAuth de um usuário para o healthUserId, use o endpoint getIdentity. Chame esse endpoint com as credenciais de um usuário durante a integração para recuperar o healthUserId dele e armazenar esse mapeamento. Esse mapeamento não muda com o tempo, então pode ser armazenado em cache indefinidamente. Para ver um exemplo, consulte Receber o ID do usuário. Isso permite que você selecione as credenciais de usuário corretas ao consultar dados com base no healthUserId em uma notificação.

Intervalos de tempo

A matriz intervals contém um ou mais objetos que representam os intervalos de tempo dos dados modificados:

Campo Tipo Obrigatório Descrição
physicalTimeInterval object Não O intervalo de tempo físico UTC dos dados modificados (formato YYYY-MM-DDTHH:MM:SSZ). Preenchido para métricas intradiárias; omitido para resumos diários e atividades de dia inteiro.
civilDateTimeInterval object Sim A data e o intervalo de tempo do calendário civil (local) dos dados modificados, sem compensações de UTC ou fuso horário.
civilIso8601TimeInterval object Sim O intervalo de tempo civil (local) representado como uma string ISO 8601 (formato YYYY-MM-DDTHH:MM:SS) sem deslocamentos de UTC ou fuso horário.

Para atividades de dia inteiro e agregados diários, o campo physicalTimeInterval é omitido intencionalmente do payload de notificação. Isso ocorre porque os eventos de dia inteiro são agrupados pela data civil nominal, e as durações de tempo físico podem ser imprecisas ao cruzar fusos horários. Ao processar eventos de dia inteiro, use os campos civilDateTimeInterval ou civilIso8601TimeInterval como prática recomendada. Para saber como consultar esses pontos de dados usando filtros de horário civil, consulte Filtrar dados por um horário de início civil de intervalo.

Veja a seguir um exemplo de payload de notificação para uma atividade de dia inteiro em que o campo physicalTimeInterval é omitido:

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

Vocabulário de tokens de tipo de dados de webhook

Ao usar uma política AUTOMATIC, o clientProvidedSubscriptionName gerado contém um dos seguintes tokens de sufixo WEBHOOK_DATA_TYPE_* em maiúsculas, dependendo do tipo de dados:

Token de tipo de dados do webhook Tipo de dados correspondente
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 (evento do sistema)
WEBHOOK_DATA_TYPE_USER_REVOKED_ACCESS user-revoked-access (evento do sistema)

Agregação de atualizações

Para minimizar o tráfego de rede, a API Google Health agrupa as mensagens em lotes. O sistema está configurado para enviar no máximo 99 mensagens por lote, que são enviadas automaticamente à medida que ficam disponíveis.

Quando as mensagens são agrupadas em lote, o payload é entregue como uma matriz JSON que contém vários objetos de notificação. Um único lote pode incluir atualizações para vários usuários em vários tipos de dados inscritos. Para ver um exemplo de payload, consulte o exemplo Lote (vários usuários e tipos de dados) na seção "Formato de notificação".

Responder a uma notificação

O servidor precisa validar o cabeçalho Authorization, processar o handshake síncrono de verificação de endpoint e responder imediatamente às notificações de mudança de dados com um código de status HTTP 204 No Content. Para evitar tempos limite, processe o payload da notificação de forma assíncrona depois de enviar a resposta. Se a API Google Health receber qualquer outro código de status ou se o tempo limite da solicitação expirar, ela tentará enviar a notificação novamente mais tarde.

Exemplo de 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).
        }
    });
});

Para buscar os dados atualizados de cada intervalo notificado, chame o endpoint reconcile com um filtro para o período do intervalo. Isso elimina automaticamente a duplicação de intervalos sobrepostos de dispositivos conectados. Para detalhes e exemplos, consulte Receber uma visualização reconciliada de dados de intervalo.

Há três estratégias comuns para atualizar o banco de dados depois de receber uma notificação de mudança de dados:

  • Enfileiramento (recomendado): coloque as notificações recebidas em uma fila de mensagens e processe-as fora da fila com base nos recursos do sistema.
  • Recuperação imediata: direcione seu back-end para chamar imediatamente a API Google Health para buscar os dados atualizados e confirmá-los no banco de dados.
  • Atualizações lentas: marque os registros do banco de dados local do usuário como sujos e busque os dados atualizados da API Google Health somente quando o usuário acessar o aplicativo novamente.

A estratégia de fila permite processar dados quase em tempo real, além de ser resiliente a uma enxurrada de notificações (por exemplo, se uma interrupção de serviço fizer com que um backlog de notificações seja entregue de uma só vez). Embora seja possível processar essas filas em uma programação de lote de intervalo longo (por exemplo, a cada 3 horas) para economizar recursos, a desvantagem é que os usuários normalmente esperam que os dados sejam atualizados quase em tempo real.

Práticas recomendadas

Siga estas práticas recomendadas para garantir o processamento confiável e eficiente das atualizações de dados:

  • Use a API Subscription: use a API Subscription para receber notificações quando um usuário tiver novos dados para download, em vez de consultar endpoints.
  • Colocar em fila e processar notificações separadamente: ao receber notificações de assinatura, coloque-as em uma fila e processe-as separadamente com base nos recursos do seu sistema. Mantenha o endpoint do webhook leve confirmando a solicitação imediatamente.

Verificação de assinatura

Para garantir a autenticidade das notificações de webhooks, o payload JSON bruto de cada notificação de webhook de saída é assinado com uma chave privada usando o PublicKeySign do Tink, fornecendo a assinatura codificada em Base64 no cabeçalho HTTP GOOGLE-HEALTH-API-SIGNATURE na solicitação. Essas chaves de assinatura são alternadas automaticamente a cada 30 dias, e o conjunto de chaves públicas oficial correspondente é distribuído como um arquivo JSON no URL permanente https://www.gstatic.com/googlehealthapi/webhooks/webhooks_public_keyset.json.

Como verificar a assinatura

Usando o Tink (recomendado): os desenvolvedores podem verificar a assinatura usando a primitiva PublicKeyVerify do Tink. Busque o conjunto de chaves públicas no URL permanente, instancie o primitivo PublicKeyVerify com o conjunto de chaves e verifique o cabeçalho GOOGLE-HEALTH-API-SIGNATURE decodificado em relação ao payload JSON bruto do webhook.

Verificação manual (sem o Tink): se os desenvolvedores optarem por não usar o Tink, eles poderão verificar a assinatura manualmente seguindo estas etapas:

  1. Decodifica em Base64 o cabeçalho GOOGLE-HEALTH-API-SIGNATURE para separar o prefixo do Tink de 5 bytes (que contém um prefixo de versão de 1 byte e um keyId de 4 bytes) da assinatura real codificada em DER.
  2. Busque o conjunto de chaves JSON em https://www.gstatic.com/googlehealthapi/webhooks/webhooks_public_keyset.json.
  3. Localize a chave correspondente ao keyId analisado e decodifique em Base64 o campo de valor, que contém um buffer de protocolo EcdsaPublicKey serializado.
  4. Extraia as coordenadas x e y big-endian (tags 3 e 4 do Protobuf) desse payload binário.
  5. Instancie uma chave pública ECDSA P-256 padrão em uma biblioteca de criptografia integrada usando as coordenadas x e y extraídas.
  6. Verifique o payload JSON bruto do webhook em relação à assinatura DER extraída usando o algoritmo SHA-256.

Status e recuperação de assinantes

Se o endpoint do assinante ficar indisponível ou retornar um código de status de erro (qualquer coisa diferente de 204), a API Google Health vai armazenar as notificações pendentes por até sete dias e tentar novamente o envio com espera exponencial.

Quando o endpoint estiver on-line novamente e responder com 204, a API vai entregar automaticamente as mensagens armazenadas. As notificações com mais de 7 dias são descartadas e não podem ser recuperadas.

Erros comuns

Código do erro Mensagem Descrição Recomendação
400 Solicitação inválida Número de projeto inválido no nome do recurso Ao excluir ou atualizar um assinante usando o ID do projeto do Google Cloud no URL da solicitação em vez do número do projeto. Isso se aplica às assinaturas de webhook que usam o endpoint projects.subscribers. Use o número do projeto do Google Cloud no URL da solicitação, não o ID do projeto.
403 Proibido O autor da chamada não tem permissão Ao criar ou listar assinantes usando o ID do projeto do Google Cloud no URL da solicitação em vez do número do projeto. Isso se aplica às assinaturas de webhook que usam o endpoint projects.subscribers. Use o número do projeto do Google Cloud no URL da solicitação, não o ID do projeto.