Webhooks
Blank delivers events to your HTTPS endpoint as signed CloudEvents 1.0 JSON. Webhooks replace polling for anything you need to react to promptly. Managing endpoints requireswebhooks:write; reading endpoints and deliveries requires webhooks:read.
Limits
Deleted endpoints do not count toward the endpoint limit. The per-batch figure is a fair-share bound rather than a quota: dispatch takes at most that many of your deliveries per pass so one account’s backlog cannot delay everyone else’s, and your remaining deliveries are picked up on subsequent passes in the usual order.
Operations
All mutations require an
Idempotency-Key. Update, delete, and rotate additionally require If-Match with the endpoint’s current version.
Event types
Types are versioned with a
.v1 suffix. A breaking payload change ships as a new type, never as a mutation of an existing one.
Create an endpoint
URL policy
Endpoint URLs are validated at creation and again on every delivery attempt. A URL is rejected with422 webhook_url_disallowed unless it:
- uses
https:on the default port (no port, or443) - carries no userinfo and no fragment
- resolves to a public, fully qualified hostname — not
localhost, and not a hostname ending in.localhost,.local,.internal,.home.arpa, or.onion - is not a private, loopback, link-local, carrier-grade NAT, documentation, or multicast IP address, in IPv4 or IPv6
The delivery request
Blank sendsPOST with these headers:
The body is a CloudEvent:
data carries the allowlisted payload for that event type. Treat unknown fields as additive and ignore them.
Verifying the signature
Blank-Signature is an HMAC-SHA-256 over "{timestamp}.{raw request body}", hex-encoded. Verify the exact raw bytes before parsing JSON — re-serializing the body changes the signature.
verifyWebhookSignature enforces a timestamp tolerance of 300 seconds by default, rejecting replays outside that window. Override it with toleranceSeconds (1 to 3,600) only if you have a reason to. It checks every v1 signature against secret, and every v0 signature against previousSecret when you supply one.
Responding
- Return any
2xxonly after the event is durably accepted. Write it to a queue or a table first, then acknowledge. - Anything other than
2xx, a timeout, or a transport failure counts as a failed attempt and is retried. - The delivery timeout is 10 seconds. Do the work asynchronously.
- Deduplicate on
Blank-Event-Id. Retries and replays reuse the same event ID with a new delivery ID, and at-least-once delivery is the contract.
Retries and dead letters
Each delivery gets up to 7 attempts: the first, then six retries at roughly 1 minute, 5 minutes, 30 minutes, 2 hours, 8 hours, and 24 hours. Each delay is jittered, and aRetry-After header on your response is honoured up to 24 hours.
Delivery status moves through:
Inspecting deliveries
id, endpointId, eventId, eventType, status, attemptCount, nextAttemptAt, responseStatus, lastErrorCode, deliveredAt, createdAt, updatedAt. Filter by status and paginate with cursor and limit.
lastErrorCode distinguishes transport failures (request_timeout, network_error, url_disallowed, redirect_limit_exceeded, redirect_location_missing) from HTTP rejections, which are recorded as http_<status>. The complete stable list is in Errors.
Replaying
Blank-Delivery-Id and the original Blank-Event-Id. Use it to drain dead letters after fixing your endpoint. A delivery that is not in a replayable state returns 409 webhook_delivery_not_replayable; an unknown or foreign delivery returns 404 webhook_delivery_not_found.
Rotating the signing secret
Rotation issues a new secret and keeps the previous one valid for a bounded overlap window, so you can deploy without dropping in-flight deliveries.overlapHours is an integer from 1 to 24, defaulting to 24. During the overlap, Blank signs each delivery with both secrets: v1 with the new secret and v0 with the previous one.
Safe rotation:
- Rotate and store the new secret as
BLANK_WEBHOOK_SECRET. - Move the old value to
BLANK_PREVIOUS_WEBHOOK_SECRETand deploy, so your handler accepts both. - Once the overlap window closes, remove
BLANK_PREVIOUS_WEBHOOK_SECRET.
Updating and deleting
Both use optimistic concurrency:update accepts any non-empty subset of name, url, eventTypes, and status (active or disabled). A stale version returns 412 precondition_failed — re-read the endpoint and retry.
delete soft-deletes the endpoint, stops all future fanout, and returns { id, deletedAt }.
Errors
Shared errors apply too. See Errors and Conventions.