Webhooks & événements

Chaque étape de la vie d'un e-mail transactionnel produit un événement, consultable par GET /smtp/statistics/events et envoyé aux webhooks abonnés à cet événement.

Événement (abonnement) Valeur event reçue Origine
sent / request request e-mail accepté pour envoi, un événement par destinataire
delivered delivered remise confirmée par le serveur destinataire
deferred deferred remise différée
softBounce / hardBounce soft_bounce / hard_bounce rejet temporaire / définitif
opened opened pixel d'ouverture, à chaque ouverture
uniqueOpened unique_opened première ouverture du message par ce destinataire
click click lien suivi
blocked blocked destinataire ou domaine bloqué, l'e-mail ne part pas vers lui

spam, invalid et unsubscribed sont acceptés comme abonnements pour rester compatibles avec Brevo, mais aucun événement de ce type n'est produit aujourd'hui.

Un rejet définitif ajoute le destinataire aux contacts bloqués : les envois suivants vers cette adresse produisent blocked.

Gérer les webhooks

Méthode et chemin Réponse
POST /webhooks 201 avec { "id": 12 }
GET /webhooks?type=transactional&sort=desc 200 avec { "webhooks": [...] }
GET /webhooks/{webhookId} 200 avec le webhook
PUT /webhooks/{webhookId} 204, seuls les champs fournis changent
DELETE /webhooks/{webhookId} 204

Un identifiant inconnu renvoie 404 (document_not_found).

Créer un webhook

curl -s -X POST "$BREVO_BASE/webhooks" -H "api-key: $BREVO_API_KEY" \
  -H "content-type: application/json" -d '{
    "url": "https://ma-boutique.fr/webhooks/brevo",
    "description": "Suivi des commandes",
    "events": ["delivered", "hardBounce", "opened", "click"],
    "type": "transactional",
    "auth": { "type": "bearer", "token": "un-secret" },
    "headers": [{ "key": "X-Source", "value": "inklura" }]
  }'
Champ Détail
url requis, http ou https, doit viser une adresse publique
events tableau non vide ; sans events, le webhook reçoit tous les événements transactionnels
type transactional par défaut ; marketing et inbound sont acceptés mais ne reçoivent aucun envoi
channel email (défaut) ou sms ; seuls les webhooks email reçoivent les événements
auth { "type": "bearer", "token" } envoie Authorization: Bearer … ; { "type": "basic", "username", "password" } envoie Authorization: Basic …
headers tableau de { "key", "value" } ajoutés à chaque appel
batched true : la charge utile arrive dans un tableau (un événement par appel)
description, domain texte libre, renvoyé par GET

Erreurs : 400 missing_parameter sans url, 400 invalid_parameter pour une URL invalide ou privée, un events vide ou un nom d'événement inconnu pour le type.

Charge utile

Chaque événement est envoyé en POST JSON, avec l'en-tête User-Agent: Brevo-Webhooks/1.0.

{
  "event": "click",
  "email": "client@example.com",
  "id": 7,
  "date": "2026-09-24 14:26:05",
  "ts": 1790259965,
  "message-id": "<202609241426.12345678901@smtp-relay.mailin.fr>",
  "ts_event": 1790259965,
  "ts_epoch": 1790259965000,
  "subject": "Commande 1042 confirmée",
  "tag": "order-confirmation",
  "tags": ["order-confirmation"],
  "template_id": 3,
  "sender_email": "boutique@ma-boutique.fr",
  "link": "https://ma-boutique.fr/suivi/1042"
}

id est l'identifiant du webhook, date est en UTC. Selon l'événement s'ajoutent reason (motif d'un rejet ou d'un blocage) et sending_ip. Sans tag, tag vaut "" et tags est absent ; template_id n'apparaît que pour un envoi par modèle.

Réponse et nouvelles tentatives

Répondez en 2xx dans les 10 secondes. Un délai dépassé, une erreur réseau, un 429 ou un 5xx relance l'appel, jusqu'à trois tentatives au total (environ 1 s puis 4 s d'écart). Une autre réponse 4xx arrête les tentatives. Les redirections sont suivies (trois au plus) et chaque adresse doit rester publique.

Historique

POST /webhooks/export produit un CSV des événements transactionnels et répond 202 avec { "processId": 42 }.

Champ Détail
notifyURL requis, adresse publique
days 7 par défaut, ignoré si startDate est fourni
startDate, endDate AAAA-MM-JJ
email, messageId filtres facultatifs
sort desc (défaut) ou asc

notifyURL reçoit en POST { "processId", "status": "completed", "export_url" }. Le lien est aussi renvoyé par GET /processes/{processId}. Le CSV est séparé par ; (colonnes date, event, email, messageId, subject, tag, reason, link) et la colonne event reprend les noms de GET /smtp/statistics/events (requests, clicks, hardBounces…). L'export compte au plus 100 000 lignes.