VerifymeDocs

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.

On this page