Webhooks
Enregistrez des endpoints qui reçoivent des notifications d’événements signées.
Créer un webhook
Crée un endpoint de webhook dans votre espace de travail. Emailit envoie les événements correspondants à l’URL par lots de 100 au maximum, sous forme de tableau JSON signé avec le secret du webhook. Pour le format des requêtes, consultez Requêtes de webhook. Nécessite une clé API de portée full.
/webhooksCorps de la requête
namestringObligatoireNom du webhook. Doit être unique dans l’espace de travail ; vous pouvez l’utiliser à la place de l’ID dans les autres endpoints de webhook.
urlstringObligatoireEndpoint qui reçoit les événements. Les URL http et https sont acceptées ; utilisez https en production.
Emailit résout le nom d’hôte à l’enregistrement et rejette localhost ainsi que les adresses IP privées, locales au lien et autres adresses réservées. Les redirections ne sont pas suivies lors de la livraison : utilisez l’URL finale.
all_eventsbooleanEnvoie tous les types d’événements, y compris ceux ajoutés ultérieurement. Par défaut : false. Lorsqu’il vaut true, events est ignoré.
enabledbooleanIndique si Emailit livre les événements au webhook. Par défaut : true.
eventsstring[]Types d’événements à envoyer, par exemple ["email.delivered", "email.bounced"]. Consultez Types d’événements. Par défaut : [], ce qui, avec all_events: false, signifie que le webhook ne reçoit rien.
Les noms d’événements ne sont pas validés. Un type mal orthographié est enregistré, mais ne correspond jamais à aucun événement.
filterobject | nullFiltre de contenu. Emailit n’envoie que les événements dont l’objet correspond aux règles. Disponible sur les forfaits Pro, Business et Custom ; un filtre avec des règles sur Pay as you go renvoie 403.
filter.matchstringall (par défaut) envoie un événement lorsque toutes les règles correspondent. any l’envoie lorsqu’au moins une règle correspond.
filter.rulesobject[]25 règles au maximum.
filter.rules[].fieldstringObligatoireChemin à points dans l’objet de l’événement, par exemple to, status, meta.plan ou, pour les événements de clic et d’ouverture, email.campaign.id. Un préfixe payload. est ignoré.
filter.rules[].operatorstringObligatoireequals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than, is_set, is_not_set, in ou not_in. Les opérateurs de texte comparent les valeurs comme des chaînes ; greater_than et less_than comparent des nombres.
filter.rules[].valueanyValeur de comparaison. Obligatoire pour tous les opérateurs sauf is_set et is_not_set. Utilisez un tableau avec in et not_in.
Réponse
Renvoie 201 Created avec l’objet webhook, y compris le secret de signature (whsec_ suivi de 64 caractères hexadécimaux). Utilisez ce secret pour vérifier les signatures des requêtes. Vous pouvez le relire avec Récupérer un webhook et en effectuer la rotation avec Effectuer une rotation du secret de signature.
| Statut | Cas |
|---|---|
400 |
name ou url est absent, l’URL n’est pas valide, ne peut pas être résolue ou pointe vers une adresse bloquée, ou le filtre n’est pas valide. |
403 |
Le filtre contient des règles et votre forfait n’inclut pas les filtres de webhook. Le corps est {"error": "plan_required", "required_plan": "pro"}. |
409 |
Un webhook portant ce nom existe déjà. Le corps inclut l’id et le name du webhook existant (existing). |
422 |
L’espace de travail a atteint la limite de webhooks de son forfait. Le corps inclut usage.used et usage.limit. Consultez Limites. |
{
"object": "webhook",
"id": "wh_2xLb3Kp9Qr1Vt7Mn5Ws0Yd4Hc8e",
"name": "Production events",
"url": "https://api.acme.com/webhooks/emailit",
"all_events": false,
"enabled": true,
"events": ["email.delivered", "email.bounced"],
"filter": null,
"filters_allowed": true,
"last_used_at": null,
"created_at": "2026-10-01T09:41:05.302000+00:00",
"updated_at": "2026-10-01T09:41:05.302000+00:00",
"secret": "whsec_175a02aca3fb7dc3107ca21e4224a73d058cacc628356a39e020422aec3ca434"
}{
"error": "URL resolves to a private/reserved IP address"
}{
"error": "plan_required",
"required_plan": "pro"
}{
"error": "Webhook with this name already exists",
"existing": {
"object": "webhook",
"id": "wh_2xLb3Kp9Qr1Vt7Mn5Ws0Yd4Hc8e",
"name": "Production events"
}
}{
"error": "Pay as you go includes 3 webhook endpoints.",
"usage": {
"used": 3,
"limit": 3
}
}{
"name": "Enterprise bounces",
"url": "https://api.acme.com/webhooks/emailit",
"events": ["email.bounced", "email.complained"],
"filter": {
"match": "all",
"rules": [
{ "field": "meta.plan", "operator": "equals", "value": "enterprise" },
{ "field": "to", "operator": "not_contains", "value": "@acme.com" }
]
}
}Récupérer un webhook
Renvoie un webhook, recherché par ID ou par nom. C’est le seul endpoint de lecture qui renvoie le secret de signature. Nécessite une clé API de portée full.
/webhooks/:idParamètres de chemin
idstringObligatoireID du webhook (wh_…) ou son nom, encodé pour l’URL.
Réponse
Renvoie 200 OK avec l’objet webhook, y compris secret et filters_allowed (indique si votre forfait permet au webhook d’utiliser un filtre de contenu). last_used_at est l’heure de la dernière livraison réussie, ou null si rien n’a encore été livré.
Renvoie 404 avec error: "Webhook not found" si aucun webhook ne correspond.
{
"object": "webhook",
"id": "wh_2xLb3Kp9Qr1Vt7Mn5Ws0Yd4Hc8e",
"name": "Production events",
"url": "https://api.acme.com/webhooks/emailit",
"all_events": false,
"enabled": true,
"events": ["email.delivered", "email.bounced"],
"filter": {
"match": "all",
"rules": [
{ "field": "meta.plan", "operator": "equals", "value": "enterprise" }
]
},
"filters_allowed": true,
"last_used_at": "2026-10-01T10:02:17.845000+00:00",
"created_at": "2026-10-01T09:41:05.302000+00:00",
"updated_at": "2026-10-01T09:41:05.302000+00:00",
"secret": "whsec_175a02aca3fb7dc3107ca21e4224a73d058cacc628356a39e020422aec3ca434"
}{
"error": "Webhook not found"
}Mettre à jour un webhook
Met à jour un webhook. N’envoyez que les champs à modifier ; au moins un est obligatoire. Le secret de signature ne change pas ; pour en effectuer la rotation, utilisez Effectuer une rotation du secret de signature. Nécessite une clé API de portée full.
/webhooks/:idParamètres de chemin
idstringObligatoireID du webhook (wh_…) ou son nom, encodé pour l’URL.
Corps de la requête
namestringNouveau nom. Doit être unique dans l’espace de travail.
urlstringNouvelle URL de l’endpoint, http ou https. Validée de la même façon qu’à la création.
all_eventsbooleantrue envoie tous les types d’événements et vide la liste events. Si vous le définissez sur false, envoyez aussi events, sinon le webhook ne reçoit rien.
enabledbooleanfalse arrête les livraisons et true les reprend. Les événements qui surviennent pendant que le webhook est désactivé ne sont pas mis en file d’attente pour lui et ne sont pas envoyés plus tard.
eventsstring[]Remplace la liste des types d’événements. Ignoré tant que all_events vaut true. Les noms ne sont pas validés.
filterobject | nullRemplace le filtre de contenu, au même format qu’à la création. Envoyez null pour le supprimer. Un filtre avec des règles nécessite un forfait Pro, Business ou Custom.
Réponse
Renvoie 200 OK avec le webhook mis à jour. Le secret n’est pas inclus ; utilisez Récupérer un webhook pour le lire.
| Statut | Cas |
|---|---|
400 |
Le corps ne contient aucun des champs ci-dessus, ou l’URL ou le filtre n’est pas valide. |
403 |
Le filtre contient des règles et votre forfait n’inclut pas les filtres de webhook (plan_required). |
404 |
Aucun webhook ne correspond à id. |
409 |
Un autre webhook utilise déjà le nouveau nom. |
{
"object": "webhook",
"id": "wh_2xLb3Kp9Qr1Vt7Mn5Ws0Yd4Hc8e",
"name": "Production events",
"url": "https://api.acme.com/webhooks/emailit",
"all_events": false,
"enabled": false,
"events": ["email.delivered", "email.bounced"],
"filter": null,
"filters_allowed": true,
"last_used_at": "2026-10-01T10:02:17.845000+00:00",
"created_at": "2026-10-01T09:41:05.302000+00:00",
"updated_at": "2026-10-01T10:15:40.000000+00:00"
}{
"error": "No valid fields provided for update. Provide at least one of: name, url, all_events, enabled, events, filter"
}{
"error": "Webhook not found"
}{
"error": "Another webhook with this name already exists"
}Lister les webhooks
Renvoie les webhooks de votre espace de travail, du plus récent au plus ancien, et le nombre de webhooks autorisés par votre forfait. Les secrets de signature ne figurent pas dans la liste. Nécessite une clé API de portée full.
/webhooksParamètres de requête
pageintegerNuméro de page, à partir de 1. Par défaut : 1.
limitintegerNombre de webhooks par page, de 1 à 100. Par défaut : 10.
searchstringRecherche insensible à la casse sur le nom ou l’URL du webhook.
matchstringall (par défaut) exige que tous les filtres correspondent. Avec or, il suffit qu’un filtre corresponde. Consultez Filtrage et tri.
orderstringClé de tri de cette liste. Consultez les clés de tri ci-dessous.
directionstringasc ou desc.
Filtres et tri
Les filtres de liste sont des paramètres de requête key.condition=value sur un seul niveau. Consultez Filtrage et tri pour match, order, direction et la liste des conditions par type.
Clés de filtre
| Clé | Type | Conditions | Remarques |
|---|---|---|---|
name | string | exact, not_exact, contains, not_contains, starts_with, ends_with, empty, not_empty | |
url | string | exact, not_exact, contains, not_contains, starts_with, ends_with, empty, not_empty | |
enabled | boolean | exact, not_exact | |
created_at | date | exact, before, after, empty, not_empty |
Clés de tri
Passez dans order l’une de ces clés et dans direction la valeur asc ou desc : name, url, enabled, created_at
Réponse
Renvoie 200 OK avec les webhooks dans data, next_page_url et previous_page_url (null en début ou en fin de liste), et un objet usage : used est le nombre de webhooks de l’espace de travail, limit le maximum autorisé par votre forfait, et filters_allowed indique si votre forfait inclut les filtres de contenu.
{
"data": [
{
"object": "webhook",
"id": "wh_2xLb3Kp9Qr1Vt7Mn5Ws0Yd4Hc8e",
"name": "Production events",
"url": "https://api.acme.com/webhooks/emailit",
"all_events": false,
"enabled": true,
"events": ["email.delivered", "email.bounced"],
"filter": null,
"filters_allowed": true,
"last_used_at": "2026-10-01T10:02:17.845000+00:00",
"created_at": "2026-10-01T09:41:05.302000+00:00",
"updated_at": "2026-10-01T10:02:17.845000+00:00"
}
],
"next_page_url": null,
"previous_page_url": null,
"usage": {
"used": 1,
"limit": 10,
"filters_allowed": true
}
}{
"statusCode": 401,
"error": "Unauthorized",
"message": "Invalid API key"
}Supprimer un webhook
Supprime définitivement un webhook et ses abonnements aux événements. Pour interrompre temporairement les livraisons, mettez plutôt à jour le webhook avec enabled: false. Nécessite une clé API de portée full.
/webhooks/:idParamètres de chemin
idstringObligatoireID du webhook (wh_…) ou son nom, encodé pour l’URL.
Réponse
Renvoie 200 OK avec l’id et le name du webhook supprimé, et deleted: true. Renvoie 404 avec error: "Webhook not found" si aucun webhook ne correspond.
{
"object": "webhook",
"id": "wh_2xLb3Kp9Qr1Vt7Mn5Ws0Yd4Hc8e",
"name": "Production events",
"deleted": true
}{
"error": "Webhook not found"
}Envoyer un événement de test
Envoie un exemple d’événement du type choisi à l’URL du webhook et renvoie la réponse de votre endpoint. Utilisez-le pour vérifier que votre endpoint est joignable et qu’il vérifie correctement les signatures. Nécessite une clé API de portée full.
La requête a le même format, les mêmes en-têtes et la même signature qu’une livraison réelle : un tableau JSON contenant un événement dont l’event_id commence par evt_test_, signé avec le secret actuel du webhook. Elle est envoyée même si le webhook est désactivé ou n’est pas abonné à ce type, n’est pas enregistrée comme requête de webhook et ne fait l’objet d’aucune nouvelle tentative. Les données d’exemple sont fixes et ne font référence à aucun objet réel.
Vous pouvez envoyer 5 événements de test par minute depuis la même adresse IP ; au-delà, la requête renvoie 429.
/webhooks/{id}/testParamètres de chemin
idstringobligatoirewh_…) ou son nom.Paramètres du corps
typestringobligatoire| Ressource | Types d’événements |
|---|---|
email.accepted, email.scheduled, email.delivered, email.bounced, email.attempted, email.failed, email.rejected, email.clicked, email.loaded, email.complained, email.received, email.suppressed, email.canceled, email.unsubscribed, email.resubscribed |
|
| Domaine | domain.created, domain.updated, domain.deleted |
| Liste de contacts | audience.created, audience.updated, audience.deleted |
| Abonné | subscriber.created, subscriber.updated, subscriber.deleted |
| Contact | contact.created, contact.updated, contact.deleted |
| Modèle | template.created, template.updated, template.deleted |
| Blocage | suppression.created, suppression.updated, suppression.deleted |
| Vérification d’e-mails | email_verification.created, email_verification.updated, email_verification_list.created, email_verification_list.updated |
| Campagne | campaign.created, campaign.updated, campaign.deleted, campaign.scheduled, campaign.queued, campaign.sending, campaign.testing, campaign.sent, campaign.canceled, campaign.archived |
Pour la signification de chaque événement, consultez Types d’événements.
Réponse
okbooleantrue si votre endpoint a répondu avec un statut 2xx.status_codeinteger0 si Emailit n’a pas pu se connecter, si la requête a expiré au bout de 30 secondes, si l’endpoint a redirigé (les redirections ne sont pas suivies) ou si l’URL pointe vers une adresse bloquée.bodystringtypestringpayloadobject[]Renvoie 400 si type est absent ou inconnu, 404 si le webhook n’existe pas, et 429 si vous dépassez la limite de tests.
{
"ok": true,
"status_code": 200,
"type": "email.delivered",
"payload": [
{
"event_id": "evt_test_3G0pUekgBm1uIxi9m4C380H70Zq",
"type": "email.delivered",
"object": {
"id": "eml_test_001",
"email_id": 12345,
"message_id": "<test-token@mydomain.com>",
"from": "sender@mydomain.com",
"to": "recipient@example.com",
"subject": "Test email",
"status": "delivered",
"delivered_at": "2026-01-15T10:30:00.000Z"
},
"data": {
"object": {
"id": "eml_test_001",
"email_id": 12345,
"message_id": "<test-token@mydomain.com>",
"from": "sender@mydomain.com",
"to": "recipient@example.com",
"subject": "Test email",
"status": "delivered",
"delivered_at": "2026-01-15T10:30:00.000Z"
}
}
}
],
"body": "{\"received\":true}"
}{
"error": "Unknown event type"
}{
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded, retry in 1 minute"
}Effectuer une rotation du secret de signature
Génère un nouveau secret de signature pour le webhook et le renvoie. Nécessite une clé API de portée full.
L’ancien secret cesse immédiatement d’être utilisé : chaque requête envoyée après la rotation, y compris les nouvelles tentatives d’événements antérieurs, est signée avec le nouveau secret. Il n’y a pas de période de chevauchement : mettez donc à jour le secret dans votre endpoint juste après la rotation, ou acceptez les deux secrets pendant un court moment, le temps de basculer. Consultez Vérifier les signatures de webhook.
/webhooks/{id}/reset-secretParamètres de chemin
idstringobligatoirewh_…) ou son nom.Réponse
Renvoie l’objet webhook avec le nouveau secret (whsec_ suivi de 64 caractères hexadécimaux). Renvoie 404 si le webhook n’existe pas.
{
"object": "webhook",
"id": "wh_3NGa6raJ3s1VJ75lO9km6Dqg4cQ",
"name": "Order notifications",
"url": "https://acme.com/webhooks/emailit",
"all_events": false,
"enabled": true,
"events": ["email.delivered", "email.bounced", "email.complained"],
"filter": null,
"last_used_at": "2026-10-01T12:58:40.000000+00:00",
"created_at": "2026-08-14T09:12:03.000000+00:00",
"updated_at": "2026-10-01T13:20:11.000000+00:00",
"secret": "whsec_a0a593d006bdec5aff2f21e88afd543ba239431acbe7d8dc7a9723d76e0a64e2"
}{
"error": "Webhook not found"
}Relancer les requêtes en échec
Remet en file d’attente, pour une nouvelle livraison, toutes les requêtes de ce webhook définitivement en échec au cours des 7 derniers jours. Nécessite une clé API de portée full.
Une requête échoue définitivement après sa dernière nouvelle tentative automatique (11 tentatives sur plusieurs jours ; voir Nouvelles tentatives et échecs). Les requêtes relancées repartent avec un calendrier complet de nouvelles tentatives et sont livrées en quelques secondes. Si au moins une requête est remise en file d’attente et que le webhook était désactivé, par exemple après 3 jours d’échecs continus, il est réactivé.
Corrigez d’abord votre endpoint, sinon les requêtes échoueront de nouveau. Pour relancer une seule requête, utilisez Relancer une requête.
/webhooks/{id}/retry-failedParamètres de chemin
idstringobligatoirewh_…) ou son nom.Réponse
retriedinteger0 s’il n’y avait rien à relancer ; l’état d’activation du webhook ne change alors pas.Renvoie 404 si le webhook n’existe pas.
{
"retried": 37
}{
"error": "Webhook not found"
}Relancer une requête
Remet en file d’attente une requête de webhook définitivement en échec pour une nouvelle livraison, avec un nouveau calendrier de nouvelles tentatives. Si le webhook était désactivé, il est réactivé. Nécessite une clé API de portée full.
Seules les requêtes dont les nouvelles tentatives automatiques sont épuisées peuvent être relancées de cette façon ; les requêtes encore en attente ou en cours de nouvelle tentative renvoient 400. Les ID de requête (whr_…) se trouvent dans l’onglet Requests du webhook, sous Email APIWebhooks. Pour relancer en une fois toutes les requêtes des 7 derniers jours, utilisez Relancer les requêtes en échec.
/webhooks/{id}/requests/{request_id}/retryParamètres de chemin
idstringobligatoirewh_…) ou son nom.request_idstringobligatoirewhr_…).Réponse
retriedinteger1.idstring| Statut | Cas |
|---|---|
400 |
La requête n’a pas définitivement échoué, ou elle n’a aucun événement à renvoyer. |
404 |
Le webhook n’existe pas, ou la requête ne lui appartient pas. |
{
"retried": 1,
"id": "whr_3tWVVMd2eHv3R9B5DWTLtwwU05X"
}{
"error": "Only permanently failed requests can be retried"
}{
"error": "Webhook request not found"
}