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β
| Header | Value | Description |
|---|---|---|
x-delivery-id | {executionId}:{attemptNumber} | Identifies this specific delivery attempt |
x-event | component.execution.terminal | Event type |
x-signature-version | v1 | Signature algorithm version (only present when callbackSecret is set) |
x-signature-timestamp | ISO-8601 string | Timestamp used to construct the signature |
x-signature | sha256=<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);
});
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β
| Field | Default | Description |
|---|---|---|
attempts | 1 | Total 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.