Receive an HTTP POST when the Send API emits an event, instead of polling
Webhooks push events from send.dev to a URL you control. You register an endpoint once, and each matching event is delivered as a signed JSON POST.
Webhook endpoints belong to the do.dev platform rather than to send.dev specifically. That means the endpoint API lives at https://api.do.dev/v1/webhooks (not under /v1/send/), and the same endpoint can receive events from other do.dev services if you subscribe to them. This page covers what matters for email.
| Event | When |
|---|---|
send.email.sent | The email was accepted and queued (the same moment the POST /v1/send/emails/send request returns 202). |
send.email.delivered | The recipient's mail server accepted the message. |
send.email.bounced | The recipient's mail server rejected it. Hard bounces are also added to your suppression list. |
send.email.complained | The recipient reported it as spam. The address is suppressed. |
send.email.unsubscribed | A recipient used the unsubscribe link or one-click header. |
Together these cover the whole life of a message, so a webhook consumer no longer needs to poll Get an email for the final status. An event for provider-side failures (send.email.failed) is planned. See Events for every payload.
/v1/webhooksRegister a URL to receive events. The URL must be https and publicly reachable. Returns the signing secret exactly once.
The path is https://api.do.dev/v1/webhooks, with no /send segment (https://api.send.dev/v1/webhooks is the equivalent alias). Use the same do_live_ API key you use for the Send API. Legacy sk_live_ keys can send email but cannot manage webhooks; they receive 403 forbidden here. Create a do_live_ key in the dashboard.
urlstringrequiredenabled_eventsstring[]requireddescriptionstringcurl -X POST https://api.do.dev/v1/webhooks \
-H "Authorization: Bearer $SEND_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://app.example.com/webhooks/send",
"enabled_events": ["send.email.*"],
"description": "Production email events"
}'{
"id": "whe_2m8x9q4wzk17c",
"url": "https://app.example.com/webhooks/send",
"enabled_events": ["send.email.*"],
"description": "Production email events",
"signing_secret": "whsec_...",
"is_active": true
}Save the signing secret
signing_secret is returned only in this response. You need it to verify signatures. If you lose it, create a new endpoint and delete the old one in the dashboard.
enabled_events| Pattern | Receives |
|---|---|
send.email.sent | Only that event. |
send.email.* | Every Send email event, including ones added later. This is the recommended choice for an email endpoint: when send.email.failed ships, you start receiving it without changing the endpoint. |
send.* | Every Send event. |
* | Every event from every do.dev service on the account. |
/v1/webhooksList endpoints on the account.
{
"endpoints": [
{
"id": "whe_2m8x9q4wzk17c",
"url": "https://app.example.com/webhooks/send",
"description": "Production email events",
"enabled_events": ["send.email.*"],
"is_active": true,
"is_paused": false,
"consecutive_failures": 0,
"last_delivery_at": 1757000001500,
"last_delivery_status": "success",
"created_at": 1756900000000,
"updated_at": 1757000001500
}
]
}/v1/webhooks/{id}Return one endpoint.
/v1/webhooks/{id}/testDeliver a synthetic platform.test event to the endpoint so you can confirm your handler and signature check work.
The test event has type: "platform.test" and livemode: false. Your handler should accept it (return 2xx) and can otherwise ignore it.
/v1/webhooks/{id}Change the URL, subscribed events, description, or enabled flag of an endpoint. Send only the fields you want to change; at least one is required.
urlstringenabled_eventsstring[]descriptionstringenabledboolean{
"id": "whe_2m8x9q4wzk17c",
"url": "https://app.example.com/webhooks/send",
"description": "Production email events",
"enabled_events": ["send.email.bounced", "send.email.complained", "send.email.unsubscribed"],
"is_active": true,
"is_paused": false,
"consecutive_failures": 0,
"created_at": 1756900000000,
"updated_at": 1757010000000
}Updating does not rotate the signing secret. A 400 validation_error lists what was wrong (an unreachable or non-https url, an empty enabled_events, a non-boolean enabled); an unknown id is 404 not_found.
/v1/webhooks/{id}Remove the endpoint. Events already queued for it are dropped.
{
"id": "whe_2m8x9q4wzk17c",
"deleted": true
}All of these work identically on https://api.send.dev/v1/webhooks/{id}.
POST with a JSON body and the signature headers. The User-Agent is DoDevWebhook/1.0.2xx counts as success; anything else, or a timeout, is a failure.id to de-duplicate, and use created rather than arrival order if sequence matters.is_paused: true with a pause_reason). Fix the endpoint, then re-enable it with PUT /v1/webhooks/{id} and { "enabled": true }, or in the dashboard.Respond quickly. Acknowledge with 200 as soon as you have durably recorded the event, and do any real work afterwards; a handler that does slow work inline will hit the 15-second timeout and cause retries.