Webhooks
Codes, pushed.
Instead of polling, give us a URL and we'll POST a signed event the moment a code arrives, an order expires or is cancelled, or a rented number gets an SMS.
Set up an endpoint
- In the dashboard, open Settings and enter your webhook URL. In production it must be
https://on a public host. - We generate a signing secret (
whsec_…) the first time. It stays the same when you change the URL; rotate it from the same page. - Press Send test event to receive a
webhook.testevent and check your signature verification end to end.
One URL per account receives every event type; switch on type.
What we send
A POST with a JSON body — the event envelope — and these headers:
POST /webhooks/passcode HTTP/1.1
Content-Type: application/json
User-Agent: passcode.sh-webhooks/1
X-Passcode-Signature: t=1791381739,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
X-Passcode-Event: order.code_received
X-Passcode-Delivery: evt_6Jt2WqR9mK4xN7bLc3Vp- id
- Unique event id (
evt_…), also inX-Passcode-Delivery. Use it to skip duplicates. - type
- The event type, also in
X-Passcode-Event. - created_at
- When the event was created (ISO 8601, UTC).
- data
- The payload.
order.*events carry the full Order, exactly as the API returns it.
{
"id": "evt_6Jt2WqR9mK4xN7bLc3Vp",
"type": "order.code_received",
"created_at": "2026-10-06T14:02:19.412Z",
"data": {
"order": {
"id": "ord_7Hq2LmX9vB3kR8tN4cYp",
"status": "received",
"service": {
"slug": "whatsapp",
"name": "WhatsApp"
},
"country": {
"iso2": "US",
"name": "United States",
"dial_code": "+1"
},
"phone_number": "+12025550147",
"price": "0.895",
"price_micros": 895000,
"code": "482913",
"messages": [
{
"id": "msg_4mQ8rXc2LpV7nZ1kT9bW",
"from": "32665",
"body": "Your WhatsApp code: 482-913\nDon't share this code with others",
"code": "482913",
"received_at": "2026-10-06T14:02:19.000Z"
}
],
"strategy": "best",
"source": "api",
"created_at": "2026-10-06T14:02:03.000Z",
"expires_at": "2026-10-06T14:22:03.000Z",
"received_at": "2026-10-06T14:02:19.000Z",
"completed_at": null,
"cancelled_at": null,
"refunded_at": null,
"can_cancel": false,
"can_finish": true,
"demo": true
}
}
}Responding and retries
- Acknowledge
- Any 2xx status within 5 seconds counts as delivered.
- Retries
- One retry, about a second later, after a timeout, a network error, a 429 or a 5xx. Other statuses aren't retried.
- Redirects
- Not followed. Point the URL at the final destination.
- Ordering
- Not guaranteed. Use created_at and the order's own status, not arrival order.
- Log
- Every attempt is recorded on our side (the last 200 per account).
Don't rely on webhooks alone
Delivery is retried only once. Keep polling as a fallback for orders you’re actively waiting on — for example, check any order still
waiting a minute after its last event.Best practices
- Verify every signature before trusting the body — see Verifying signatures.
- Respond fast. Return 2xx as soon as the event is stored; do slow work afterwards.
- Deduplicate on the event
id. A retry after a slow response can deliver the same event twice. - Expect new event types and fields. Ignore what you don’t recognise.