Frameworks
React, Next.js, Vue, Nuxt, Svelte, Angular, plain HTML — one widget.
Every binding wraps the same loader, so options and events are identical.
All of them take baseUrl when you run your own Verifyme instance.
React
"use client"
import { VerifyWidget } from "@cubis/verifyme/react"
export function PhoneStep({ token }: { token: string | null }) {
return (
<VerifyWidget
token={token} // null is fine: the loader starts early, the widget opens when set
onSuccess={({ proof }) => complete(proof)}
onError={({ code }) => code === "session_expired" && restart()}
/>
)
}Need your own chrome around it? Use the hook:
import { useVerifyWidget } from "@cubis/verifyme/react"
const { ref, status, error } = useVerifyWidget({ token, onSuccess })
return (
<>
{status === "loading" && <Spinner />}
<div ref={ref} />
</>
)Callbacks are kept in a ref, so re-renders never restart the verification.
Next.js: use it in a Client Component ("use client"); create the session in
a Route Handler or Server Action.
Vue and Nuxt
<script setup lang="ts">
import { VerifyWidget } from "@cubis/verifyme/vue"
const token = ref<string | null>(null)
onMounted(async () => {
token.value = (await $fetch("/api/verify/start", { method: "POST" })).token
})
</script>
<template>
<ClientOnly> <!-- Nuxt only -->
<VerifyWidget :token="token" @success="({ proof }) => complete(proof)" />
</ClientOnly>
</template>Composable form: const { el, status } = useVerifyWidget({ token, onSuccess })
with <div ref="el" />. token, theme and labels may be refs.
Svelte, Angular, Solid, anything else
The loader defines a <verifyme-widget> custom element:
<script>
import { onMount } from "svelte"
import { loadWidgetScript } from "@cubis/verifyme/browser"
export let token
onMount(() => loadWidgetScript())
</script>
<verifyme-widget {token} on:verifyme-success={(e) => complete(e.detail.proof)} />Events: verifyme-ready, verifyme-success ({ proof, expiresIn }),
verifyme-error ({ code, message }), verifyme-close. Set theme and
labels as properties. Changing token starts over; removing the element
closes it.
Modal instead of inline
Omit the container and the widget opens as an accessible modal (Esc and backdrop close it, focus returns afterwards):
import { openWidget } from "@cubis/verifyme/browser"
const widget = await openWidget({
token,
width: 400, // 280–640
onSuccess: ({ proof }) => complete(proof),
onClose: () => {},
})
widget.close()Options
| Option | Default | |
|---|---|---|
token | — | vw_… from your server |
container | modal | selector or element |
baseUrl | https://verifyme.cubis.tech | your instance |
theme · labels | saved branding | see Customize |
height | 320 | initial px, avoids layout shift |
autoClose | true | modal closes after success |
version · integrity · nonce | — | pin the loader, SRI, CSP nonce |
Performance
- The React and Vue bindings fetch the loader on mount, in parallel with
your session request. Elsewhere call
loadWidgetScript()early. - The loader is a few KB and cached; the frame's assets are content-hashed and immutable, so a return visit downloads nothing but the page.
- Set
heightto your widget's usual height to avoid a jump.
Pin and lock the loader
For a strict CSP or change control, pin the exact file with Subresource
Integrity. GET /widget/v1/integrity returns each file's version and
integrity:
<VerifyWidget token={token} version="8ef6e2b8c38d" integrity="sha384-…" nonce={cspNonce} />Unpinned is the right default: fixes reach you within five minutes.