Backend

@okyc/node

The server SDK for Node 18+, Bun, Deno and edge runtimes — typed, retried, never logs bodies.

View as Markdown

Version0.1.1

Install

npm install @okyc/node
import { Okyc } from '@okyc/node';

const okyc = new Okyc(process.env.OKYC_SECRET_KEY!); // sk_test_… or sk_live_…
OptionDefault
baseUrlhttps://api.okyc.streamAPI origin, for a dedicated or self-hosted deployment. Must be https except on localhost.
apiVersion—Pins the OKYC-Version header.
timeoutMs30000Per request.
maxRetries3Network errors, 408, 429, 5xx; exponential backoff with jitter, honours Retry-After.
fetchglobal fetchCustom fetch implementation.
logger—Receives method, path, status, latency, attempt and request id — never bodies.

The constructor refuses anything that isn't a secret key, so a client token can't be used by mistake.

Sessions

server.mjs
// Your frontend calls this, then passes { clientToken, sessionUrl } to @okyc/web or @okyc/react.
app.post('/api/kyc/session', express.json(), async (req, res) => {
  const userId = req.header('x-user-id') ?? 'demo-user'; // use your own auth here
  try {
    const session = await okyc.sessions.create(
      {
        workflowId: process.env.OKYC_WORKFLOW_ID,
        referenceId: userId,
        redirectUrl: process.env.REDIRECT_URL,
      },
      { idempotencyKey: `kyc-${userId}-${new Date().toISOString().slice(0, 10)}` },
    );
    res.json({ clientToken: session.clientToken, sessionUrl: session.url });
  } catch (e) {
    res.status(e.statusCode ?? 500).json({ error: e.code ?? 'error' });
  }
});
MethodEndpoint
sessions.create(params, { idempotencyKey })POST /v1/sessions → { id, status, url, clientToken, expiresAt }
sessions.retrieve(id, { expand: ['checks', 'applicant', 'business'] })GET /v1/sessions/:id
sessions.list(params)async iterator over every page (firstPage() for one page)
sessions.listPage(params)one page: { data, has_more }
sessions.decide(id, { outcome, reasonCode, note, steps })POST /v1/sessions/:id/decision
sessions.cancel(id)POST /v1/sessions/:id/cancel
sessions.createClientToken(id)POST /v1/sessions/:id/client_token
sessions.evidenceUrl(id, evidenceId)GET /v1/sessions/:id/evidence/:evidence_id
sessions.erasePii(id)DELETE /v1/sessions/:id/pii

create takes camelCase (workflowId, referenceId, redirectUrl, expiresIn, prefill.fullName); retrieved sessions are returned as the API sends them (snake_case).

Webhooks

server.mjs
// Webhooks: verify against the RAW body, dedupe on event.id, then fetch details from the API.
const seen = new Set();
app.post('/okyc/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  let event;
  try {
    event = await okyc.webhooks.constructEvent(
      req.body,
      req.header('okyc-signature'),
      process.env.OKYC_WEBHOOK_SECRET,
    );
  } catch {
    return res.sendStatus(400);
  }
  if (seen.has(event.id)) return res.sendStatus(200);
  seen.add(event.id);
  if (event.type === 'session.approved' || event.type === 'session.declined') {
    const session = await okyc.sessions.retrieve(event.data.session.id);
    console.log(`user ${session.reference_id} → ${session.status}`);
    // mark the user verified / rejected in your database
  }
  res.sendStatus(200);
});

webhooks.constructEvent(rawBody, header, secret, { toleranceSec }) throws WebhookSignatureError when the header is missing, malformed, too old or wrong, or when the body isn't JSON. A valid event can arrive more than once within the tolerance, so de-duplicate on its id (webhooks). generateTestHeader(secret, body) signs a body for your tests. Both use Web Crypto, so they work in edge runtimes.

Checks and other resources

Method
checks.run(key, input, { async }) / checks.retrieve(id)Direct checks, e.g. checks.run('in.pan.verify', { pan }).
in.pan.verify, in.gstin.verify, in.bank.verify, in.mca.company, in.udyam.verifyTyped shortcuts for India checks.
screening.sanctions({ name, dob, entity_type })Sanctions screening without a session.
workflows.list() / workflows.retrieve(id)Published workflows.
webhookEndpoints.create/list/delManage endpoints (the secret is returned once by create).
events.list({ type, limit })Recent events, for reconciliation.
capabilities.list({ country })What a country supports.

Errors are typed — see errors.

On this page