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.