boundry.
RegionSydney, Australia
Documentation/webhooks

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

NameTypeDescription
boundry-webhook-idrequiredstring

The endpoint identifier bound into the signature.

boundry-event-idrequiredstring

Stable event identifier; use this as the idempotency key.

boundry-delivery-idrequiredstring

Unique attempt identifier for support and debugging.

boundry-timestamprequiredinteger

Unix timestamp in seconds, included in the MAC.

boundry-signaturerequiredstring

Space-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

  1. Read the raw body bytes and the five boundry-* headers.
  2. Reject a timestamp more than 300 seconds old or more than 60 seconds in the future.
  3. Compute HMAC-SHA256 over webhookId.timestamp.rawBody.
  4. Constant-time compare it with every v1 signature. One matching signature passes during secret rotation.
  5. Only then parse the JSON and durably deduplicate by event.id.
JavaScript verification
TypeScript
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;
}
Python verification
python
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", 200
curl / OpenSSL verification
bash
payload=$(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

AttemptDelayResult
1Immediate

First delivery; each delay uses 0.5–1.0 full jitter.

2+5 seconds

Retryable failure; each delay uses 0.5–1.0 full jitter.

3+5 minutes

Retryable failure; each delay uses 0.5–1.0 full jitter.

4+30 minutes

Retryable failure; each delay uses 0.5–1.0 full jitter.

5+2 hours

Retryable failure; each delay uses 0.5–1.0 full jitter.

6+5 hours

Retryable failure; each delay uses 0.5–1.0 full jitter.

7+10 hours

Retryable failure; each delay uses 0.5–1.0 full jitter.

8+24 hours

Final 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.

Browse event contracts