La API de Google Health permite que tu aplicación reciba notificaciones en tiempo real cuando cambian los datos de salud de un usuario. En lugar de sondear los cambios, tu servidor recibe un webhook de solicitud POST de HTTPS en cuanto los datos están disponibles en la API de Google Health.
Tipos de datos admitidos
Las notificaciones de webhook son compatibles con los siguientes tipos de datos:
| Tipo de datos | Operaciones de disponibles |
Alcance |
|---|---|---|
|
Minutos de actividad
dataType:
active-minutesfilter parameter: active_minutes
Tipo de registro: Intervalo
Resolución de almacenamiento: 1 minuto
Dispositivos compatibles
|
list, reconcile, rollup, dailyRollup | .activity_and_fitness.readonly.activity_and_fitness.writeonly |
|
Minutos en zona activa
dataType:
active-zone-minutesfilter parameter: active_zone_minutes
Tipo de registro: Intervalo
Resolución de almacenamiento: 1 minuto
Dispositivos compatibles
|
list, reconcile, rollup, dailyRollup | .activity_and_fitness.readonly.activity_and_fitness.writeonly |
|
Nivel de actividad
dataType:
activity-levelfilter parameter: activity_level
Tipo de registro: Intervalo
|
list, reconcile | .activity_and_fitness.readonly.activity_and_fitness.writeonly |
|
Altitud
dataType:
altitudefilter parameter: altitude
Tipo de registro: Intervalo
Resolución de almacenamiento: 1 minuto
|
list, reconcile, rollup, dailyRollup | .activity_and_fitness.readonly.activity_and_fitness.writeonly |
|
Glucemia
dataType:
blood-glucosefilter parameter: blood_glucose
Tipo de registro: Muestra
|
list, get, reconcile, rollup, dailyRollup | .health_metrics_and_measurements.readonly.health_metrics_and_measurements.writeonly |
|
Grasa corporal
dataType:
body-fatfilter parameter: body_fat
Tipo de registro: Muestra
Dispositivos compatibles
|
list, get, reconcile, rollup, dailyRollup, create, update, batchDelete | .health_metrics_and_measurements.readonly.health_metrics_and_measurements.writeonly |
|
Calorías en la zona de frecuencia cardíaca
dataType:
calories-in-heart-rate-zonefilter parameter: calories_in_heart_rate_zone
Tipo de registro: Intervalo
Resolución de almacenamiento: 1 minuto
|
rollup, dailyRollup | .activity_and_fitness.readonly.activity_and_fitness.writeonly |
|
Variabilidad de la frecuencia cardíaca diaria
dataType:
daily-heart-rate-variabilityfilter parameter: daily_heart_rate_variability
Tipo de registro: Diario
Dispositivos compatibles
|
list, reconcile | .health_metrics_and_measurements.readonly.health_metrics_and_measurements.writeonly |
|
Zonas de frecuencia cardíaca diarias
dataType:
daily-heart-rate-zonesfilter parameter: daily_heart_rate_zones
Tipo de registro: Diario
|
list, reconcile | .health_metrics_and_measurements.readonly.health_metrics_and_measurements.writeonly |
|
Saturación de oxígeno diaria
dataType:
daily-oxygen-saturationfilter parameter: daily_oxygen_saturation
Tipo de registro: Diario
Dispositivos compatibles
|
list, reconcile | .health_metrics_and_measurements.readonly.health_metrics_and_measurements.writeonly |
|
Frecuencia respiratoria diaria
dataType:
daily-respiratory-ratefilter parameter: daily_respiratory_rate
Tipo de registro: Diario
Dispositivos compatibles
|
list, reconcile | .health_metrics_and_measurements.readonly.health_metrics_and_measurements.writeonly |
|
Frecuencia cardíaca en reposo diaria
dataType:
daily-resting-heart-ratefilter parameter: daily_resting_heart_rate
Tipo de registro: Diario
Dispositivos compatibles
|
list, reconcile | .health_metrics_and_measurements.readonly.health_metrics_and_measurements.writeonly |
|
Desviaciones diarias de la temperatura durante el sueño
dataType:
daily-sleep-temperature-derivationsfilter parameter: daily_sleep_temperature_derivations
Tipo de registro: Diario
Dispositivos compatibles
|
list, reconcile | .health_metrics_and_measurements.readonly.health_metrics_and_measurements.writeonly |
|
Distancia
dataType:
distancefilter parameter: distance
Tipo de registro: Intervalo
Resolución de almacenamiento: 1 minuto
Dispositivos compatibles
|
list, reconcile, rollup, dailyRollup | .activity_and_fitness.readonly.activity_and_fitness.writeonly |
|
Ejercicio
dataType:
exercisefilter parameter: exercise
Tipo de registro: Sesión
Dispositivos compatibles
|
list, get, reconcile, create, update, batchDelete | .activity_and_fitness.readonly.activity_and_fitness.writeonly |
|
Pisos
dataType:
floorsfilter parameter: floors
Tipo de registro: Intervalo
Resolución de almacenamiento: 1 minuto
|
reconcile, rollup, dailyRollup | .activity_and_fitness.readonly.activity_and_fitness.writeonly |
|
Frecuencia cardíaca
dataType:
heart-ratefilter parameter: heart_rate
Tipo de registro: Muestra
Resolución de almacenamiento: 1 segundo
Dispositivos compatibles
|
list, reconcile, rollup, dailyRollup | .health_metrics_and_measurements.readonly.health_metrics_and_measurements.writeonly |
|
Variabilidad de la frecuencia cardíaca
dataType:
heart-rate-variabilityfilter parameter: heart_rate_variability
Tipo de registro: Muestra
Dispositivos compatibles
|
list, reconcile | .health_metrics_and_measurements.readonly.health_metrics_and_measurements.writeonly |
|
Altura
|
list, get, reconcile, create, update, batchDelete | .health_metrics_and_measurements.readonly.health_metrics_and_measurements.writeonly |
|
Registro de hidratación
dataType:
hydration-logfilter parameter: hydration_log
Tipo de registro: Sesión
|
list, get, reconcile, rollup, dailyRollup, create, update, batchDelete | .nutrition.readonly.nutrition.writeonly |
|
Registro de nutrición
dataType:
nutrition-logfilter parameter: nutrition_log
Tipo de registro: Sesión
Dispositivos compatibles
|
list, get, reconcile, rollup, dailyRollup, create, update, batchDelete | .nutrition.readonly.nutrition.writeonly |
|
Resumen de sueño de la frecuencia respiratoria
dataType:
respiratory-rate-sleep-summaryfilter parameter: respiratory_rate_sleep_summary
Tipo de registro: Muestra
Dispositivos compatibles
|
list, reconcile | .health_metrics_and_measurements.readonly.health_metrics_and_measurements.writeonly |
|
VO2 máx. en carreras
dataType:
run-vo2-maxfilter parameter: run_vo2_max
Tipo de registro: Muestra
Dispositivos compatibles
|
list, reconcile, rollup, dailyRollup | .activity_and_fitness.readonly.activity_and_fitness.writeonly |
|
Período sedentario
dataType:
sedentary-periodfilter parameter: sedentary_period
Tipo de registro: Intervalo
Dispositivos compatibles
|
list, reconcile, rollup, dailyRollup | .activity_and_fitness.readonly.activity_and_fitness.writeonly |
|
Sueño
dataType:
sleepfilter parameter: sleep
Tipo de registro: Sesión
Dispositivos compatibles
|
list, get, reconcile, create, update, batchDelete | .sleep.readonly.sleep.writeonly |
|
Pasos
dataType:
stepsfilter parameter: steps
Tipo de registro: Intervalo
Resolución de almacenamiento: 1 minuto
Dispositivos compatibles
|
list, reconcile, rollup, dailyRollup | .activity_and_fitness.readonly.activity_and_fitness.writeonly |
|
Tiempo en la zona de frecuencia cardíaca
dataType:
time-in-heart-rate-zonefilter parameter: time_in_heart_rate_zone
Tipo de registro: Intervalo
Resolución de almacenamiento: 1 minuto
|
list, reconcile, rollup, dailyRollup | .activity_and_fitness.readonly.activity_and_fitness.writeonly |
|
Peso
dataType:
weightfilter parameter: weight
Tipo de registro: Muestra
Dispositivos compatibles
|
list, get, reconcile, rollup, dailyRollup, create, update, batchDelete | .health_metrics_and_measurements.readonly.health_metrics_and_measurements.writeonly |
Permisos de notificaciones
Las notificaciones se envían para estos tipos de datos solo cuando un usuario otorgó el consentimiento para uno de los permisos correspondientes:
- Actividad, que abarca los tipos de datos de minutos de actividad, pasos, altitud, distancia y pisos:
https://www.googleapis.com/auth/googlehealth.activity_and_fitness.readonlyhttps://www.googleapis.com/auth/googlehealth.activity_and_fitness.writeonly
- Métricas de salud, que abarca el tipo de datos de peso:
https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.readonlyhttps://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.writeonly
- Sueño, que abarca el tipo de datos de sueño:
https://www.googleapis.com/auth/googlehealth.sleep.readonlyhttps://www.googleapis.com/auth/googlehealth.sleep.writeonly
Cuentas de servicio de IAM
Aunque no es obligatorio, te recomendamos que uses una cuenta de servicio de IAM cuando configures suscriptores para la API de Google Health. Las cuentas de servicio proporcionan una mejor seguridad para las cargas de trabajo de las aplicaciones en comparación con las cuentas de usuario estándar a través de las siguientes funciones:
- Credenciales de corta duración automatizadas: Cuando se conectan a un entorno de ejecución de Google Cloud (como Compute Engine, Cloud Run o Google Kubernetes Engine), las cuentas de servicio obtienen y rotan automáticamente credenciales seguras de corta duración. Esto elimina los riesgos de administrar y almacenar claves estáticas persistentes.
- Principio de privilegio mínimo: Las cuentas de servicio proporcionan identidades dedicadas para las cargas de trabajo. Puedes otorgarles solo los permisos específicos necesarios para administrar los extremos de los suscriptores, lo que evita un acceso más amplio a tus recursos de Google Cloud.
- Independencia del ciclo de vida: Las cuentas de servicio operan de forma independiente de la cuenta de cualquier usuario individual, lo que garantiza que los cambios de personal no afecten la estabilidad de la autenticación a largo plazo.
Configurar una cuenta de servicio
Para configurar tu aplicación de suscriptor de modo que se autentique con una cuenta de servicio, sigue estos pasos:
- Crea una cuenta de servicio: En la consola de Google Cloud, navega a la página de IAM y administración de tu proyecto para crear una nueva cuenta de servicio administrada por el usuario.
- Otorga los roles de IAM necesarios: Asigna a la cuenta de servicio los roles adecuados necesarios para administrar los suscriptores en tu proyecto de Google Cloud.
- Adjunta la cuenta de servicio a tu carga de trabajo: Configura el entorno que aloja tu lógica de suscriptor para que se ejecute como la nueva cuenta de servicio.
Esto permite que el código de tu aplicación (como las bibliotecas cliente de la API de Google)
detecte y use automáticamente las credenciales de corta duración de la cuenta de servicio
cuando llame a la API de REST de
projects.subscribers.
Roles de CPE
Para administrar los suscriptores o las suscripciones de la API de Google Health, debes otorgar el rol adecuado a la cuenta de servicio que actúa en nombre de otra y que realiza las llamadas a la API. Según el nivel de acceso necesario, asigna uno de los siguientes roles:
- Lectura de la API de Google Health
- Editor de la API de Google Health
- Administrador de la API de Google Health
Obtén más información sobre los roles y permisos de IAM de la API de Google Health.
Cómo administrar los suscriptores
Antes de recibir notificaciones, debes registrar un suscriptor, que
representa el extremo de notificación de tu aplicación. Puedes administrar los suscriptores
con la API de REST disponible en
projects.subscribers.
El extremo del suscriptor debe usar HTTPS (TLSv1.2 o superior) y ser de acceso público.
Durante la creación y las actualizaciones de los suscriptores, la API de Google Health realiza un
desafío de verificación para garantizar que seas el propietario del URI del extremo. Si la verificación
falla, las operaciones de creación y actualización de suscriptores fallan con un
FailedPreconditionException.
Crea un suscriptor
Para registrar un suscriptor nuevo en tu proyecto, usa el extremo
create. Debes proporcionar lo siguiente:
project-id: Es el número del proyecto en el que se creó la cuenta de servicio del webhook.subscriberId: Es un identificador único que proporcionas para el suscriptor. Este ID debe tener entre 4 y 36 caracteres, y coincidir con la expresión regular ([a-z]([a-z0-9-]{2,34}[a-z0-9])).endpointUri: Es la URL de destino para las notificaciones de webhook.subscriberConfigs: Son los tipos de datos para los que deseas recibir notificaciones y la política de suscripción de cada uno.endpointAuthorization: Es el mecanismo de autorización para tu extremo. Debe contener unsecretque proporciones. El valor desecretse envía en el encabezadoAuthorizationcon cada mensaje de notificación. Puedes usar este token para verificar que las solicitudes entrantes provengan de la API de Google Health. Por ejemplo, puedes establecersecretenBearer R4nd0m5tr1ng123para la autenticación de portador oBasic dXNlcjpwYXNzd29yZA==para la autenticación básica.
En subscriberConfigs, debes establecer subscriptionCreatePolicy para cada tipo de datos. Establécelo en AUTOMATIC para usar suscripciones automáticas o en MANUAL si
pretendes administrar las suscripciones de los usuarios por tu cuenta. Consulta
Suscripciones automáticas y
Suscripciones manuales para obtener más detalles sobre cada opción.
Solicitud
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"
}
}Respuesta
{
"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"
}
]
}Enumera los suscriptores
Usa el extremo list
para recuperar todos los suscriptores registrados en tu proyecto.
Solicitud
GET https://health.googleapis.com/v4/projects/project-id/subscribers
Respuesta
{
"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
}Actualiza un suscriptor
Usa el extremo patch
para actualizar un suscriptor en tu proyecto. Los campos que se pueden actualizar son
endpointUri, subscriberConfigs y endpointAuthorization.
Para actualizar los campos, debes proporcionar un parámetro de consulta updateMask y un cuerpo de la solicitud. El updateMask debe contener una lista separada por comas de los nombres de los campos
que deseas actualizar, con mayúsculas mediales para los nombres de los campos
(por ejemplo, endpointUri). El cuerpo de la solicitud debe contener un objeto Subscriber
parcial con los valores nuevos de los campos que deseas actualizar. Solo se actualizan los campos especificados
en updateMask. Si proporcionas campos en el cuerpo de la solicitud que no están en updateMask, se ignoran.
Si actualizas endpointUri o endpointAuthorization, se realiza la verificación de extremos. Consulta Verificación de extremos para obtener más detalles.
Cuando actualices subscriberConfigs, ten en cuenta que es un reemplazo completo, no una
combinación. Si se incluye subscriberConfigs en updateMask, todas las configuraciones almacenadas
para ese suscriptor se reemplazan por la lista proporcionada en el cuerpo de la
solicitud. Para agregar o quitar una configuración, debes proporcionar el conjunto completo de
parámetros de configuración. Si actualizas otros campos y quieres mantener tus configuraciones
actuales, omite subscriberConfigs de updateMask.
Solicitud
PATCH https://health.googleapis.com/v4/projects/project-id/subscribers/subscriber-id?updateMask=endpointUri
{
"endpointUri": "https://myapp.com/new-webhooks/health"
}Respuesta
{
"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"
}
]
}Borra un suscriptor
Usa el extremo delete
para quitar un suscriptor de tu proyecto. Una vez que se borre, el suscriptor
ya no recibirá notificaciones.
Solicitud
DELETE https://health.googleapis.com/v4/projects/project-id/subscribers/subscriber-id
Respuesta
Si la eliminación se realiza correctamente, se devuelve un cuerpo de respuesta vacío con el estado HTTP `200 OK`.{}Verificación de extremos
Para garantizar la seguridad y confiabilidad de la entrega de notificaciones, la API de Google Health realiza un protocolo de enlace de verificación en dos pasos obligatorio cada vez que creas un suscriptor o actualizas su configuración de extremo (endpointUri o endpointAuthorization). Este proceso se realiza de forma síncrona durante la llamada a la API. El servicio envía dos solicitudes POST automatizadas al URI de tu extremo,
con el usuario-agente Google-Health-API-Webhooks y el cuerpo JSON
{"type": "verification"}.
- Protocolo de enlace autorizado: La primera solicitud se envía con el encabezado
Authorizationconfigurado. Tu servidor debe responder con un estado200 OKo201 Created. - Desafío no autorizado: La segunda solicitud se envía sin credenciales.
Tu servidor debe responder con un estado
401 Unauthorizedo403 Forbidden.
Este protocolo de enlace confirma que tu extremo está activo y aplica la seguridad de forma correcta. Si alguno de los pasos falla, la solicitud a la API falla con un
error FAILED_PRECONDITION. Solo después de que este protocolo de enlace se realice correctamente, se guardará y activará tu suscriptor para recibir notificaciones de datos de salud.
Rotación de claves
Si necesitas rotar las claves de endpointAuthorization, sigue estos pasos:
- Configura tu extremo para que acepte los valores de
endpointAuthorizationantiguos y nuevos. - Actualiza la configuración del suscriptor con el nuevo valor de
endpointAuthorizationcon una solicitudpatchcon?updateMask=endpointAuthorization. - Configura tu extremo para que acepte solo el valor
endpointAuthorizationnuevo después de confirmar que el paso 2 se realizó correctamente.
Suscripciones del usuario
La API de Google Health te ayuda a administrar las suscripciones de los usuarios de manera eficiente, lo que reduce la necesidad de realizar un registro manual durante la incorporación de los usuarios.
Suscripciones automáticas
Te recomendamos que uses las suscripciones automáticas. Para habilitar esta función, establece
subscriptionCreatePolicy en AUTOMATIC en tu subscriberConfigs para los
tipos de datos específicos. Los dataTypes que especificas con una política AUTOMATIC son
los mismos tipos de datos para los que la API de Google Health envía notificaciones,
siempre que también se otorgue el consentimiento del usuario para esos tipos de datos.
Cuando un usuario otorga el consentimiento de la aplicación para los permisos que corresponden a los tipos de datos con una política AUTOMATIC, la API de Google Health realiza un seguimiento automático y envía notificaciones para los tipos de datos que resultan de la intersección entre los tipos de datos obtenidos con consentimiento del usuario y los tipos de datos de configuración de suscriptor automático para ese usuario. Luego, se envían notificaciones a tu extremo
cada vez que ese usuario genera datos nuevos para esos tipos. Esto funciona para los usuarios que
otorgan el consentimiento antes o después de que crees el suscriptor. Las notificaciones
no se reabastecen para los datos generados antes de que se creara el suscriptor.
Si un usuario revoca el consentimiento, se detendrán las notificaciones de los tipos de datos correspondientes. Google administra las suscripciones automáticas, que no se pueden enumerar ni borrar de forma individual. Solo se quitan cuando se borra el suscriptor superior.
Suscripciones manuales
Si tu suscriptor está configurado con un subscription_create_policy MANUAL para tipos de datos específicos, debes crear y administrar suscripciones de forma explícita para cada usuario. Una suscripción vincula a un usuario específico con tu extremo de suscriptor para un conjunto definido de tipos de datos. Los desarrolladores pueden usar APIs específicas para lo siguiente:
- Crear suscripciones (manuales) por
healthUserId: Crea una suscripción nueva para un usuario específico. Este método requiere que el suscriptor tenga unSubscriptionCreatePolicyestablecido enMANUALpara los tipos de datos solicitados. - Actualizar suscripción (manual): Actualiza los tipos de datos de una suscripción de usuario existente.
- Borrar suscripción (manual): Borra una suscripción de usuario específica. Una vez borrado, tu extremo de suscriptor ya no recibirá notificaciones de este usuario para los tipos de datos asociados.
- Enumera las suscripciones (manuales): Enumera todas las suscripciones activas de un suscriptor determinado. Puedes filtrar los resultados por usuario o tipo de datos.
Notificaciones
Cuando cambian los datos de un usuario para un tipo de datos suscrito, la API de Google Health envía una solicitud POST de HTTPS a la URL del extremo del suscriptor.
Formato de las notificaciones
La carga útil de la notificación contiene detalles sobre el cambio de datos, incluidos el ID de usuario, el tipo de datos y los intervalos de tiempo, que puedes usar para consultar los datos actualizados. Cuando se producen varias actualizaciones o se agregan actualizaciones, las notificaciones se agrupan por lotes y se entregan como un array JSON de objetos de notificación.
Cuando se activa una notificación de webhook, la API de Google Health envía una solicitud POST HTTPS a tu extremo de suscriptor con el siguiente formato:
Actualizar o insertar (datos de series temporales)
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"
}
}
]
}
}Borra (datos de series temporales)
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"
}
}
]
}
}
]Borrar (datos identificables)
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"
}
}Por lotes (varios usuarios y tipos de datos)
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"
}
}
]El campo operation indica el tipo de cambio que activó la
notificación:
UPSERT: Se envía para cualquier adición o modificación de datos.DELETE: Se envía cuando un usuario borra datos de forma explícita (como borrardataPointscon la app de Fitbit o realizar una eliminación por lotes con la API). No se envían notificaciones de webhook para eventos de eliminación a nivel del sistema, como cuando un usuario revoca los permisos de la aplicación o borra su cuenta de Fitbit.
Te recomendamos que hagas que tu lógica de control de notificaciones sea idempotente, en especial para
las operaciones UPSERT, ya que los reintentos pueden provocar que se envíen notificaciones duplicadas.
El campo clientProvidedSubscriptionName es un identificador único:
- En el caso de las suscripciones con una política
MANUAL, este campo contiene el nombre de suscripción persistente proporcionado por el desarrollador que se especifica cuando se crea la suscripción. Esto proporciona un ID estable para administrar las suscripciones manuales. - Para las suscripciones creadas con una política
AUTOMATIC, la API de Google Health genera y asigna automáticamente un identificador con el siguiente formato:auto-<project-number>-<subscriber-id>-<WEBHOOK_DATA_TYPE_TOKEN>, donde:<project-number>es el número de proyecto de Google Cloud del desarrollador (por ejemplo,362978535920).<subscriber-id>es el ID de registro o el nombre del suscriptor (por ejemplo,google-health-sync).<WEBHOOK_DATA_TYPE_TOKEN>es el token que representa el tipo de datos específico. Consulta Vocabulario de tokens de tipo de datos de webhook para conocer todos los tokens posibles.
Incluir clientProvidedSubscriptionName para las políticas manuales y
automáticas garantiza un formato de carga útil de notificación coherente en todos los
tipos de suscripción.
Asignación de ID de usuario
El healthUserId es un identificador de la API de Google Health para el usuario cuyos
datos cambiaron. Si tu aplicación admite varios usuarios, puedes recibir
notificaciones de cualquier usuario que haya otorgado su consentimiento a la aplicación. Cuando
recibas una notificación, usa healthUserId para identificar qué datos del usuario
cambiaron, de modo que puedas usar sus credenciales de OAuth para consultar sus datos.
Para asignar las credenciales de OAuth de un usuario a su healthUserId, usa el
extremo getIdentity. Llama
a este extremo con las credenciales de un usuario durante su incorporación para recuperar
su healthUserId y almacenar esta asignación. Esta asignación no cambia con el tiempo, por lo que se puede almacenar en caché de forma indefinida. Para ver un ejemplo, consulta
Obtén el ID de usuario. Esto te permite seleccionar las credenciales de usuario correctas cuando consultas datos según el healthUserId en una notificación.
Intervalos
El array intervals contiene uno o más objetos que representan los intervalos de tiempo de los datos modificados:
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
physicalTimeInterval |
object |
No | Es el intervalo de tiempo físico en UTC de los datos modificados (formato YYYY-MM-DDTHH:MM:SSZ). Se completa para las métricas intradía y se omite para los resúmenes diarios y las actividades de todo el día. |
civilDateTimeInterval |
object |
Sí | Es el intervalo de fecha y hora del calendario civil (local) de los datos modificados, sin compensaciones de UTC ni de zona horaria. |
civilIso8601TimeInterval |
object |
Sí | Es el intervalo de tiempo civil (local) representado como una cadena ISO 8601 (formato YYYY-MM-DDTHH:MM:SS) sin compensaciones de UTC ni de zona horaria. |
En el caso de las actividades de todo el día y los resúmenes diarios, el campo physicalTimeInterval se omite de forma intencional de la carga útil de la notificación. Esto se debe a que
los eventos de día completo se agrupan por su fecha civil nominal, y las duraciones de tiempo físico
pueden ser imprecisas cuando se cruzan zonas horarias. Cuando proceses eventos de todo el día, debes confiar en los campos civilDateTimeInterval o
civilIso8601TimeInterval como práctica recomendada. Para obtener detalles sobre cómo
consultar estos datos con filtros de hora civil, consulta
Filtra datos por una hora de inicio civil de intervalo.
A continuación, se muestra un ejemplo de carga útil de notificación para una actividad de todo el día en la que
se omite el campo 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"
}
}
]
}
}Vocabulario de tokens de tipo de datos de webhook
Cuando usas una política AUTOMATIC, el clientProvidedSubscriptionName generado
contiene uno de los siguientes tokens de sufijo WEBHOOK_DATA_TYPE_* en mayúsculas,
según el tipo de datos:
| Token de tipo de datos de webhook | Tipo de datos correspondiente |
|---|---|
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 del sistema) |
WEBHOOK_DATA_TYPE_USER_REVOKED_ACCESS |
user-revoked-access (evento del sistema) |
Agregación de actualizaciones
Para minimizar el tráfico de red, la API de Google Health agrupa los mensajes. El sistema está configurado para enviar un máximo de 99 mensajes por lote, que se envían automáticamente a medida que están disponibles.
Cuando los mensajes se envían por lotes, la carga útil se entrega como un array JSON que contiene varios objetos de notificación. Un solo lote puede incluir actualizaciones para varios usuarios en varios tipos de datos suscritos. Para ver un ejemplo de carga útil, consulta el ejemplo de Lote (varios usuarios y tipos de datos) en la sección Formato de notificación.
Responde a una notificación
Tu servidor debe validar el encabezado Authorization, controlar el protocolo de enlace síncrono de la verificación de extremos y responder a las notificaciones de cambio de datos con un código de estado HTTP 204 No Content de inmediato. Para
evitar tiempos de espera, procesa la carga útil de la notificación de forma asíncrona después de enviar
la respuesta. Si la API de Google Health recibe cualquier otro código de estado o se agota el tiempo de espera de la solicitud, volverá a intentar enviar la notificación más tarde.
Ejemplo 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 recuperar los datos actualizados de cada intervalo notificado, llama al extremo reconcile con un filtro para el período del intervalo. Esto
anula automáticamente los intervalos superpuestos de los dispositivos conectados. Para
obtener detalles y ejemplos, consulta
Obtén una vista conciliada de los datos de intervalos.
Existen tres estrategias comunes para actualizar tu base de datos después de recibir una notificación de cambio de datos:
- Colas (recomendado): Coloca las notificaciones entrantes en una cola de mensajes y procésalas fuera de la cola según los recursos del sistema.
- Recuperación inmediata: Dirige tu backend para que llame de inmediato a la API de Google Health para recuperar los datos actualizados y confirmarlos en tu base de datos.
- Actualizaciones diferidas: Marca los registros de la base de datos local del usuario como modificados y solo recupera los datos actualizados de la API de Google Health cuando el usuario vuelva a visitar tu aplicación.
La estrategia de cola te permite procesar datos casi en tiempo real y, al mismo tiempo, mantener la resiliencia ante una gran cantidad de notificaciones (por ejemplo, si una interrupción del servicio provoca que se entregue una gran cantidad de notificaciones a la vez). Si bien puedes elegir procesar estas colas en un programa por lotes de intervalos largos (por ejemplo, cada 3 horas) para ahorrar recursos, la desventaja es que los usuarios suelen esperar ver sus datos actualizados casi en tiempo real.
Prácticas recomendadas
Sigue estas prácticas recomendadas para garantizar un manejo confiable y eficiente de las actualizaciones de datos:
- Usa la API de Subscription: Usa la API de Subscription para recibir notificaciones cuando un usuario tenga datos nuevos para descargar, en lugar de sondear extremos.
- Pon en cola y procesa las notificaciones por separado: Cuando recibas notificaciones de suscripción, colócalas en una cola y procésalas por separado según los recursos de tu sistema. Mantén el extremo del webhook ligero confirmando la solicitud de inmediato.
Verificación de firmas
Para garantizar la autenticidad de las notificaciones de webhooks, la carga útil de JSON sin procesar de
cada notificación de webhook saliente se firma con una clave privada usando PublicKeySign de
Tink,
lo que proporciona la firma codificada en Base64 en el encabezado HTTP
GOOGLE-HEALTH-API-SIGNATURE de la solicitud. Estas claves de firma se rotan automáticamente cada 30
días, y el conjunto de claves públicas oficial correspondiente se distribuye como un archivo JSON
en la URL permanente
https://www.gstatic.com/googlehealthapi/webhooks/webhooks_public_keyset.json.
Cómo verificar la firma
Con Tink (recomendado): Los desarrolladores pueden verificar la firma con la primitiva PublicKeyVerify de Tink. Recupera el conjunto de claves públicas de la URL permanente, crea una instancia de la
primitiva PublicKeyVerify con el conjunto de claves y verifica el encabezado
GOOGLE-HEALTH-API-SIGNATURE decodificado con la carga útil de JSON del webhook sin procesar.
Verificación manual (sin Tink): Si los desarrolladores deciden no usar Tink, pueden verificar la firma de forma manual siguiendo estos pasos:
- Decodifica en Base64 el encabezado
GOOGLE-HEALTH-API-SIGNATUREpara separar el prefijo de Tink de 5 bytes (que contiene un prefijo de versión de 1 byte y un keyId de 4 bytes) de la firma real codificada en DER. - Recupera el conjunto de claves JSON de https://www.gstatic.com/googlehealthapi/webhooks/webhooks_public_keyset.json.
- Ubica la clave que coincida con el keyId analizado y decodifica en Base64 su campo de valor, que contiene un búfer de protocolo EcdsaPublicKey serializado.
- Extrae las coordenadas x e y big-endian (etiquetas de Protobuf 3 y 4) de esta carga útil binaria.
- Crea una instancia de una clave pública ECDSA P-256 estándar en una biblioteca de criptografía integrada con las coordenadas x e y extraídas.
- Verifica la carga útil de JSON del webhook sin procesar con la firma DER extraída con el algoritmo SHA-256.
Estado y recuperación de los suscriptores
Si tu extremo de suscriptor deja de estar disponible o devuelve un código de estado de error
(cualquier valor que no sea 204), la API de Google Health almacena las notificaciones
pendientes durante un máximo de 7 días y vuelve a intentar la entrega con una retirada exponencial.
Una vez que tu extremo vuelva a estar en línea y responda con 204, la API
entregará automáticamente la lista de mensajes almacenados. Las notificaciones con más de
7 días de antigüedad se descartan y no se pueden recuperar.
Errores comunes
| Código de error | Mensaje | Descripción | Recomendación |
|---|---|---|---|
| 400 Bad Request | Número de proyecto no válido en el nombre del recurso | Cuando borras o actualizas un suscriptor con el ID del proyecto de Google Cloud en la URL de la solicitud en lugar del número del proyecto. Esto se aplica a las suscripciones de webhook que usan el extremo projects.subscribers. |
Usa el número de tu proyecto de Google Cloud en la URL de la solicitud, no el ID del proyecto. |
| 403 Forbidden | El emisor no tiene permiso | Cuando creas o enumeras suscriptores con el ID del proyecto de Google Cloud en la URL de la solicitud en lugar del número del proyecto. Esto se aplica a las suscripciones de webhook que usan el extremo projects.subscribers. |
Usa el número de tu proyecto de Google Cloud en la URL de la solicitud, no el ID del proyecto. |