For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
SupportDashboard
GuidesAPI Reference
GuidesAPI Reference
  • Getting Started
    • Introduction
    • Authentication
    • Quickstart
  • Core Concepts
    • Prefixed IDs
    • Pagination
    • Error Handling
    • Rate Limits
    • Webhooks
  • Use Cases
    • ELP Tests
LogoLogo
SupportDashboard
On this page
  • Quickstart
  • 1. Register an endpoint
  • 2. Receive a delivery
  • 3. Verify the signature
  • Idempotency
  • Secret rotation
  • Reserved headers
  • Event catalog
  • Next steps
Core Concepts

Webhooks

Receive events as HTTPS POSTs to your endpoint. Standard Webhooks compliant.
Was this page helpful?
Previous

ELP Tests

Administer English Language Proficiency tests over the phone, then receive structured scores via webhook.
Next
Built with

Webhooks deliver events to your server the moment they happen, so you do not have to poll. Each delivery is a JSON POST to a URL you register, signed with HMAC-SHA256 so you can verify it came from us.

Our envelope and signature scheme implement the Standard Webhooks specification. Any verifier from that ecosystem will work out of the box.

Quickstart

1. Register an endpoint

Register from the dashboard at Organization → Webhooks. You supply the destination URL and pick the event types you want delivered. There is no wildcard, so subscribers opt in to each event type explicitly.

After creation you receive a signing secret of the form whsec_…. Store it immediately. It is not retrievable later. If you lose it, rotate it.

Organization → Webhooks panel showing a registered endpoint, its subscribed event types, status, and row actions

Programmatic management of endpoints (create, list, update, delete) is available in the API reference.

2. Receive a delivery

Every delivery is a POST to your registered URL with this envelope:

1{
2 "id": "msg_3f2504e0-4f89-41d3-9a0c-0305e82c3301",
3 "type": "elp_test.scored",
4 "timestamp": "2026-05-26T19:33:42Z",
5 "data": {
6 "id": "elp_550e8400-e29b-41d4-a716-446655440000",
7 "status": "COMPLETED",
8 "scoring_status": "SCORED",
9 "...": "full ELP test response"
10 }
11}

Three signing headers travel with the body:

HeaderExampleWhat it is
webhook-idmsg_3f2504e0-4f89-41d3-9a0c-0305e82c3301Stable per logical message. Same across retries.
webhook-timestamp1748287322Unix seconds when we signed the payload.
webhook-signaturev1,K8U3...base64hmac... v1,N9F2...Space-separated v1,<sig> per active secret.

Always respond with a 2xx within 15 seconds. Anything else is treated as a delivery failure and triggers a retry.

3. Verify the signature

Use the official Standard Webhooks library for your language. The verifier takes your whsec_… secret plus the request body and the three webhook-* headers, and tells you whether the signature is valid.

Python (standardwebhooks on PyPI):

1from standardwebhooks import Webhook
2
3wh = Webhook(signing_secret) # "whsec_..."
4payload = wh.verify(request_body, request_headers) # raises on failure

TypeScript / Node (standardwebhooks on npm):

1import { Webhook } from "standardwebhooks";
2
3const wh = new Webhook(signingSecret); // "whsec_..."
4const payload = wh.verify(requestBody, requestHeaders); // throws on failure

For any other language (Go, Ruby, PHP, Rust, Java, .NET, and more), pick the official SDK from standardwebhooks.com/#sdks. All follow the same interface: hand it the secret, body, and headers; get back the verified payload or an error.

Idempotency

webhook-id is stable across retries. If the same webhook-id arrives twice, it is the same logical event. Process once, ignore the rest.

on_webhook(headers, body):
msg_id = headers["webhook-id"]
if seen(msg_id):
return 200 # already processed; ack so we stop retrying
process(body)
record_seen(msg_id)
return 200

Retries are exponential with jitter. We retry on any non-2xx response or connection failure for up to ~24 hours, then give up. Every attempt is captured in the per-endpoint delivery log at Organization → Webhooks → {endpoint}. Expand a row to see the request payload and response. Failed and dead deliveries can be replayed from there.

Endpoint detail page. Delivery log with a mix of delivered and failed attempts; expand a row to see the response code and body.

Secret rotation

Rotate from the dashboard at Organization → Webhooks. Pick the endpoint row and select Rotate secret.

Rotation mints a fresh whsec_… secret and keeps the previous one valid for 24 hours. During the overlap window, webhook-signature includes one v1,<sig> per active secret, so verification with either succeeds. Update your stored secret to the new value any time within those 24 hours.

Rotate-secret modal showing the new whsec_… signing secret and the 24-hour grace window

Reserved headers

webhook-id, webhook-timestamp, and webhook-signature are always set by us. If you configure custom headers on your endpoint that collide with these names, the custom values are silently dropped at delivery time. That prevents an endpoint from being tricked into signing its own deliveries.

Event catalog

Event typeFires whenBody
elp_test.scoredA driver’s ELP test completes post-call scoring.Same shape as the get-ELP-test response. See the ELP Tests guide.
elp_test.failedA driver’s ELP test reaches a terminal failure state (call never connected, call ended without scoring, or scoring exhausted retries).Same shape as the get-ELP-test response. status / scoring_status / notes carry the failure detail.

The authoritative list of subscribable event types lives in the API reference.

Next steps

  • ELP Tests. Worked example using elp_test.scored.
  • Standard Webhooks spec. The underlying signing and envelope contract.