Webhooks & events
Every step in the life of a transactional email produces an event, readable through
GET /smtp/statistics/events and pushed to the webhooks subscribed to that event.
| Event (subscription) | event value received |
Source |
|---|---|---|
sent / request |
request |
email accepted for sending, one event per recipient |
delivered |
delivered |
delivery confirmed by the recipient's server |
deferred |
deferred |
delivery deferred |
softBounce / hardBounce |
soft_bounce / hard_bounce |
temporary / permanent bounce |
opened |
opened |
open pixel, on every open |
uniqueOpened |
unique_opened |
first open of the message by this recipient |
click |
click |
tracked link |
blocked |
blocked |
recipient or domain blocked, the email is not sent to them |
spam, invalid and unsubscribed are accepted as subscriptions for Brevo compatibility,
but no event of these kinds is produced today.
A hard bounce adds the recipient to the blocked contacts: later sends to that address
produce blocked.
Managing webhooks
| Method and path | Response |
|---|---|
POST /webhooks |
201 with { "id": 12 } |
GET /webhooks?type=transactional&sort=desc |
200 with { "webhooks": [...] } |
GET /webhooks/{webhookId} |
200 with the webhook |
PUT /webhooks/{webhookId} |
204, only the fields you send change |
DELETE /webhooks/{webhookId} |
204 |
An unknown id returns 404 (document_not_found).
Create a webhook
curl -s -X POST "$BREVO_BASE/webhooks" -H "api-key: $BREVO_API_KEY" \
-H "content-type: application/json" -d '{
"url": "https://my-shop.com/webhooks/brevo",
"description": "Order tracking",
"events": ["delivered", "hardBounce", "opened", "click"],
"type": "transactional",
"auth": { "type": "bearer", "token": "a-secret" },
"headers": [{ "key": "X-Source", "value": "inklura" }]
}'
| Field | Details |
|---|---|
url |
required, http or https, must point to a public address |
events |
non-empty array; without events, the webhook receives every transactional event |
type |
transactional by default; marketing and inbound are accepted but receive nothing |
channel |
email (default) or sms; only email webhooks receive events |
auth |
{ "type": "bearer", "token" } sends Authorization: Bearer …; { "type": "basic", "username", "password" } sends Authorization: Basic … |
headers |
array of { "key", "value" } added to every call |
batched |
true: the payload arrives inside an array (one event per call) |
description, domain |
free text, returned by GET |
Errors: 400 missing_parameter without url, 400 invalid_parameter for an invalid or
private URL, an empty events or an event name unknown for the type.
Payload
Each event is sent as a JSON POST, with the header User-Agent: Brevo-Webhooks/1.0.
{
"event": "click",
"email": "customer@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": "Order 1042 confirmed",
"tag": "order-confirmation",
"tags": ["order-confirmation"],
"template_id": 3,
"sender_email": "shop@my-shop.com",
"link": "https://my-shop.com/track/1042"
}
id is the webhook id, date is in UTC. Depending on the event, reason (why it bounced or
was blocked) and sending_ip are added. Without a tag, tag is "" and tags is absent;
template_id only appears for a template send.
Response and retries
Answer with a 2xx within 10 seconds. A timeout, network error, 429 or 5xx retries the
call, up to three attempts in total (about 1 s then 4 s apart). Any other 4xx stops the
retries. Redirects are followed (three at most) and every address must stay public.
History
POST /webhooks/export builds a CSV of transactional events and answers 202 with
{ "processId": 42 }.
| Field | Details |
|---|---|
notifyURL |
required, public address |
days |
7 by default, ignored when startDate is set |
startDate, endDate |
YYYY-MM-DD |
email, messageId |
optional filters |
sort |
desc (default) or asc |
notifyURL receives a POST with { "processId", "status": "completed", "export_url" }. The
link is also returned by GET /processes/{processId}. The CSV is ;-separated (columns
date, event, email, messageId, subject, tag, reason, link) and the event
column uses the names of GET /smtp/statistics/events (requests, clicks,
hardBounces…). The export holds at most 100,000 rows.