Base de connaissances
Webhooks
Prévenez votre point de terminaison quand du courrier arrive, au lieu de vous obliger à interroger.
Détails
- Utilisables dès aujourd'hui depuis Paramètres → Webhooks et via l'API : enregistrez un point de terminaison https, choisissez lesquels des vingt événements il veut, et copiez le secret de signature whsec_, montré à la création et à la rotation et jamais ensuite. Les livraisons sont de vrais POST signés émis par la boîte mail elle-même plutôt que par un appel d'API : ils se déclenchent donc sur le courrier entrant et sur les ouvertures et les clics, quelle qu'en soit l'origine d'envoi. L'envoi se déclenche depuis toutes les surfaces, et auparavant il ne se déclenchait que depuis certaines : un envoi via l'API, MCP, un modèle ou une règle levait email.sent alors qu'un message envoyé depuis la fenêtre de rédaction de l'application ne le faisait pas, parce que celle-ci écrit directement dans la boîte mail plutôt que de passer par le service d'envoi qui émettait l'événement. L'événement est désormais levé au niveau de la boîte mail, là où tous se rejoignent : rédiger dans l'application, programmer pour mardi et poster sur l'API sont trois façons de provoquer le même webhook. Un envoi différé le dit deux fois : email.scheduled ou email.queued à son acceptation, email.sent quand il part réellement, et email.cancelled si vous le reprenez entre-temps. Dix points de terminaison par boîte mail, appliqué partout où l'on en enregistre un plutôt que sur ce seul écran.
- Les événements se répartissent en trois familles. Quinze portent sur un message : email.received, email.replied, email.sent, email.delivered, email.failed, email.cancelled, email.scheduled, email.queued (le pendant de scheduled pour l'annulation d'envoi), email.delivery_delayed, email.bounced, email.complained, email.suppressed, email.opened, email.clicked et email.downloaded. email.sent signifie que le service d'envoi a accepté le message, email.delivered que le serveur destinataire l'a accepté, et email.delivery_delayed qu'il n'est pas encore arrivé et que les tentatives se poursuivent. email.replied se déclenche à côté de email.received quand le message qui arrive répond à un message déjà présent dans la boîte mail, si bien qu'un consommateur qui veut les deux les obtient tous les deux. email.downloaded se déclenche quand une personne récupère un fichier parti sous forme de lien de téléchargement, avec le même classificateur qui tient les scanners et les aperçus de liens hors du compte, et il ne nomme aucun destinataire, car le lien est le même pour tous ceux à qui le message est parti. Trois portent sur un domaine : domain.verified quand il commence à recevoir, domain.sending_changed quand son verdict d'envoi bouge, et domain.deleted quand il est supprimé, que vous l'ayez demandé ou que le nettoyage à sept jours l'ait retiré faute de vérification. Deux portent sur la liste de suppression elle-même, qui est autre chose que email.suppressed : suppression.added quand une adresse y entre, suppression.removed quand une adresse est de nouveau autorisée. N'en souscrire aucun signifie tous les événements de message sauf email.replied, soit quatorze aujourd'hui, jamais une famille ajoutée plus tard, et l'API relit cela comme ["*"]. Nommez les événements voulus si vous préférez être explicite. Chaque livraison porte X-OpenEmail-Signature sous la forme t=<unix>,v1=<hex>, un HMAC-SHA-256 sur l'horodatage, un point, et le corps brut, ainsi que X-OpenEmail-Event et X-OpenEmail-Delivery. Vérifiez sur les octets tels qu'ils sont arrivés : analyser puis resérialiser réordonne les clés et casse la signature. La fenêtre de rejeu de 300 secondes est au destinataire de l'appliquer, et le vérificateur du SDK la prend par défaut.
- L'enregistrement est refusé pour tout ce qui n'est pas https ou n'est pas routable publiquement (loopback, RFC1918, link-local, CGNAT et les équivalents IPv6), et les redirections ne sont pas suivies : un 3xx est donc enregistré comme une livraison échouée plutôt que poursuivi ailleurs. Le destinataire dispose de 5 secondes, les points de terminaison sont livrés en parallèle, si bien que dix d'entre eux coûtent toujours 5 secondes plutôt que 50, et toutes les tentatives sont listées, page par page, sur la page du point de terminaison avec le code de réponse et la durée.
- Une livraison est tentée jusqu'à 8 fois. La première part au moment de l'événement ; un échec susceptible de se résoudre de lui-même est retenté après 1 minute, puis 5, puis 30, puis 2 heures, 5 heures, 10 heures et encore 10 heures, ce qui étale un événement sur environ 27 heures et demie. Chaque attente varie d'un dixième au plus, pour que mille événements tombés en échec ensemble ne reviennent pas tous à la même seconde, et un Retry-After du point de terminaison qui demande un délai plus long est respecté, jusqu'à 6 heures. Les nouvelles tentatives sont conservées comme travail durable plutôt qu'en mémoire : un déploiement au milieu de cette fenêtre ne les perd donc pas. Seuls les échecs qui valent la peine d'être répétés le sont : un dépassement de délai, une connexion refusée, 408, 425, 429 ou n'importe quel 5xx. Tout autre 4xx est le point de terminaison qui rejette délibérément la charge utile, et demander sept fois de plus ferait sept fois la charge pour la même réponse. L'identifiant de l'événement et son createdAt sont fixés une seule fois et chaque tentative les porte, l'identifiant aussi dans X-OpenEmail-Delivery : un destinataire qui voit deux fois le même identifiant peut donc ignorer le second plutôt qu'agir deux fois. Une fois le point de terminaison réparé, une livraison en échec peut être rejouée depuis le journal de livraison dans l'application ou par l'API, un événement à la fois, et un rejeu porte le même identifiant. Un rejeu suspend les nouvelles tentatives automatiques de cet événement pendant son envoi, et est refusé si l'une d'elles est déjà en cours d'envoi, si bien que le destinataire ne reçoit jamais deux copies à la fois. Après 100 événements d'affilée dont toutes les tentatives échouent, le point de terminaison est désactivé, l'espace de travail reçoit un e-mail, et la raison est lisible sur le point de terminaison lui-même. Un point de terminaison qui répond 410 Gone est désactivé sur-le-champ.
- Un point de terminaison qui échoue 100 fois d'affilée est désactivé plutôt que rappelé indéfiniment, et toutes les personnes ayant accès aux webhooks reçoivent un e-mail pour le dire : lequel, ce qu'a rapporté la dernière tentative, et que rien n'a été mis en file pendant qu'il échouait. Le compteur est CONSÉCUTIF et toute tentative livrée le remet à zéro : un mauvais après-midi en mars dernier ne peut donc pas s'additionner en une désactivation aujourd'hui. Le réactiver remet aussi le compteur à zéro. La console distingue les deux états plutôt que d'afficher un seul interrupteur : un point de terminaison que vous avez désactivé n'a pas la même allure qu'un point de terminaison que nous avons désactivé.
- Gérer les points de terminaison est une seule tâche avec deux portes d'entrée. Via l'API, c'est POST /webhooks, le patch, le delete, la rotation du secret, le test, le journal de livraison et le rejeu, avec une méthode pour chacun dans le SDK ; dans l'application, c'est Paramètres → Webhooks, sur le même registre plutôt qu'un second. La lecture est conditionnée à webhooks:read, si bien que quiconque construit une intégration peut voir les points de terminaison et leur historique de livraison (lequel s'est déclenché, ce qu'a répondu le destinataire, combien de temps cela a pris) sans être le propriétaire. Enregistrer, modifier, tester, faire tourner, rejouer et supprimer exigent webhooks:write ET la propriété de la boîte mail, sur les deux surfaces, et cette seconde moitié est délibérée : un point de terminaison entend parler de chaque adresse que détient l'espace de travail, sauf si ses propres listes d'autorisation le restreignent, avec les sujets et les destinataires, et aucune permission ne veut dire « peut recevoir tout cela ». Un rôle qui construit des intégrations et ne lit pas le courrier pilote cela avec une clé d'espace de travail à la place. Ouvrir une livraison pour lire le corps envoyé et la réponse entière exige aussi d'être le propriétaire, car ce corps porte les mêmes objets et destinataires.
- Les journaux se lisent de la même façon partout. GET /webhooks/deliveries lit d'un coup le journal de livraison de tous les points de terminaison et GET /webhooks/{id}/deliveries celui d'un seul, tous deux filtrés par statut, l'interrupteur « échecs seulement », et par une période, et GET /webhooks/activity et GET /webhooks/{id}/activity lisent qui a créé, modifié, activé ou désactivé, fait tourner, testé, rejoué ou supprimé quoi, en @username ou sous le nom de la clé API qui l'a fait. Le SDK a une méthode pour chacun, et le serveur MCP a listWebhookDeliveries, getWebhookDelivery, listWebhookActivity et replayWebhookDelivery, qui demande toujours avant d'envoyer. Lire le corps d'une livraison reste réservé au propriétaire sur toutes les surfaces, et une clé limitée à une adresse d'un domaine ne peut pas lire les livraisons d'un point de terminaison qui couvre tout le domaine.