Managing endpoints
Provision webhook endpoints from deploy scripts and infrastructure-as-code. For the receiver side, see Webhooks.
Every route here, reads included, needs the webhooks:manage
scope. Without it: 403 insufficient_scope. Mint a
new key; scopes cannot be added to an existing one.
An endpoint is a standing feed of every address you mail, pushed to a URL the caller chooses. A base key can read contacts but has to keep asking; a key that adds an endpoint gets them pushed forever, silently. The delivery log names every recipient, so reads are gated too.
The Webhook endpoint object
{
"id": "whe_2v0dz1k9x4m7",
"object": "webhook_endpoint",
"url": "https://yourapp.com/webhooks/day3",
"description": "production",
"events": ["email.bounced", "email.complained", "suppression.created"],
"status": "active",
"consecutive_failures": 0,
"last_success_at": "2026-08-21T09:00:03.000Z",
"last_failure_at": null,
"last_error": null,
"created_at": "2026-08-15T09:00:00.000Z"
}Endpoints are never auto-disabled. Monitor consecutive_failures,
last_failure_at and last_error instead.
The signing secret is not here. See below.
List endpoints
/v1/webhooksneeds webhooks:manageCreate an endpoint
/v1/webhooksneeds webhooks:manage| Field | Type | Required | Notes |
|---|---|---|---|
url | string | Yes | Public https, port 443 or 8443. |
events | string[] | Yes | Which events to send. |
description | string | No | For your own bookkeeping. |
curl -X POST https://go.day3.app/api/v1/webhooks \
-H "Authorization: Bearer $DAY3_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/day3",
"description": "production",
"events": ["email.bounced", "email.complained", "suppression.created"]
}'This response is the only time the API returns the signing secret. Capture it now.
{
"id": "whe_2v0dz1k9x4m7",
"object": "webhook_endpoint",
"url": "https://yourapp.com/webhooks/day3",
"secret": "whsec_...",
"events": ["email.bounced", "email.complained", "suppression.created"],
"status": "active"
}Retrieve an endpoint
/v1/webhooks/{webhook_id}needs webhooks:manageUpdate an endpoint
/v1/webhooks/{webhook_id}needs webhooks:manageAccepts url, description, events and status. events replaces the list;
it does not append.
Delete an endpoint
/v1/webhooks/{webhook_id}needs webhooks:manageRemoves the endpoint and its delivery history.
Delivery log
/v1/webhooks/{webhook_id}/deliveriesneeds webhooks:manageNewest first, cursor-paginated. Filter with ?status=. Rows are kept for 30
days.
{
"id": "whd_9x4v0dz1k2m7",
"object": "webhook_delivery",
"endpoint_id": "whe_2v0dz1k9x4m7",
"event_id": "evt_2k4h9x4v0dz1",
"event_type": "email.bounced",
"status": "failed",
"attempt": 3,
"response_status": 502,
"error": "Bad Gateway",
"duration_ms": 10012,
"next_attempt_at": "2026-08-21T09:40:00.000Z",
"delivered_at": null,
"created_at": "2026-08-21T09:00:00.000Z"
}The signed payload is not included. Over the API it would be a paginated dump of every address you mail. The app’s delivery log shows it.
The signing secret
Returned once, by POST, and never again at any scope. Reveal and rotate live
in the app behind a session, because a key that could read the secret could
forge our events into your receiver.
Lost it? Rotate in the app under API keys, then Webhooks, following the zero-downtime order.