Webhooks
Webhooks push verification outcomes to your server the moment they happen. Register
an endpoint once; idcheck.now POSTs signed JSON events to it. Polling
GET /v1/verifications/{id} works too, but webhooks are the recommended path.
Create an endpoint
To register a webhook, POST /v1/webhooks with your HTTPS url and the
events to subscribe to ("*" subscribes to everything). The response
includes the endpoint secret used to verify delivery signatures —
it is returned only here, at creation.
Request body
| Field | Type | Description |
|---|---|---|
| urlrequired | string (URL) | HTTPS endpoint that accepts POST. 10-second delivery timeout. |
| eventsrequired | string[] | Event names, or ["*"] for all events. |
curl -X POST https://idcheck.now/v1/webhooks -H "X-Api-Key: $IDCHECK_API_KEY" -H "Content-Type: application/json" -d '{"url": "https://yourapp.example/hooks/idcheck", "events": ["verification.decided"]}'const res = await fetch("https://idcheck.now/v1/webhooks", {
method: "POST",
headers: { "X-Api-Key": process.env.IDCHECK_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ url: "https://yourapp.example/hooks/idcheck", events: ["verification.decided"] }),
});
const { id, secret } = await res.json(); // store secret — shown onceimport os, requests
res = requests.post(
"https://idcheck.now/v1/webhooks",
headers={"X-Api-Key": os.environ["IDCHECK_API_KEY"]},
json={"url": "https://yourapp.example/hooks/idcheck", "events": ["verification.decided"]},
)
endpoint = res.json() # store endpoint["secret"] — shown once{
"id": 4,
"url": "https://yourapp.example/hooks/idcheck",
"events": ["verification.decided"],
"secret": "9f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a39281706f5e4d3c2b1a0",
"active": true
}List endpoints
To list your webhook endpoints, GET /v1/webhooks. Secrets are never included.
[
{"id": 4, "url": "https://yourapp.example/hooks/idcheck", "events": ["verification.decided"], "active": true}
]Delete an endpoint
To stop deliveries, DELETE /v1/webhooks/{endpoint_id}. The endpoint is deactivated
(soft delete) and the response is 204 No Content. Endpoints owned by another key return
404 webhook not found.
curl -X DELETE https://idcheck.now/v1/webhooks/4 -H "X-Api-Key: $IDCHECK_API_KEY"Event catalog
| Event | Fired when | Payload (<code>data</code>) |
|---|---|---|
verification.decided | A verification reaches status decided (at POST /v1/session/submit). | {"decision": "approve|review|decline", "score": number, "reasons": string[]} |
Subscribe with the exact event name or * for all current and future events. After
receiving an event, fetch full details (checks, reasons, audit log) with
GET /v1/verifications/{verification_id}.
Payload format
Deliveries are POST requests with a JSON body and two headers:
Content-Type: application/json and X-IdCheck-Signature.
{
"event": "verification.decided",
"verification_id": "b3f1c2a4-7d5e-4f6a-9c8b-1e2d3a4b5c6d",
"data": {
"decision": "approve",
"score": 96.2,
"reasons": []
}
}Verify signatures
Every delivery is signed with the endpoint secret. Header format:
X-IdCheck-Signature: t=<unix_ts>,v1=<hmac_sha256_hex> where the HMAC input is
"<t>.<raw_body>". Full verification code (Node.js + Python, constant-time
comparison, replay-window check) is in
Authentication → Webhook signatures.
// Express: capture the RAW body before JSON parsing
app.post("/hooks/idcheck", express.raw({ type: "application/json" }), (req, res) => {
const ok = verifyIdCheckSignature(
req.body.toString("utf8"),
req.get("X-IdCheck-Signature"),
process.env.IDCHECK_WEBHOOK_SECRET
);
if (!ok) return res.sendStatus(401);
const { event, verification_id, data } = JSON.parse(req.body);
if (event === "verification.decided") { /* fetch full verification, update user */ }
res.sendStatus(200); // any 2xx marks the delivery as delivered
});# Flask: read the raw body, verify, then parse
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/hooks/idcheck")
def idcheck_hook():
ok = verify_idcheck_signature(
request.get_data(),
request.headers.get("X-IdCheck-Signature", ""),
os.environ["IDCHECK_WEBHOOK_SECRET"],
)
if not ok:
abort(401)
payload = request.get_json()
if payload["event"] == "verification.decided":
pass # fetch full verification, update user
return "", 200 # any 2xx marks the delivery as deliveredRetry policy
| Property | Value |
|---|---|
| Attempts | Up to 3 per delivery |
| Backoff between attempts | 0.5s, then 1.0s, then 2.0s |
| Per-attempt timeout | 10 seconds |
| Success criteria | Any response status < 300 marks the delivery delivered |
| Exhaustion | Delivery marked failed; fetch the result via GET /v1/verifications/{id} instead |
Return a 2xx immediately and do the heavy work (API calls, DB writes) after responding. A slow handler eats the 10-second timeout and burns retry attempts.