Two ways to receive events
Use the webhook as the main path and the feed as the safety net. An event is confirmed once, by whichever way gets there first, and then stops coming from the other: a 2xx removes it from the feed, and a pull that confirms it cancels the webhook that was waiting. If you run both, let your pull wait about a minute before confirming new events, so the webhook gets its turn.
Which one delivered it? The delivery log shows
delivered_via: webhook or feed.
Your URL is yours to choose. Register any HTTPS URL, such as https://ats.example.com/hooks/record or https://example.com/api/record-results. Record posts to exactly that URL.
How the pull loop works
Say 5 events are waiting. Your first pull returns all 5. If you never confirm them, the next pull returns the same 5 again. Once you pass their ids inack, they are gone and you get only newer ones.
- Pull with
ack: []. You get the events, each with anevent_id. - Save them. Skip an
event_idyou already saved. - Pull again with those ids in
ack. They are confirmed and the next events come back. - Stop when a pull returns no events.
When an event is sent
Once, when a candidate reachescompleted, declined or failed and their result has stayed the same for about 45 seconds (the last questions are answered after the skill result exists, so Record waits instead of sending one event per answer). If a finished result later changes (a re-grade), you get a new event with a higher version for the same candidate.
What your server must do
- Verify the signature (below), then answer 2xx within 10 seconds. That is the acknowledgement. Process the event after answering.
- Keep the
event_idof what you processed and skip repeats: delivery is at-least-once, so the same event can arrive twice. - Keep the highest
versionper candidate; ignore an older one that arrives late. - Any other answer, a timeout or a connection error means “not received”: Record retries.
Retries and nothing lost
Verify the signature
Each delivery hasRecord-Signature: t=<seconds>,v1=<hex>. Compute HMAC-SHA256 of <t>.<raw request body> with your API secret key (VFN_SK_…, the same one you sign your own calls with) and compare to v1. Reject if t is more than 5 minutes old. Nothing else to configure.
Webhook endpoints
Create an endpoint
POST /webhooks/endpoints
Register the HTTPS URL that receives result events. The path is up to you. Deliveries are signed with your API secret key.
Scope: webhooks:manage · Idempotency-Key: optional
data.endpoint.endpoint_id for the next step.
Can also return idempotency errors (
IDEMPOTENCY_IN_PROGRESS, IDEMPOTENCY_KEY_REUSED) and the common errors. Full schema
List endpoints
GET /webhooks/endpoints
Each endpoint with its status (active, paused, auto_paused), consecutive failures and last success.
Scope: webhooks:manage
Get an endpoint
GET /webhooks/endpoints/{endpoint_id}
One endpoint with its current status.
Scope: webhooks:manage
Can also return the common errors. Full schema
Update an endpoint
PATCH /webhooks/endpoints/{endpoint_id}
Send only what changes. status: paused holds deliveries (nothing is lost); status: active resumes them, oldest first. Use it to bring back an auto_paused endpoint.
Scope: webhooks:manage · Idempotency-Key: optional
Can also return idempotency errors (
IDEMPOTENCY_IN_PROGRESS, IDEMPOTENCY_KEY_REUSED) and the common errors. Full schema
Delete an endpoint
DELETE /webhooks/endpoints/{endpoint_id}
Deliveries still waiting for it stop. The events stay in the feed for 30 days.
Scope: webhooks:manage · Idempotency-Key: optional
Can also return idempotency errors (
IDEMPOTENCY_IN_PROGRESS, IDEMPOTENCY_KEY_REUSED) and the common errors. Full schema
Send a test event
POST /webhooks/endpoints/{endpoint_id}/test
Sends a signed webhook.test right now and shows what your server answered. Not stored, not retried.
Scope: webhooks:manage
Can also return the common errors. Full schema
Backfill past results
POST /webhooks/endpoints/{endpoint_id}/backfill
Queues the results since from (at most 30 days back) as normal deliveries. Safe to repeat.
Scope: webhooks:manage · Idempotency-Key: optional
Can also return idempotency errors (
IDEMPOTENCY_IN_PROGRESS, IDEMPOTENCY_KEY_REUSED) and the common errors. Full schema
List deliveries
GET /webhooks/endpoints/{endpoint_id}/deliveries
Every send to this endpoint, newest first: attempts, last HTTP status or error, when the next retry is due.
Scope: webhooks:manage
Can also return the common errors. Full schema
Retry a delivery
POST /webhooks/deliveries/{delivery_id}/retry
For a dead one after you fixed your endpoint. Its attempts count from zero again.
Scope: webhooks:manage · Idempotency-Key: optional
Can also return idempotency errors (
IDEMPOTENCY_IN_PROGRESS, IDEMPOTENCY_KEY_REUSED) and the common errors. Full schema
Event feed
Pull and confirm events
POST /events/pull
One call does both jobs. Send the ack ids of the events you already processed, and get the next events back. An event keeps coming back until you put its id in ack.
Scope: events:read
Can also return the common errors. Full schema
Get an event
GET /events/{event_id}
Fetch one event by its id.
Scope: events:read
Can also return the common errors. Full schema

