Verificate
Menu

The Verificate widget

For signup pages. It shows the code the customer scans with their phone, and follows the verification as it happens: the link opened, each step done, the decision.

It is 12 KB over the wire, needs no framework, and keeps its styles to itself.

What your server does first

The widget is given two values that your server gets when it creates the session:

From the session For the widget
client_secret clientSecret Gives progress and nothing else. Meant for the browser
url url The link for the phone. Give it to show the code. Leave it out if you send the link by message and want progress only

Your API key stays on the server. It never goes to the page.

On the page

<div id="verify"></div>

<script src="https://api.verificate.io/sdk/v1/verificate.js"></script>
<script>
  const widget = Verificate.mount("#verify", {
    clientSecret: "vs_…_secret_…",
    url: "https://verificate.me/v#vs_….…",
    onFinish(status) {
      if (status === "approved") location.assign("/welcome");
    },
  });
</script>

As a module:

<script type="module">
  import { mount } from "https://api.verificate.io/sdk/v1/verificate.mjs";
  mount("#verify", { clientSecret, url });
</script>

Settings

clientSecret Required
url The session’s link. With it the widget shows the code, and a button that copies the link
locale en, he or es. Left out, the widget speaks the language the customer chose on their phone, and before that the session’s own
baseUrl Where the service lives. Left out, it is where the script was loaded from, and for a copy bundled into your own code https://api.verificate.io. It is never taken from url: the link is for the customer’s phone and lives on verificate.me
onChange(snapshot) Called with every change, and once at the start
onFinish(status, snapshot) Called when the session is approved, rejected, needs_review, expired or canceled
onProblem(problem) "not_found": the secret matches no session. "offline": the service cannot be reached, and the widget keeps trying. null: the trouble has passed

mount returns the widget:

widget.snapshot The latest that is known, or null before the first answer
widget.destroy() Takes the widget off the page and stops following the session

The element also sends two events, which bubble: verificate:change and verificate:finish. Their detail is the snapshot.

A session held for review

onFinish("needs_review") means a person will decide, which can take hours. Let the customer go on, and do not make them wait on the page. The decision reaches your server by webhook. If the page is still open when it comes, onFinish is called again with approved or rejected.

What a snapshot holds

{
  "type": "snapshot",
  "session_id": "vs_7X11LVCKb7GxwD5j6GMZHn",
  "status": "in_progress",
  "mode": "live",
  "locale": "en",
  "opened": true,
  "steps": [
    { "index": 0, "type": "id_document", "status": "completed", "verification": "pending" },
    { "index": 1, "type": "selfie", "status": "in_progress", "verification": "pending" }
  ],
  "branding": { "name": "Acme", "logo_url": null, "primary_color": "#0b6e5f", "support_url": null, "privacy_url": null },
  "expires_at": "2026-09-30T09:40:00.000Z",
  "updated_at": "2026-09-29T09:42:31.000Z"
}

No names, no pictures, no reasons. A page cannot learn from the widget why a session was held back or turned down. Your server can, with its key.

Without the widget’s looks

To draw progress yourself:

const following = Verificate.watch({
  clientSecret,
  baseUrl: "https://api.verificate.io",
  onChange(snapshot) { render(snapshot); },
  onFinish(status) { … },
});

following.stop();

Looks

The widget takes the typeface of the page around it, and the tenant’s colour, name and logo as they are set at the review desk. Its styles live in a shadow root: the page’s styles do not reach in, and the widget’s do not reach out.

Four properties can be set on the element the widget is mounted on:

#verify {
  --verificate-color: #1f4f8f;   /* used when the tenant has set no colour */
  --verificate-ground: #ffffff;  /* background */
  --verificate-ink: #101c2a;     /* text */
  --verificate-radius: 14px;     /* corners */
}

The code itself is always black on white. Cameras read that best.

Pages with a content security policy

The widget needs:

script-src  https://api.verificate.io
connect-src https://api.verificate.io wss://api.verificate.io
img-src     https:            (only if the tenant has set a logo)

It needs no 'unsafe-inline', for scripts or for styles, and it works with require-trusted-types-for 'script'.

How changes arrive

Over a WebSocket. When that cannot be had, or drops, the widget asks for the status every three to ten seconds and tries the socket again, after waits that grow from two seconds to thirty. The customer is told only when the service cannot be reached at all.

The widget stops following once the session is approved, expired or canceled. It keeps following a session that is rejected or held for review, because a reviewer can still change that.