Skip to content

Webhooks

Webhooks send an HTTPS POST to your server when something happens, so you do not have to poll.

EventSent whendata
alert.createdA detector raises an alertalert_id, event_id
screening.potential_matchA payee screening needs reviewscreening_id, event_id
pack.readyA pack has finished buildingpack_id
{
"type": "alert.created",
"created_at": "2026-10-17T10:05:12.440Z",
"data": { "alert_id": "alr_01JA8X4K3T9V2C7M5N6P8R0S1W", "event_id": "evt_01JA8Q4K3T9V2C7M5N6P8R0S1W" }
}

The body is small on purpose. Fetch the full record with the id, using GET /v1/alerts/{id} for an alert or GET /v1/packs for a pack. That way a webhook never carries data you did not ask for.

HeaderValue
HalvorAI-Signaturev1= followed by a hex HMAC-SHA256
HalvorAI-TimestampThe time the delivery was made, as an RFC 3339 timestamp
Content-Typeapplication/json

Always verify a delivery before you act on it. The signature is an HMAC-SHA256, keyed with your endpoint secret, over the timestamp, a dot, and the exact raw request body.

import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyHalvorWebhook(secret: string, timestamp: string, rawBody: string, signature: string): boolean {
const expected = `v1=${createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')}`;
const a = Buffer.from(expected);
const b = Buffer.from(signature);
const fresh = Math.abs(Date.now() - Date.parse(timestamp)) < 5 * 60_000;
return fresh && a.length === b.length && timingSafeEqual(a, b);
}
  • Use the raw body bytes, not a re-serialized copy.
  • Compare in constant time.
  • Reject a timestamp more than five minutes old, which stops replays.

Return any 2xx status to acknowledge a delivery. HalvorAI treats a 5xx, a 429, or a timeout as a failure and retries up to five times, waiting 0.5, 1, 2 and 4 seconds between attempts. A 4xx other than 429 is treated as a refusal and not retried.

Deliveries can repeat, and can arrive out of order. Make your handler idempotent by using the ids in data, and fetch current state rather than trusting the order.

Webhook endpoints and their secrets are configured per organization. A secret is at least 32 characters, and a new endpoint needs one. Ask your HalvorAI contact to add or change an endpoint during the pilot; self-service setup in the dashboard is planned.