Webhooks
Get a signed server-to-server callback when a widget verification succeeds.
Webhooks are optional — redeeming the proof is the reliable path. Use a webhook when a backend job, not the user's request, should react.
Setup
In Console → Widget → Setup, set a callback URL (public https) and generate
the signing secret (whsec_…, shown once). A session can override the URL with
callback_url.
Payload
POST /your/webhook
X-Verifyme-Event: verification.succeeded
X-Verifyme-Delivery: 5b0c… # same across retries — deduplicate on it
X-Verifyme-Signature: t=1767225600,v1=9f86d0…
{"event":"verification.succeeded","session_id":"…","verification_id":"…",
"business_id":"…","mode":"live","target":"+855…","client_reference":"user_123",
"verified_at":"2026-01-01T00:00:00Z"}Verify the signature
v1 = HMAC-SHA256(secret, t + "." + rawBody). Always use the raw body.
import { parseWebhook, type WidgetCallbackEvent } from "@cubis/verifyme"
export async function POST(req: Request) {
const event = await parseWebhook<WidgetCallbackEvent>({
payload: await req.text(),
signature: req.headers.get("x-verifyme-signature") ?? "",
secret: process.env.VERIFYME_WEBHOOK_SECRET!,
}) // throws on a bad or older-than-5-minutes signature
await markVerified(event.client_reference, event.target)
return new Response(null, { status: 204 })
}Delivery
Any 2xx counts as delivered. Failures retry at +5 s and +30 s; redirects are
never followed. Respond fast and do slow work in a queue. For local
development use a tunnel, or skip webhooks and redeem.