SDKs

Two small open-source packages (MIT, no dependencies) wrap the steps above: @secuveri/web opens the flow in your page, and @secuveri/node creates sessions, verifies signed results and checks webhooks on your server. The browser callbacks only say that the person finished, cancelled or that the flow could not load. Base decisions on the webhook and the signed result.

Browser: @secuveri/web

TypeScript
// npm install @secuveri/web
import { Secuveri } from "@secuveri/web";

// sessionUrl: the `url` your backend got from POST /v1/sessions
const flow = Secuveri.start({
  sessionUrl,
  mode: "modal", // or "inline" with container: "#verify"
  language: "de",
  onComplete({ verificationId, status }) {
    // Finished. Ask your server for the signed result.
  },
  onCancel() {
    // Closed before finishing.
  },
  onError({ code }) {
    // "load_timeout": usually this website is not listed under Settings → Branding.
  },
});
  • Messages are accepted only from the frame itself, from the session link's exact origin, in the secuveri format.
  • The modal keeps focus inside, closes with Escape or its close button, and takes the whole screen on phones.
  • The frame may use the camera (allow="camera; microphone; fullscreen; clipboard-write; accelerometer; gyroscope"). With a Content Security Policy, allow the verification origin in frame-src.
  • sessionToken with verificationId works instead of sessionUrl; set verifyUrl for a custom verification domain.

Server: @secuveri/node

TypeScript
// npm install @secuveri/node   (Node 20+, Bun, Deno, edge runtimes)
import { Secuveri, verifyWebhook } from "@secuveri/node";

const secuveri = new Secuveri({
  apiKey: process.env.SECUVERI_API_KEY, // sk_test_… or sk_live_…
  organizationId: process.env.SECUVERI_ORGANIZATION_ID, // results must be issued to you
});

// 1. Create a session; hand session.url to the browser.
const session = await secuveri.createSession(
  { workflow_id: "<published workflow id>", vendor_data: "order-1042" },
  { idempotencyKey: "order-1042" }
);

// 2. Webhook: check the signature over the raw body.
const event = await verifyWebhook(rawBody, request.headers.get("secuveri-signature"), process.env.SECUVERI_WEBHOOK_SECRET);

// 3. The signed result, verified against /.well-known/jwks.json.
const result = await secuveri.getResult(event.data.verification_id);
if (result.verification.status === "COMPLETE" && result.verification.verdict === "APPROVED") {
  // go ahead; keep result.jws as evidence
}

getResult throws with status 409 while the verification is still running. verifyWebhook refuses signatures older than 5 minutes and returns the parsed event. Both throw a typed error with a code you can log.

Examples

  • age-check-shop: a Next.js shop. An 18+ item opens the age check at checkout, and the order is placed only once the signed result confirms it (Swiss Tobacco Products Act).
  • bank-onboarding: a neo-bank and crypto signup with full KYC inline (document, liveness, AML), and a dashboard built from the signed result and expected_details.
  • webhook-receiver: a single-file Node/Bun server that checks signatures and reads the signed result.
  • All three are in the public repository github.com/secuveri/examples and run against the sandbox with a sk_test_ key.