Webhooks
Boundry sends signed, project-scoped platform events to verified endpoints. Every endpoint has its own signing secret and receives the API version pinned when it was created.
Request headers
boundry-webhook-idrequiredstringThe endpoint identifier bound into the signature.
boundry-event-idrequiredstringStable event identifier; use this as the idempotency key.
boundry-delivery-idrequiredstringUnique attempt identifier for support and debugging.
boundry-timestamprequiredintegerUnix timestamp in seconds, included in the MAC.
boundry-signaturerequiredstringSpace-separated v1,base64-HMAC-SHA256 signatures; rotation can include both secrets.
Signature construction
Boundry signs the exact raw request bytes. It constructs ${webhookId}.${timestamp}.${rawBody}, then sets boundry-signature to v1,<base64(HMAC-SHA256(secret, signedPayload))>. Do not parse and re-serialize JSON before verification.
Verification algorithm
- Read the raw body bytes and the five
boundry-*headers. - Reject a timestamp more than 300 seconds old or more than 60 seconds in the future.
- Compute HMAC-SHA256 over
webhookId.timestamp.rawBody. - Constant-time compare it with every
v1signature. One matching signature passes during secret rotation. - Only then parse the JSON and durably deduplicate by
event.id.
import { webhooks, BoundrySignatureError } from "@boundry/sdk";
try {
const event = await webhooks.verify(await request.arrayBuffer(), request.headers, process.env.BOUNDRY_WEBHOOK_SECRET!);
// Store event.id before processing: retries reuse the same event id.
switch (event.type) {
case "email.delivered":
await recordDelivery(event.data.object);
break;
}
return new Response("ok");
} catch (error) {
if (error instanceof BoundrySignatureError) return new Response("invalid signature", { status: 400 });
throw error;
}from boundry import webhooks, BoundrySignatureError
def handler(raw_body: bytes, headers: dict[str, str]):
try:
event = webhooks.verify(raw_body, headers, BOUNDRY_WEBHOOK_SECRET)
except BoundrySignatureError:
return "invalid signature", 400
if event["type"] == "email.delivered":
record_delivery(event["data"]["object"])
return "ok", 200payload=$(cat request.json)
signed_payload="$boundry_webhook_id.$boundry_timestamp.$payload"
expected=$(printf %s "$signed_payload" | openssl dgst -sha256 -hmac "$BOUNDRY_WEBHOOK_SECRET" -binary | base64)
printf %s "$boundry_signature" | tr " " "\n" | grep -Fx "v1,$expected"Retries and endpoint health
1ImmediateFirst delivery; each delay uses 0.5–1.0 full jitter.
2+5 secondsRetryable failure; each delay uses 0.5–1.0 full jitter.
3+5 minutesRetryable failure; each delay uses 0.5–1.0 full jitter.
4+30 minutesRetryable failure; each delay uses 0.5–1.0 full jitter.
5+2 hoursRetryable failure; each delay uses 0.5–1.0 full jitter.
6+5 hoursRetryable failure; each delay uses 0.5–1.0 full jitter.
7+10 hoursRetryable failure; each delay uses 0.5–1.0 full jitter.
8+24 hoursFinal retry; each delay uses 0.5–1.0 full jitter.
2xx completes a delivery. 410 disables the endpoint immediately; other 4xx responses except 408 and 429 are permanent failures. Timeouts, connection/TLS errors, 408, 429, and 5xx responses retry; a short Retry-After is honoured.
Boundry disables an endpoint after 20 consecutive failed deliveries or 72 hours with no successful delivery. Re-enable it explicitly in the dashboard or API; enabling does not replay the backlog. The delivery log keeps each attempt, status, response code and redacted response excerpt, and supports manual redelivery.
Secret rotation
Call the rotate-secret endpoint with an overlap duration (24 hours by default, 72 hours maximum), save the returned replacement secret, then deploy it. During the overlap, Boundry adds signatures for both old and new secrets to the same request. Remove the old secret after previous_secret_expires_at.
Consumer guidance
Acknowledge after durable enqueueing, not after slow business processing. Webhooks are at-least-once: deduplicate strictly by event.id, which is stable across retries. Use boundry-delivery-id only to investigate one attempt. Allow inbound traffic from public Boundry delivery infrastructure and require HTTPS; do not rely on source IP alone in place of signature verification.