# 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

```js
// 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

```js
// 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.
