Skip to content
Sekyro AI

Build verification into your service

A no-code hosted link, or the API from your server: pick the integration that suits your team, test it in Sandbox, then go Live.

Hosted or embedded?

The four ways to integrate Sekyro AI, side by side. Start with the simplest: you can add another later on the same app.

Integration options compared
CriterionHosted linksServer APIWeb and ReactiOS and Android
AvailabilityAvailableAvailableTo be confirmedPlanned
Code to writeNoneYour serverYour server and your siteYour server and your app
Where the person verifiesA Sekyro AI hosted page, opened from a linkThe session’s hosted pageInside your siteInside your app
Platform to declareNone — an app without platforms serves hosted linksNoneWeb: allowed originsiOS: Team ID and bundle ID · Android: package and SHA-256 fingerprints
API keyNo: links are created in the dashboardYes, server-side onlyYes, server-side onlyYes, server-side only

Scroll the table sideways

From invitation to first session

An integration, in the order the dashboard proposes it. Each step says who can do it.

  1. Get into the dashboard

    Your organization is opened by the Sekyro AI team. An admin invites you with a role; accept with the invited address.

    All roles

  2. Create an app

    Name it and pick its platforms. Web needs allowed origins, iOS and Android need identifiers. With no platform, the app serves the API and hosted links.

    AdminDeveloper

  3. Choose the eKYC profile

    Start from a preset and check the step preview. Registry access depends on your organization’s entitlement.

    AdminDeveloper

  4. Create a Sandbox key

    An admin creates the app key. The secret is shown once: copy it into your secrets manager.

    Admin

  5. Create a test session

    With the quickstart below or a hosted link, then inspect the session — and any review — in the dashboard.

    AdminDeveloper

  6. Prepare Live

    Once Live is open for your organization, create a Live key and replace the Sandbox key on your server.

    Admin

Your first session in one call

Create a session from your server with one of your app’s Sandbox keys.

  • The key stays on your server: never in a web page or a mobile app.
  • A key starting with sk_test_ works in Sandbox, sk_live_ in Live.
  • The request body passes the national identification number when the profile uses the registry.
Full quickstart guide
Create a sessionserver-create.ts
// Next.js Server Actions (App Router) backed by @sekyro/node.
//
// Two jobs live on YOUR server, never in the browser:
//   1. startVerification — create a session BOUND to the user's NNI and hand
//      the page nothing but the one-time client token.
//   2. decideVerification — read the authoritative session and decide.
//
// The API key is a server secret. The client token is the end user's flow
// credential: returned once by `sessions.create`, never logged, never stored
// in a cookie or URL.
"use server";

import { Sekyro, SekyroApiError, type Session } from "@sekyro/node";

const sekyro = new Sekyro({
  apiKey: requireEnv("SEKYRO_API_KEY"),
  // Production is the default. For the sandbox set
  // SEKYRO_API_BASE_URL=https://api-staging.sekyro.ai/api/v2 with an sk_test_ key.
  baseUrl: process.env.SEKYRO_API_BASE_URL ?? "https://api.sekyro.ai/api/v2",
});

export interface StartVerificationResult {
  sessionId: string;
  /** Hand this to the page (`createVerification({ clientToken })`). Returned ONCE. */
  clientToken: string;
  /** ISO 8601 — the capture must finish before this. */
  expiresAt: string;
}

/**
 * Create a session bound to `nni`. The browser never sends the NNI: the SDK
 * submits `population_registry` with an empty input and the server uses the
 * bound value (a browser that sends a DIFFERENT nni gets 409 `nni_locked`).
 *
 * `attempt` is your own counter per user: the idempotency key makes a network
 * retry of the SAME attempt return the SAME session instead of minting two.
 */
export async function startVerification(
  userId: string,
  nni: string,
  attempt: number,
): Promise<StartVerificationResult> {
  try {
    const created = await sekyro.sessions.create(
      // No recipe here: the API key belongs to an app, and the app's eKYC
      // profile decides the steps (e.g. population_registry → liveness →
      // face_match for the "Mauritania: registry" profile).
      { locale: "ar", input: { nni } },
      { idempotencyKey: `kyc:${userId}:${attempt}` },
    );
    // Persist the id BEFORE returning: the webhook can arrive before the page
    // reports back, and your handler must be able to find the user by session.
    await saveSessionId(userId, created.id);
    return {
      sessionId: created.id,
      clientToken: created.clientToken,
      expiresAt: created.expiresAt,
    };
  } catch (err) {
    if (err instanceof SekyroApiError) {
      // `retryable` covers network, timeout, 429, 5xx and idempotency_in_progress:
      // back off and call again with the SAME idempotencyKey.
      throw new Error(
        `Sekyro AI ${err.code} (request ${err.requestId ?? "n/a"}), retryable=${err.retryable}`,
      );
    }
    throw err;
  }
}

export type Decision = "approved" | "rejected" | "pending" | "expired";

export interface DecideResult {
  decision: Decision;
  session: Session;
}

/**
 * The ONLY place a verdict is decided. Call it when the page reports
 * `onResult`, and again from your webhook handler on async transitions.
 */
export async function decideVerification(sessionId: string): Promise<DecideResult> {
  const session = await sekyro.sessions.get(sessionId);
  return { decision: decide(session), session };
}

function decide(session: Session): Decision {
  switch (session.status) {
    case "completed":
      // `outcome` is `approved | review | rejected`; `review` here means a
      // human decided the session is complete but flagged — treat as pending
      // until your own policy says otherwise.
      if (session.outcome === "approved") return "approved";
      if (session.outcome === "rejected") return "rejected";
      return "pending";
    case "failed":
      return "rejected";
    case "review_pending":
      // Parked for human review (up to 48 h). Wait for `session.review_decided`.
      return "pending";
    case "expired":
      // The user did not finish in time: offer a new session.
      return "expired";
    case "created":
    case "in_progress":
      return "pending";
  }
}

function requireEnv(name: string): string {
  const value = process.env[name];
  if (value === undefined || value === "") throw new Error(`${name} is not set`);
  return value;
}

// Replace with your database write.
async function saveSessionId(_userId: string, _sessionId: string): Promise<void> {}

Dashboard access

Already have access? Sign in with Ibtikar ID to manage your apps, keys and sessions.

No access yet? Your organization gets it from the Sekyro AI team, then an admin invites you.

Let’s talk about your verification journey

Describe your service and the people you need to verify. The Sekyro AI team helps you choose an integration and opens your Sandbox access.