Abonnements aux webhooks

L'API Google Health permet à votre application de recevoir des notifications en temps réel lorsque les données de santé d'un utilisateur changent. Au lieu d'interroger les modifications, votre serveur reçoit une requête HTTPS POST (webhook) dès que des données sont disponibles dans l'API Google Health.

Types de données acceptés

Les notifications de webhook sont compatibles avec les types de données suivants :

Tableau : Types de données de l'API Google Health compatibles avec les webhooks
Type de données Opérations
disponibles
Champ d'application
Minutes actives
dataType : active-minutes
paramètre de filtre : active_minutes
Type d'enregistrement  : Intervalle
Résolution de stockage  : 1 minute

Appareils compatibles

  • 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
Minutes en zone active
dataType : active-zone-minutes
paramètre de filtre : active_zone_minutes
Type d'enregistrement  : Intervalle
Résolution de stockage  : 1 minute

Appareils compatibles

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Niveau d'activité
dataType : activity-level
paramètre de filtre : activity_level
Type d'enregistrement  : Intervalle
list, reconcile .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Altitude
dataType : altitude
paramètre de filtre : altitude
Type d'enregistrement  : Intervalle
Résolution de stockage  : 1 minute
list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Glycémie
dataType : blood-glucose
paramètre de filtre : blood_glucose
Type d'enregistrement  : Exemple
list, get, reconcile, rollup, dailyRollup .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Masse grasse
dataType : body-fat
paramètre de filtre : body_fat
Type d'enregistrement  : Exemple

Appareils compatibles

list, get, reconcile, rollup, dailyRollup, create, update, batchDelete .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Calories dans la zone de fréquence cardiaque
dataType : calories-in-heart-rate-zone
paramètre de filtre : calories_in_heart_rate_zone
Type d'enregistrement  : Intervalle
Résolution de stockage  : 1 minute
rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Variabilité quotidienne de la fréquence cardiaque
dataType : daily-heart-rate-variability
paramètre de filtre : daily_heart_rate_variability
Type d'enregistrement  : quotidien

Appareils compatibles

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Zones de fréquence cardiaque quotidiennes
dataType : daily-heart-rate-zones
paramètre de filtre : daily_heart_rate_zones
Type d'enregistrement  : quotidien
list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Saturation en oxygène quotidienne
dataType : daily-oxygen-saturation
paramètre de filtre : daily_oxygen_saturation
Type d'enregistrement  : quotidien

Appareils compatibles

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Fréquence respiratoire quotidienne
dataType : daily-respiratory-rate
paramètre de filtre : daily_respiratory_rate
Type d'enregistrement  : quotidien

Appareils compatibles

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Fréquence cardiaque au repos quotidienne
dataType : daily-resting-heart-rate
paramètre de filtre : daily_resting_heart_rate
Type d'enregistrement  : quotidien

Appareils compatibles

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Écarts quotidiens de température pendant le sommeil
dataType : daily-sleep-temperature-derivations
paramètre de filtre : daily_sleep_temperature_derivations
Type d'enregistrement  : quotidien

Appareils compatibles

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Distance
dataType : distance
paramètre de filtre : distance
Type d'enregistrement  : Intervalle
Résolution de stockage  : 1 minute

Appareils compatibles

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Exercice
dataType : exercise
paramètre de filtre : exercise
Type d'enregistrement  : Session

Appareils compatibles

list, get, reconcile, create, update, batchDelete .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Étages
dataType : floors
paramètre de filtre : floors
Type d'enregistrement  : Intervalle
Résolution de stockage  : 1 minute
reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Fréquence cardiaque
dataType : heart-rate
paramètre de filtre : heart_rate
Type d'enregistrement  : Exemple
Résolution de stockage  : 1 seconde

Appareils compatibles

list, reconcile, rollup, dailyRollup .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Variabilité de la fréquence cardiaque
dataType : heart-rate-variability
paramètre de filtre : heart_rate_variability
Type d'enregistrement  : Exemple

Appareils compatibles

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Hauteur
dataType : height
paramètre de filtre : height
Type d'enregistrement  : Exemple
list, get, reconcile, create, update, batchDelete .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
Journal d'hydratation
dataType : hydration-log
paramètre de filtre : hydration_log
Type d'enregistrement  : Session
list, get, reconcile, rollup, dailyRollup, create, update, batchDelete .nutrition.readonly
.nutrition.writeonly
Journal de nutrition
dataType : nutrition-log
paramètre de filtre : nutrition_log
Type d'enregistrement  : Session

Appareils compatibles

list, get, reconcile, rollup, dailyRollup, create, update, batchDelete .nutrition.readonly
.nutrition.writeonly
Résumé de la fréquence respiratoire pendant le sommeil
dataType : respiratory-rate-sleep-summary
paramètre de filtre : respiratory_rate_sleep_summary
Type d'enregistrement  : Exemple

Appareils compatibles

list, reconcile .health_metrics_and_measurements.readonly
.health_metrics_and_measurements.writeonly
VO2 max de la course
dataType : run-vo2-max
paramètre de filtre : run_vo2_max
Type d'enregistrement  : Exemple

Appareils compatibles

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Période sédentaire
dataType : sedentary-period
paramètre de filtre : sedentary_period
Type d'enregistrement  : Intervalle

Appareils compatibles

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Sommeil
dataType : sleep
paramètre de filtre : sleep
Type d'enregistrement  : Session

Appareils compatibles

list, get, reconcile, create, update, batchDelete .sleep.readonly
.sleep.writeonly
Étapes
dataType : steps
paramètre de filtre : steps
Type d'enregistrement  : Intervalle
Résolution de stockage  : 1 minute

Appareils compatibles

list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Temps dans la zone de fréquence cardiaque
dataType : time-in-heart-rate-zone
paramètre de filtre : time_in_heart_rate_zone
Type d'enregistrement  : Intervalle
Résolution de stockage  : 1 minute
list, reconcile, rollup, dailyRollup .activity_and_fitness.readonly
.activity_and_fitness.writeonly
Poids
dataType : weight
paramètre de filtre : weight
Type d'enregistrement  : Exemple

Appareils compatibles

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

Champs d'application des notifications

Les notifications ne sont envoyées pour ces types de données que lorsqu'un utilisateur a donné son consentement pour l'un des champs d'application correspondants :

  • Activité, qui couvre les types de données "minutes actives", "pas", "altitude", "distance" et "étages" :
    • https://www.googleapis.com/auth/googlehealth.activity_and_fitness.readonly
    • https://www.googleapis.com/auth/googlehealth.activity_and_fitness.writeonly
  • Indicateurs de santé, qui couvre le type de données de poids :
    • https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.readonly
    • https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.writeonly
  • Sommeil, qui couvre le type de données de sommeil :
    • https://www.googleapis.com/auth/googlehealth.sleep.readonly
    • https://www.googleapis.com/auth/googlehealth.sleep.writeonly

Comptes de service IAM

Bien que cela ne soit pas obligatoire, nous vous recommandons d'utiliser un compte de service IAM lors de la configuration des abonnés pour l'API Google Health. Les comptes de service offrent une meilleure sécurité pour les charges de travail d'application que les comptes utilisateur standards grâce aux fonctionnalités suivantes :

  • Identifiants éphémères automatisés : lorsqu'ils sont associés à un environnement d'exécution Google Cloud (tel que Compute Engine, Cloud Run ou Google Kubernetes Engine), les comptes de service obtiennent et alternent automatiquement des identifiants éphémères sécurisés. Cela élimine les risques liés à la gestion et au stockage des clés statiques persistantes.
  • Principe du moindre privilège : les comptes de service fournissent des identités dédiées aux charges de travail. Vous pouvez leur accorder uniquement les autorisations spécifiques nécessaires pour gérer les points de terminaison des abonnés, ce qui évite un accès plus large à vos ressources Google Cloud.
  • Indépendance du cycle de vie : les comptes de service fonctionnent indépendamment du compte de chaque utilisateur, ce qui garantit que les changements de personnel n'ont pas d'impact sur la stabilité de l'authentification à long terme.

Configurer un compte de service

Pour configurer votre application d'abonné afin qu'elle s'authentifie à l'aide d'un compte de service :

  1. Créer un compte de service : dans la console Google Cloud, accédez à la page "IAM et administration" de votre projet pour créer un compte de service géré par l'utilisateur.
  2. Attribuez les rôles IAM nécessaires : attribuez au compte de service les rôles appropriés requis pour gérer les abonnés sur votre projet Google Cloud.
  3. Associez le compte de service à votre charge de travail : configurez l'environnement hébergeant votre logique d'abonné pour qu'il s'exécute en tant que nouveau compte de service. Cela permet au code de votre application (comme les bibliothèques clientes des API Google) de détecter et d'utiliser automatiquement les identifiants de courte durée du compte de service lors de l'appel de l'API REST projects.subscribers.

Rôles CPE

Pour gérer les abonnés ou les abonnements à l'API Google Health, vous devez attribuer le rôle approprié au compte de service emprunté qui effectue les appels d'API. En fonction du niveau d'accès requis, attribuez l'un des rôles suivants :

  • Lecture de l'API Google Health
  • Éditeur de l'API Google Health
  • Administrateur de l'API Google Health

En savoir plus sur les rôles et autorisations IAM de l'API Google Health

Gérer vos abonnés

Avant de pouvoir recevoir des notifications, vous devez enregistrer un abonné, qui représente le point de terminaison de notification de votre application. Vous pouvez gérer les abonnés à l'aide de l'API REST disponible à l'adresse projects.subscribers.

Votre point de terminaison d'abonné doit utiliser HTTPS (TLSv1.2+) et être accessible publiquement. Lors de la création et de la mise à jour des abonnés, l'API Google Health effectue une vérification pour s'assurer que vous êtes bien propriétaire de l'URI du point de terminaison. Si la validation échoue, les opérations de création et de mise à jour de l'abonné échouent avec une erreur FailedPreconditionException.

Créer un abonné

Pour enregistrer un nouvel abonné pour votre projet, utilisez le point de terminaison create. Vous devez fournir les éléments suivants :

  • project-id : numéro du projet dans lequel le compte de service du webhook a été créé.
  • subscriberId : identifiant unique que vous fournissez pour l'abonné. Cet ID doit comporter entre 4 et 36 caractères et correspondre à l'expression régulière ([a-z]([a-z0-9-]{2,34}[a-z0-9])).
  • endpointUri : URL de destination des notifications de webhook.
  • subscriberConfigs : types de données pour lesquels vous souhaitez recevoir des notifications et règle d'abonnement pour chacun d'eux.
  • endpointAuthorization : mécanisme d'autorisation pour votre point de terminaison. Il doit contenir un secret que vous fournissez. La valeur de secret est envoyée dans l'en-tête Authorization avec chaque message de notification. Vous pouvez utiliser ce jeton pour vérifier que les requêtes entrantes proviennent de l'API Google Health. Par exemple, vous pouvez définir secret sur Bearer R4nd0m5tr1ng123 pour l'authentification du porteur ou sur Basic dXNlcjpwYXNzd29yZA== pour l'authentification de base.

Dans subscriberConfigs, vous devez définir subscriptionCreatePolicy pour chaque type de données. Définissez-le sur AUTOMATIC pour utiliser les abonnements automatiques, ou sur MANUAL si vous avez l'intention de gérer vous-même les abonnements des utilisateurs. Pour en savoir plus sur chaque option, consultez Abonnements automatiques et Abonnements manuels.

Requête

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

Réponse

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

Lister les abonnés

Utilisez le point de terminaison list pour récupérer tous les abonnés enregistrés pour votre projet.

Requête

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

Réponse

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

Mettre à jour un abonné

Utilisez le point de terminaison patch pour mettre à jour un abonné dans votre projet. Les champs pouvant être mis à jour sont endpointUri, subscriberConfigs et endpointAuthorization.

Vous mettez à jour les champs en fournissant un paramètre de requête updateMask et un corps de requête. updateMask doit contenir une liste de noms de champs à mettre à jour, séparés par une virgule, en utilisant le format camelCase pour les noms de champs (par exemple, endpointUri). Le corps de la requête doit contenir un objet Subscriber partiel avec les nouvelles valeurs des champs que vous souhaitez mettre à jour. Seuls les champs spécifiés dans updateMask sont mis à jour. Si vous fournissez des champs dans le corps de la requête qui ne figurent pas dans updateMask, ils sont ignorés.

Si vous mettez à jour endpointUri ou endpointAuthorization, la validation du point de terminaison est effectuée. Pour en savoir plus, consultez Validation des points de terminaison.

Lorsque vous mettez à jour subscriberConfigs, notez qu'il s'agit d'un remplacement complet, et non d'une fusion. Si subscriberConfigs est inclus dans updateMask, toutes les configurations stockées pour cet abonné sont écrasées par la liste fournie dans le corps de la requête. Pour ajouter ou supprimer une configuration, vous devez fournir l'ensemble complet des configurations. Si vous mettez à jour d'autres champs et que vous souhaitez conserver vos configurations actuelles, omettez subscriberConfigs de updateMask.

Requête

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

Réponse

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

Supprimer un abonné

Utilisez le point de terminaison delete pour supprimer un abonné de votre projet. Une fois supprimé, l'abonné ne recevra plus de notifications.

Requête

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

Réponse

Si la suppression aboutit, un corps de réponse vide avec l'état HTTP "200 OK" est renvoyé.
{}

Validation des points de terminaison

Pour garantir la sécurité et la fiabilité de la distribution de vos notifications, l'API Google Health effectue une validation en deux étapes obligatoire chaque fois que vous créez un abonné ou que vous mettez à jour sa configuration de point de terminaison (endpointUri ou endpointAuthorization). Ce processus est effectué de manière synchrone lors de l'appel d'API. Le service envoie deux requêtes POST automatisées à l'URI de votre point de terminaison, à l'aide de l'agent utilisateur Google-Health-API-Webhooks, avec le corps JSON {"type": "verification"}.

  • Handshake autorisé : la première requête est envoyée avec l'en-tête Authorization que vous avez configuré. Votre serveur doit répondre avec un état 200 OK ou 201 Created.
  • Demande non autorisée : la deuxième requête est envoyée sans identifiants. Votre serveur doit répondre avec un état 401 Unauthorized ou 403 Forbidden.

Ce handshake confirme que votre point de terminaison est actif et applique correctement la sécurité. Si l'une des étapes échoue, la requête API échoue avec une erreur FAILED_PRECONDITION. Ce n'est qu'une fois ce handshake réussi que votre abonné est enregistré et activé pour recevoir des notifications de données de santé.

Rotation des clés

Si vous devez effectuer une rotation des clés pour endpointAuthorization, procédez comme suit :

  1. Configurez votre point de terminaison pour qu'il accepte les anciennes et les nouvelles valeurs endpointAuthorization.
  2. Mettez à jour la configuration de l'abonné avec la nouvelle valeur endpointAuthorization à l'aide d'une requête patch avec ?updateMask=endpointAuthorization.
  3. Configurez votre point de terminaison pour qu'il n'accepte que la nouvelle valeur endpointAuthorization après avoir confirmé que l'étape 2 a réussi.

Abonnements utilisateur

L'API Google Health vous aide à gérer efficacement les abonnements des utilisateurs, ce qui réduit la nécessité d'une inscription manuelle lors de l'intégration des utilisateurs.

Abonnements automatiques

Nous vous recommandons d'utiliser les abonnements automatiques. Pour activer cette fonctionnalité, définissez subscriptionCreatePolicy sur AUTOMATIC dans votre subscriberConfigs pour les types de données spécifiques. Les dataTypes que vous spécifiez avec une règle AUTOMATIC sont les mêmes types de données pour lesquels l'API Google Health envoie des notifications, à condition que l'utilisateur ait également donné son consentement pour ces types de données.

Lorsqu'un utilisateur accorde l'autorisation d'application pour les champs d'application correspondant aux types de données avec une règle AUTOMATIC, l'API Google Health suit et envoie automatiquement des notifications pour les types de données résultant de l'intersection entre les types de données autorisés par l'utilisateur et les types de données de configuration d'abonné automatique pour cet utilisateur. Des notifications sont ensuite envoyées à votre point de terminaison chaque fois que cet utilisateur génère de nouvelles données pour ces types. Cela fonctionne pour les utilisateurs qui accordent leur consentement avant ou après la création de l'abonné. Les notifications ne sont pas remplies pour les données générées avant la création de l'abonné.

Si un utilisateur révoque son consentement, les notifications pour les types de données correspondants s'arrêtent. Les abonnements automatiques sont gérés par Google et ne peuvent pas être listés ni supprimés individuellement. Ils ne sont supprimés que lorsque l'abonné parent est supprimé.

Abonnements manuels

Si votre abonné est configuré avec un subscription_create_policy MANUAL pour des types de données spécifiques, vous devez créer et gérer explicitement des abonnements pour chaque utilisateur. Un abonnement associe un utilisateur spécifique à votre point de terminaison d'abonné pour un ensemble défini de types de données. Les développeurs peuvent utiliser des API spécifiques pour :

  • Créer des abonnements (manuels) par healthUserId : crée un abonnement pour un utilisateur spécifique. Cette méthode nécessite que l'abonné dispose d'un SubscriptionCreatePolicy défini sur MANUAL pour les types de données demandés.
  • Mettre à jour un abonnement (manuel) : met à jour les types de données d'un abonnement utilisateur existant.
  • Supprimer un abonnement (manuel) : supprime l'abonnement d'un utilisateur spécifique. Une fois supprimé, votre point de terminaison d'abonné ne recevra plus de notifications pour cet utilisateur pour les types de données associés.
  • Lister les abonnements (manuels) : liste tous les abonnements actifs d'un abonné donné. Vous pouvez filtrer les résultats par utilisateur ou par type de données.

Notifications

Lorsque les données d'un utilisateur changent pour un type de données auquel il est abonné, l'API Google Health envoie une requête HTTPS POST à l'URL du point de terminaison de l'abonné.

Format des notifications

La charge utile de la notification contient des détails sur la modification des données, y compris l'ID utilisateur, le type de données et les intervalles de temps, que vous pouvez utiliser pour interroger les données mises à jour. Lorsque plusieurs mises à jour se produisent ou que les mises à jour sont agrégées, les notifications sont regroupées et envoyées sous la forme d'un tableau JSON d'objets de notification.

Lorsqu'une notification de webhook est déclenchée, l'API Google Health envoie une requête HTTPS POST à votre point de terminaison d'abonné au format suivant :

Upsert (données de séries temporelles)

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

Supprimer (données de séries temporelles)

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

Supprimer (données identifiables)

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

Lot (plusieurs utilisateurs et types de données)

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

Le champ operation indique le type de modification qui a déclenché la notification :

  • UPSERT : envoyé pour tout ajout ou modification de données.
  • DELETE : envoyé lorsqu'un utilisateur supprime explicitement des données (par exemple, en supprimant dataPoints à l'aide de l'application Fitbit ou en effectuant une suppression par lot à l'aide de l'API). Les notifications de webhook ne sont pas envoyées pour les événements de suppression au niveau du système, par exemple lorsqu'un utilisateur révoque les autorisations d'une application ou supprime son compte Fitbit.

Nous vous recommandons de rendre idempotente votre logique de gestion des notifications, en particulier pour les opérations UPSERT, car les nouvelles tentatives peuvent entraîner l'envoi de notifications en double.

Le champ clientProvidedSubscriptionName est un identifiant unique :

  • Pour les abonnements associés à une règle MANUAL, ce champ contient le nom d'abonnement persistant fourni par le développeur, spécifié lors de la création de l'abonnement. Cela fournit un ID stable pour gérer les abonnements manuels.
  • Pour les abonnements créés avec une règle AUTOMATIC, l'API Google Health génère et attribue automatiquement un identifiant au format auto-<project-number>-<subscriber-id>-<WEBHOOK_DATA_TYPE_TOKEN>, où :
    • <project-number> est le numéro du projet Google Cloud du développeur (par exemple, 362978535920).
    • <subscriber-id> est l'ID d'enregistrement de l'abonné / le nom de l'abonné (par exemple, google-health-sync).
    • <WEBHOOK_DATA_TYPE_TOKEN> est le jeton représentant le type de données spécifique. Consultez la section Vocabulaire des jetons de type de données de webhook pour connaître tous les jetons possibles.

L'inclusion de clientProvidedSubscriptionName pour les règles manuelles et automatiques garantit un format de charge utile de notification cohérent pour tous les types d'abonnement.

Mappage des ID utilisateur

healthUserId est un identifiant de l'API Google Health pour l'utilisateur dont les données ont changé. Si votre application accepte plusieurs utilisateurs, vous pouvez recevoir des notifications pour tout utilisateur ayant accordé son consentement à votre application. Lorsque vous recevez une notification, utilisez healthUserId pour identifier l'utilisateur dont les données ont changé. Vous pourrez ainsi utiliser ses identifiants OAuth pour interroger ses données.

Pour mapper les identifiants OAuth d'un utilisateur à son healthUserId, utilisez le point de terminaison getIdentity. Appelez ce point de terminaison avec les identifiants d'un utilisateur lors de son intégration pour récupérer son healthUserId, puis stockez ce mappage. Ce mappage ne change pas au fil du temps, il peut donc être mis en cache indéfiniment. Pour obtenir un exemple, consultez Obtenir l'ID utilisateur. Cela vous permet de sélectionner les identifiants utilisateur appropriés lorsque vous interrogez des données en fonction de l'healthUserId dans une notification.

Intervalles de temps

Le tableau intervals contient un ou plusieurs objets représentant les intervalles de temps pour les données modifiées :

Champ Type Obligatoire Description
physicalTimeInterval object Non Intervalle de temps physique UTC des données modifiées (format YYYY-MM-DDTHH:MM:SSZ). Renseigné pour les métriques intrajournalières ; omis pour les cumuls quotidiens et les activités d'une journée complète.
civilDateTimeInterval object Oui Date et intervalle de temps du calendrier civil (local) des données modifiées, sans décalage UTC ni décalage de fuseau horaire.
civilIso8601TimeInterval object Oui Intervalle de temps civil (local) représenté sous forme de chaîne ISO 8601 (format YYYY-MM-DDTHH:MM:SS) sans décalage UTC ni décalage de fuseau horaire.

Pour les activités d'une journée complète et les agrégats quotidiens, le champ physicalTimeInterval est intentionnellement omis de la charge utile de la notification. En effet, les événements d'une journée entière sont regroupés par date civile nominale, et les durées physiques peuvent être inexactes lorsque l'on change de fuseau horaire. Lorsque vous traitez des événements d'une journée entière, nous vous recommandons d'utiliser les champs civilDateTimeInterval ou civilIso8601TimeInterval. Pour savoir comment interroger ces points de données à l'aide de filtres d'heure civile, consultez Filtrer les données par heure de début civile d'un intervalle.

Voici un exemple de charge utile de notification pour une activité d'une journée complète où le champ physicalTimeInterval est omis :

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

Vocabulaire des jetons de type de données de webhook

Lorsque vous utilisez une règle AUTOMATIC, le clientProvidedSubscriptionName généré contient l'un des jetons de suffixe WEBHOOK_DATA_TYPE_* en majuscules suivants, en fonction du type de données :

Jeton de type de données de webhook Type de données correspondant
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 (événement système)
WEBHOOK_DATA_TYPE_USER_REVOKED_ACCESS user-revoked-access (événement système)

Agrégation des mises à jour

Pour réduire le trafic réseau, l'API Google Health regroupe les messages par lot. Le système est configuré pour envoyer un maximum de 99 messages par lot, qui sont automatiquement transmis dès qu'ils sont disponibles.

Lorsque les messages sont traités par lot, la charge utile est envoyée sous la forme d'un tableau JSON contenant plusieurs objets de notification. Un même lot peut inclure des mises à jour pour plusieurs utilisateurs sur plusieurs types de données auxquels ils sont abonnés. Pour obtenir un exemple de charge utile, consultez l'exemple Lot (plusieurs utilisateurs et types de données) dans la section "Format de notification".

Répondre à une notification

Votre serveur doit valider l'en-tête Authorization, gérer le handshake de validation du point de terminaison synchrone et répondre immédiatement aux notifications de modification des données avec un code d'état HTTP 204 No Content. Pour éviter les délais avant expiration, traitez la charge utile de la notification de manière asynchrone après l'envoi de la réponse. Si l'API Google Health reçoit un autre code d'état ou si la requête expire, elle réessaie d'envoyer la notification ultérieurement.

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

Pour récupérer les données mises à jour pour chaque intervalle notifié, appelez le point de terminaison reconcile avec un filtre pour la période de l'intervalle. Cela permet de dédupliquer automatiquement les intervalles qui se chevauchent sur les appareils connectés. Pour en savoir plus et obtenir des exemples, consultez Obtenir une vue rapprochée des données d'intervalle.

Il existe trois stratégies courantes pour mettre à jour votre base de données après avoir reçu une notification de modification de données :

  • Mise en file d'attente (recommandé) : placez les notifications entrantes dans une file d'attente de messages et traitez-les en fonction des ressources système.
  • Récupération immédiate : demandez à votre backend d'appeler immédiatement l'API Google Health pour récupérer les données mises à jour et les enregistrer dans votre base de données.
  • Mises à jour différées : marquez les enregistrements de la base de données locale de l'utilisateur comme modifiés et ne récupérez les données mises à jour à partir de l'API Google Health que lors de la prochaine visite de l'utilisateur dans votre application.

La stratégie de mise en file d'attente vous permet de traiter les données en temps quasi réel tout en restant résilient face à un afflux de notifications (par exemple, si une interruption de service entraîne la distribution simultanée d'un grand nombre de notifications). Bien que vous puissiez choisir de traiter ces files d'attente selon une planification par lot à intervalles longs (par exemple, toutes les trois heures) pour économiser des ressources, l'inconvénient est que les utilisateurs s'attendent généralement à voir leurs données mises à jour en temps quasi réel.

Bonnes pratiques

Suivez ces bonnes pratiques pour garantir une gestion fiable et efficace des mises à jour de données :

  • Utiliser l'API Subscription : utilisez l'API Subscription pour être averti lorsqu'un utilisateur dispose de nouvelles données à télécharger, plutôt que d'interroger les points de terminaison.
  • Mettez en file d'attente et traitez les notifications séparément : lorsque vous recevez des notifications d'abonnement, placez-les dans une file d'attente et traitez-les séparément en fonction des ressources de votre système. Maintenez le point de terminaison du webhook léger en accusant réception de la requête immédiatement.

Validation de la signature

Pour garantir l'authenticité des notifications de webhook, la charge utile JSON brute de chaque notification de webhook sortante est signée avec une clé privée à l'aide de PublicKeySign de Tink, qui fournit la signature encodée en Base64 dans l'en-tête HTTP GOOGLE-HEALTH-API-SIGNATURE de la requête. Ces clés de signature font l'objet d'une rotation automatique tous les 30 jours, et l'ensemble de clés publiques officiel correspondant est distribué sous forme de fichier JSON à l'URL permanente https://www.gstatic.com/googlehealthapi/webhooks/webhooks_public_keyset.json.

Vérifier la signature

Utiliser Tink (recommandé) : les développeurs peuvent valider la signature à l'aide de la primitive PublicKeyVerify de Tink. Récupérez l'ensemble de clés public à partir de l'URL permanente, instanciez la primitive PublicKeyVerify avec l'ensemble de clés, puis vérifiez l'en-tête GOOGLE-HEALTH-API-SIGNATURE décodé par rapport à la charge utile JSON brute du webhook.

Validation manuelle (sans Tink) : si les développeurs choisissent de ne pas utiliser Tink, ils peuvent valider manuellement la signature en procédant comme suit :

  1. Décode en base64 l'en-tête GOOGLE-HEALTH-API-SIGNATURE pour séparer le préfixe Tink de 5 octets (qui contient un préfixe de version de 1 octet et un keyId de 4 octets) de la signature encodée en DER.
  2. Récupérez la collection de clés JSON à l'adresse https://www.gstatic.com/googlehealthapi/webhooks/webhooks_public_keyset.json.
  3. Localisez la clé correspondant au keyId analysé et décodez en Base64 son champ de valeur, qui contient un tampon de protocole EcdsaPublicKey sérialisé.
  4. Extrayez les coordonnées X et Y big-endian (tags Protobuf 3 et 4) de cette charge utile binaire.
  5. Instanciez une clé publique ECDSA P-256 standard dans une bibliothèque de chiffrement intégrée à l'aide des coordonnées x et y extraites.
  6. Vérifiez la charge utile JSON brute du webhook par rapport à la signature DER extraite à l'aide de l'algorithme SHA-256.

État de l'abonnement et récupération

Si votre point de terminaison d'abonné devient indisponible ou renvoie un code d'état d'erreur (autre que 204), l'API Google Health stocke les notifications en attente pendant sept jours maximum et relance la distribution avec un intervalle exponentiel entre les tentatives.

Une fois que votre point de terminaison est de nouveau en ligne et répond par 204, l'API envoie automatiquement l'arriéré de messages stockés. Les notifications datant de plus de sept jours sont supprimées et ne peuvent pas être récupérées.

Erreurs fréquentes

Code d'erreur Message Description Recommandation
400 Requête erronée Numéro de projet non valide dans le nom de la ressource Lorsque vous supprimez ou mettez à jour un abonné en utilisant l'ID de votre projet Google Cloud dans l'URL de la requête au lieu du numéro de projet. Cela s'applique aux abonnements aux webhooks utilisant le point de terminaison projects.subscribers. Utilisez le numéro de votre projet Google Cloud dans l'URL de la requête, et non l'ID du projet.
403 Interdit L'appelant n'a pas l'autorisation requise Lorsque vous créez ou listez des abonnés en utilisant l'ID de votre projet Google Cloud dans l'URL de la requête au lieu du numéro de projet. Cela s'applique aux abonnements aux webhooks utilisant le point de terminaison projects.subscribers. Utilisez le numéro de votre projet Google Cloud dans l'URL de la requête, et non l'ID du projet.