Register a public HTTPS receiver
POST /api/v1/webhooks registers a destination and returns its signing secret once. Store that secret in server configuration. Destinations must use public HTTPS on port 443; private, local, reserved, and redirected destinations are rejected. Do not include a credential in the destination URL.
screening.completed events contain an event ID, environment, screening ID, status, and creation time. They omit the submitted subject. Fetch the authorized result separately if your handler needs it. Webhook delivery history and retries are available in the dashboard.
Verify before parsing or acting
Read the raw request body without reformatting JSON. Webhook-Id, Webhook-Timestamp, and Webhook-Signature authenticate the string formed from ID, timestamp, and exact body with periods between them. The signature header uses v1= followed by the SHA-256 HMAC in hexadecimal.
The client helper uses a five-minute timestamp tolerance and constant-time signature comparison. After verification, persist the event ID and your intended work in one transaction before acknowledging it. Duplicate deliveries should not create duplicate business actions. A changed delivery timestamp does not make a repeated event new.
import { verifyWebhook } from '@sanctionskit/client';
const rawBody = await request.text();
const valid = verifyWebhook(
process.env.SANCTIONSKIT_WEBHOOK_SECRET!,
request.headers.get('Webhook-Id') ?? '',
request.headers.get('Webhook-Timestamp') ?? '',
rawBody,
request.headers.get('Webhook-Signature') ?? ''
);
if (!valid) return new Response('Invalid signature', { status: 400 });
// Persist the event and durable work before returning 2xx.KEEP BUILDING