Webhooks
Create webhook
Register a webhook. Returns 201 with the webhook and its signing secret. This is the only time the full secret is shown, so store it now.
POST
https://api.sndr.sh/api/cranl/v1/webhooksCreate webhook
curl -X POST 'https://api.sndr.sh/api/cranl/v1/webhooks' \
-H 'Authorization: Bearer sndr_your_access_token' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://your-app.com/webhooks/sndr",
"events": ["email.delivered", "email.bounced", "email.failed"]
}'201Created
{
"id": "wh_2a7d4e9c-6b1",
"organization_id": "org_8d41c7a2-3f6",
"url": "https://your-app.com/webhooks/sndr",
"events": ["email.delivered", "email.bounced", "email.failed"],
"secret_prefix": "whsec_Xk9pQ2",
"active": true,
"created_at": "2026-06-01T12:00:00Z",
"secret": "Xk9pQ2vLr8TzW4mN1bYcE7hJ0aSdFgUi"
}Authorization
AuthorizationstringheaderrequiredBearer followed by an access token. Needs the send scope.
Body
application/jsonurlstringrequiredPublic HTTPS URL. Hosts that resolve to private, loopback or link-local addresses are rejected.
eventsstring[]requiredEvents to receive, at least one of: email.delivered, email.bounced, email.failed, email.unsubscribed (email.queued and email.complained are accepted but not sent).
Errors
401
unauthenticatedMissing or invalid Bearer token.401
token_expiredAccess token expired — exchange the refresh token for a new pair.401
token_revokedToken was explicitly revoked.400
invalid_requestBody is not valid JSON, url is missing or not a URL, or an event name is not supported.402
plan_limit_reachedThe plan’s webhook limit is reached.403
insufficient_scopeToken is missing the send scope.422
validation_failedurl is not https, cannot be resolved, or points at a private address.