Webhooks
Get told when a login session is scanned, approved or denied, instead of polling.
Set a webhook when you create a login session:
{
"webhookUrl": "https://example.com/idfier/webhook",
"webhookSecret": "a-random-string-of-at-least-16-characters"
}| Field | Description |
|---|---|
webhookUrl | An https URL, at most 500 characters |
webhookSecret | Required with webhookUrl, 16–255 characters; signs each event |
Events
IDfier POSTs JSON to your URL:
{ "event": "authentication.completed", "referenceId": "550e8400-…", "status": "approved" }event | status | When |
|---|---|---|
authentication.started | started | The customer scanned the QR code |
authentication.completed | approved | The customer approved. From an EU wallet, the event also has "method": "eudi_wallet" |
authentication.denied | denied | The customer declined |
An event never contains the customer's data. Fetch it with
POST /business/auth/result, as you would when
polling, and keep the browser binding of your
login flow.
Verify the signature
Each request has an x-idfier-signature: sha256=<hex> header: the
HMAC-SHA256 of the raw request body with your webhookSecret. Check it
before trusting the event:
import { createHmac, timingSafeEqual } from 'node:crypto';
export function isFromIdfier(rawBody: string, header: string | null, secret: string) {
const expected = `sha256=${createHmac('sha256', secret).update(rawBody).digest('hex')}`;
return header !== null && header.length === expected.length && timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}Compute it over the body exactly as received, before parsing the JSON.
Delivery
- Answer with a
2xxwithin 15 seconds. - On a timeout, a network error or a
5xx, IDfier tries again after 2 and 4 seconds: at most 3 attempts in all. A4xxis not retried. - Events can arrive late or not at all, so treat them as a hint: the result endpoint is the source of truth, and polling is a fine fallback.