Overview
A hosted, branded code-entry UI — embed it, then redeem the proof on your server.
The widget is the fastest safe integration: Verifyme renders the code form in an iframe, sends and resends the code, and hands your page a single-use proof. Your server redeems the proof to learn who was verified.
1. Your server ── createSession (API key) ──▶ Verifyme → widget_token
2. Your page ── <VerifyWidget token> ─────▶ iframe → proof
3. Your server ── redeem(proof) ────────────▶ Verifyme → verified ✓Setup
In Console → Widget → Setup, add every site origin that will show the
widget, e.g. https://app.example.com (exact, no wildcards). Test keys also
accept http://localhost:*.
Create a session (server)
import { Verifyme } from "@cubis/verifyme"
const verifyme = Verifyme.fromEnv()
export async function POST(req: Request) {
const user = await requireUser(req) // your auth
const { phone } = await req.json()
const session = await verifyme.widget.createSession({
target: phone,
origin: "https://app.example.com", // must be allowlisted
client_reference: user.id, // comes back on redeem
})
await saveForUser(user.id, session.session_id) // keep it server-side
return Response.json({ token: session.widget_token }) // only this goes to the page
}Options: provider (sms default, email, whatsapp, telegram),
length (4–8, default 6), timeout (10–300 s, default 300), template,
callback_url. The widget token lives 15 minutes.
Show the widget (browser)
import { VerifyWidget } from "@cubis/verifyme/react"
<VerifyWidget token={token} onSuccess={({ proof }) => complete(proof)} />More frameworks, modals and options: Frameworks.
Add frame-src https://verifyme.cubis.tech to your Content-Security-Policy.
Redeem the proof (server)
export async function POST(req: Request) {
const user = await requireUser(req)
const { proof } = await req.json()
const v = await verifyme.widget.redeem(proof) // throws if invalid, used or expired
// The proof must belong to *this* user's session.
if (v.session_id !== (await loadForUser(user.id)) || v.client_reference !== user.id) {
return Response.json({ verified: false }, { status: 403 })
}
await markPhoneVerified(user.id, v.target)
return Response.json({ verified: true })
}A proof is single-use, valid 5 minutes, and bound to your business and mode.
The browser's onSuccess is only a hint
Anyone can call your page's success handler. Only a proof redeemed by your server means verified.
Limits
5 wrong codes burn a code; 15 checks per session; up to 3 resends, 30 s apart. A refused resend (throttle, outage) does not use one up.
Events and errors
onError / verifyme-error receive { code, message }:
code | Meaning | What to do |
|---|---|---|
invalid_code | Wrong digits, attempts remain | Nothing — the widget asks again |
code_spent | That code is used up | The widget offers a resend |
session_expired · session_invalid · too_many_checks | Session over | Create a new session |
rate_limited | Throttled | Wait and retry |
load_timeout · load_failed | Frame or script did not load | Check CSP frame-src / network |