Skip to main content

Webhooks

When a callbackUrl is provided on execution, White Rabbit delivers the completed execution record to your endpoint as an HTTP POST with a JSON body. This page covers how to secure, verify, and recover webhook deliveries.


How it works​

POST /v1/sdk/components
callbackUrl: "https://your-app.com/webhooks/wr"
callbackSecret: "my-hmac-secret"
attempts: 2
β”‚
β–Ό (component runs in background)
β”‚
β–Ό on completion
White Rabbit ──POST──▢ https://your-app.com/webhooks/wr
x-delivery-id: <executionId>:<attemptNumber>
x-event: component.execution.terminal
x-signature-version: v1
x-signature-timestamp: <ISO-8601>
x-signature: sha256=<hex>
Body: { execution record }

The payload is the same execution record shape returned by Execute Component.

Callback headers​

HeaderValueDescription
x-delivery-id{executionId}:{attemptNumber}Identifies this specific delivery attempt
x-eventcomponent.execution.terminalEvent type
x-signature-versionv1Signature algorithm version (only present when callbackSecret is set)
x-signature-timestampISO-8601 stringTimestamp used to construct the signature
x-signaturesha256=<hex>HMAC-SHA256 signature (only present when callbackSecret is set)

Signature verification​

When callbackSecret is set, White Rabbit signs {x-signature-timestamp}.{rawBody} using HMAC-SHA256 and sends the result in the x-signature header as sha256=<hex>. The timestamp is an ISO-8601 string sent in x-signature-timestamp.

Always verify this signature before processing the payload.

Verifying in Node.js​

import { createHmac, timingSafeEqual } from 'crypto';

function verifyWebhookSignature(
rawBody: Buffer,
headers: Record<string, string | string[] | undefined>,
secret: string,
): boolean {
const timestamp = headers['x-signature-timestamp'] as string;
const signatureHeader = headers['x-signature'] as string;

const signedPayload = `${timestamp}.${rawBody.toString()}`;
const expected = createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');

const received = signatureHeader.replace('sha256=', '');

return timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(received, 'hex'),
);
}

// Express handler example
app.post('/webhooks/wr', express.raw({ type: 'application/json' }), (req, res) => {
if (!verifyWebhookSignature(req.body, req.headers, process.env.WR_CALLBACK_SECRET!)) {
return res.status(401).send('Invalid signature');
}

const execution = JSON.parse(req.body.toString());
console.log('Execution completed:', execution.id, execution.status);
res.sendStatus(200);
});
warning

Always use timingSafeEqual to compare signatures. Standard string comparison (===) is vulnerable to timing attacks.

Verifying in Python​

import hmac
import hashlib

def verify_signature(raw_body: bytes, headers: dict, secret: str) -> bool:
timestamp = headers['x-signature-timestamp']
signed_payload = f"{timestamp}.{raw_body.decode()}"
expected = hmac.new(
secret.encode(),
signed_payload.encode(),
hashlib.sha256,
).hexdigest()
received = headers['x-signature'].replace('sha256=', '')
return hmac.compare_digest(expected, received)

Retry behaviour​

FieldDefaultDescription
attempts1Total delivery attempts (max 3)
callbackHeaders{}Custom headers added to every delivery request

If a delivery fails (non-2xx response or timeout), White Rabbit retries up to the configured attempts count with exponential backoff.


Replay callback​

POST /v1/sdk/components/executions/:executionId/replay-callback

Manually re-deliver the completion webhook for a finished execution. Useful when your endpoint was temporarily unavailable or returned an error. Idempotent β€” safe to call multiple times.

curl -X POST \
https://api.whiterabbit.app/v1/sdk/components/executions/3f7a.../replay-callback \
-H "X-Api-Key: ws_..." \
-H "X-Sdk-Timestamp: <timestamp>" \
-H "X-Sdk-Signature: <signature>"

Response β€” same execution record shape as Execute Component.