Skip to main content

Web Client-side Rendering

Before you do Anything

Please Sign Up to get started here - https://app.creditchek.africa/auth/signup

The first thing you need to do is to go to the B2B portal at https://app.creditchek.africa/auth/signup to create an account with CreditChek. After signing up, follow the process required to verify your account on the app using the verification code sent to you email used for account creation. After this is done, you can then login.

CreditChek React SDK for Web Apps

Add CreditChek identity verification to your React web app. With a few lines of code, your customers can:

  • verify their identity with a BVN or NIN
  • complete a face liveness check, matched against their BVN or NIN photo

The SDK opens the hosted CreditChek widget in a modal on your page, notifies your app when each step finishes, and tells you when the customer is done. It works with React 17+, Vite, Create React App and Next.js.


How integration works​

Every verification takes four steps. Two happen on your server and two in your React web app:

#WhereWhat happens
1Your server backendCreates a widget session with your secret key, and returns its sessionId to your app
2Your React web appCalls open() from this SDK. The widget opens in a modal with your public key, your widget ID and the sessionId
3Your React web appThe customer completes the steps. The SDK calls onStep as each one finishes, and onClose when the modal closes
4Your server backendReads the session with your secret key to find out what the customer actually completed
 Customer        Your React Web App       Your backend Server        CreditChek
β”‚ click "Verify" β”‚ β”‚ β”‚
│─────────────────▢│ POST /api/session β”‚ β”‚
β”‚ │───────────────────────▢│ 1. create session β”‚
β”‚ β”‚ │───────────────────────▢│
β”‚ β”‚ sessionId │◀───────────────────────│
β”‚ │◀───────────────────────│ β”‚
β”‚ 2. modal opens (publicKey, widgetId, β”‚ β”‚
β”‚ sessionId) β”‚ β”‚
│◀─────────────────│ β”‚ β”‚
β”‚ completes steps β”‚ 3. onStep … onClose β”‚ β”‚
│─────────────────▢│ GET /api/session/:id β”‚ β”‚
β”‚ │───────────────────────▢│ 4. read session β”‚
β”‚ β”‚ │───────────────────────▢│
β”‚ β”‚ verified: true/false │◀───────────────────────│
β”‚ │◀───────────────────────│ β”‚

Why a server? Creating and reading sessions needs your secret key, and the secret key must never reach the browser. This SDK runs only in the browser and never touches your secret key.

How the modal works. The SDK adds a modal to your page with the widget inside an iframe, and removes it when the customer finishes, cancels, or hits an error screen. It slides up as a full-screen sheet on phones. The customer never leaves your page, and no other window or tab opens.


Before you start​

The widget needs three values to open. All three are required:

ValueWhat it isWhere it comes from
publicKeyYour public keyThe dashboard (see Keys below)
widgetIdYour widget IDYour business dashboard (see Widget ID below)
sessionIdA new widget session for this verificationYour server creates it with your secret key (Step 1)

Customer details you already hold can be passed as optional prefill values.

Keys. Your keys are on the CreditChek B2B dashboard, in the App section. Each app has a live and a test pair:

KeyWhere it's usedKeep it secret?
Secret keyYour server only: creating and reading sessionsYes. Never put it in React code, a VITE_ / NEXT_PUBLIC_ / REACT_APP_ variable, or a URL
Public keyYour React app, passed to the SDKNo, it identifies your business

Always use a secret key and a public key from the same app and the same pair (both live, or both test).

Widget ID. Your widget ID is on your CreditChek business dashboard. Use the same widgetId in two places:

  • when your server creates a session (Step 1)
  • when your React app opens the widget (Step 2)

API base URL (your server): https://api.creditchek.africa/v1

Your site's security headers. If your site sends these headers, allow the widget in them:

HeaderWhat to allow
Content-Security-Policyframe-src https://securedwidget.creditchek.africa
Permissions-Policycamera=(self "https://securedwidget.creditchek.africa"), so the liveness step can use the camera

Web App SDK Installation​

npm install @creditchektechsupport/react-sdk
# or
yarn add @creditchektechsupport/react-sdk
# or
pnpm add @creditchektechsupport/react-sdk

react 17 or later is a peer dependency. The SDK has no other dependencies.


Step 1: Create a session on your server​

Each verification needs its own widget session. The session links the verification to your widget, and tracks the customer's progress. Create it when the customer clicks to start: sessions are short-lived.

POST https://api.creditchek.africa/v1/auth/widget-session/token
token: <your secret key>
Content-Type: application/json

{ "widgetId": "<your widget ID>" }
FieldTypeDescription
widgetIdstringRequired. Your widget ID from the business dashboard. Use the same ID in the SDK
sessionIdstringOptional. Your own unique ID for the session

Your widget's setup on the dashboard decides which steps the customer goes through.

Example (Node.js 18+ with Express; any server language works):

// server.js
import express from "express";

const API_BASE = "https://api.creditchek.africa/v1";
const SECRET_KEY = process.env.CREDITCHEK_SECRET_KEY; // server only
const WIDGET_ID = process.env.CREDITCHEK_WIDGET_ID;

async function creditchek(path, init = {}) {
const res = await fetch(`${API_BASE}${path}`, {
...init,
headers: { "Content-Type": "application/json", token: SECRET_KEY, ...init.headers },
});
const body = await res.json();
if (!res.ok || !body.success) throw new Error(body.message ?? `CreditChek HTTP ${res.status}`);
return body.data;
}

const app = express();

app.post("/api/verification/session", async (req, res) => {
try {
const session = await creditchek("/auth/widget-session/token", {
method: "POST",
body: JSON.stringify({ widgetId: WIDGET_ID }),
});
await saveSessionForCustomer(req, session.sessionId); // your storage
res.json({ sessionId: session.sessionId });
} catch {
res.status(502).json({ error: "Could not start verification" });
}
});

The response data looks like this:

{
"sessionId": "a78e8b61-469b-4ec6-8d1a-5f0ef30d927c",
"status": "active",
"services": { "bvn": { "status": "pending" }, "liveness": { "status": "pending" } },
"expiresAt": "2026-09-09T14:10:00.000Z"
}

Step 2: Open the widget​

Use the useCreditChek hook and call open from a click handler:

import { useCreditChek } from "@creditchektechsupport/react-sdk";

async function createSession() {
const res = await fetch("/api/verification/session", { method: "POST" });
if (!res.ok) throw new Error("Could not start verification");
return (await res.json()).sessionId;
}

export function VerifyButton() {
const { open, isOpen } = useCreditChek({
publicKey: "YOUR_PUBLIC_KEY",
widgetId: "YOUR_WIDGET_ID",
onClose: ({ sessionId }) => {
// Step 3: the customer is done. Ask your server for the result.
},
});

return (
<button onClick={() => open(createSession)} disabled={isOpen}>
Verify my identity
</button>
);
}

Pass the session as a function (open(createSession)). The modal opens straight away with a loading spinner, calls your function, then loads the widget. If you already have a session ID, open("the-session-id") works too.

Prefer a ready-made button? CreditChekButton does the same. Any other props go to the <button>:

import { CreditChekButton } from "@creditchektechsupport/react-sdk";

<CreditChekButton
publicKey="YOUR_PUBLIC_KEY"
widgetId="YOUR_WIDGET_ID"
sessionId={createSession}
onClose={({ sessionId }) => checkResult(sessionId)}
className="btn btn-primary"
>
Verify my identity
</CreditChekButton>

Step 3: Know when the customer is done​

The SDK gives you two callbacks:

CallbackWhenUse it for
onStep({ module, status })Each time a step finishes in the widgetProgress in your UI, analytics
onClose({ sessionId, steps })Once, when the widget closes for any reason: the customer finished, hit an error screen, or closed itAsking your server for the result

The widget closing means "the customer is done", not "the customer passed". Step events are hints for your UI. Always decide the outcome on your server (Step 4).

If something goes wrong before the widget loads, onError fires instead of onClose. See Errors.


Step 4: Confirm the result on your server​

The session is the source of truth:

GET https://api.creditchek.africa/v1/auth/widget-session/<sessionId>
token: <your secret key>
// server.js (continued)
app.get("/api/verification/session/:sessionId", async (req, res) => {
try {
if (!(await customerOwnsSession(req, req.params.sessionId))) return res.sendStatus(404);
const session = await creditchek(`/auth/widget-session/${encodeURIComponent(req.params.sessionId)}`);
const services = Object.values(session.services);
const verified = services.length > 0 && services.every((s) => s?.status === "completed");
res.json({ verified, services: session.services });
} catch {
res.status(502).json({ error: "Could not read verification" });
}
});
FieldValueMeaning
services.<name>.statuspendingNot done yet, or the customer left before finishing
completedVerified
failedThe last attempt failed. Liveness can be retried in the same session
statusactiveServices are still outstanding
completedEvery service on the session is completed

For the name, date of birth and gender behind a completed BVN, call GET /auth/widget-session/bvn-data/<sessionId> with your secret key.


Complete example​

import { useState } from "react";
import { useCreditChek } from "@creditchektechsupport/react-sdk";

export default function Verification({ customer }) {
const [message, setMessage] = useState("");

const { open, isOpen, steps } = useCreditChek({
publicKey: import.meta.env.VITE_CREDITCHEK_PUBLIC_KEY,
widgetId: import.meta.env.VITE_CREDITCHEK_WIDGET_ID,
prefill: { firstName: customer.firstName, lastName: customer.lastName },
onClose: async ({ sessionId }) => {
setMessage("Checking your result…");
const result = await fetch(`/api/verification/session/${sessionId}`).then((r) => r.json());
setMessage(result.verified ? "You're verified." : "Verification isn't complete yet.");
},
onError: () => setMessage("Something went wrong. Please try again."),
});

const createSession = async () => {
const res = await fetch("/api/verification/session", { method: "POST" });
if (!res.ok) throw new Error("Could not start verification");
return (await res.json()).sessionId;
};

return (
<div>
<button onClick={() => open(createSession)} disabled={isOpen}>
Verify my identity
</button>
<ul>
{steps.map((s, i) => (
<li key={i}>{s.module}: {s.status}</li>
))}
</ul>
<p>{message}</p>
</div>
);
}

API reference​

useCreditChek(options)​

const { open, close, isOpen, steps, error } = useCreditChek(options);

Options

OptionTypeDefaultDescription
publicKeystringrequiredYour public key. Must be from the same app and pair as the secret key that created the session
widgetIdstringrequiredYour widget ID
prefillWidgetPrefillDetails you already hold: firstName, lastName, dob (YYYY-MM-DD), bvn, nin, email. Prefilled identity fields are locked in the widget
onStep(event: WidgetStepEvent) => voidA step finished
onClose(result: WidgetCloseResult) => voidThe widget closed
onError(error: CreditChekError) => voidThe widget couldn't open

Options are read when open is called, so they can change between renders.

The sessionId is required too, but it isn't an option: you pass it to open each time, because every verification needs a new session.

Returns

FieldTypeDescription
open(sessionId: string | () => string | Promise<string>) => voidOpens the widget. sessionId is required: a session ID, or a function that creates one. If the widget is already open, focuses it
close() => voidCloses the widget. onClose fires as normal
isOpenbooleanTrue from open until the widget closes or fails
stepsWidgetStepEvent[]Step events since the last open
errorCreditChekError | nullThe error from the last open

If the component unmounts while the widget is open, the modal is removed and callbacks stop firing.

<CreditChekButton />​

Takes every useCreditChek option, plus a required sessionId prop (a session ID, or a function that creates one), plus any <button> props. children defaults to "Verify with CreditChek". An onClick prop runs before the widget opens; call event.preventDefault() in it to stop the widget opening.

openCreditChekWidget(options)​

The same behaviour without React, for use outside components. Takes every useCreditChek option plus a required sessionId, and returns a handle:

const handle = openCreditChekWidget({ publicKey, widgetId, sessionId: createSession, onClose });
handle.isOpen; // boolean
handle.focus(); // focus the widget
handle.close(); // close the widget; onClose fires
handle.detach(); // remove the modal without calling onClose

Exactly one of onClose or onError fires for each call.

buildWidgetUrl(config, sessionId)​

Returns the full widget URL, if you want to host the widget yourself.

Types​

type WidgetModule = "identity" | "liveness";

interface WidgetStepEvent {
module: WidgetModule;
status: "successful" | "failed";
}

interface WidgetCloseResult {
sessionId: string;
steps: WidgetStepEvent[]; // oldest first
}

All types are exported from the package.


Modules​

ModuleWhat the customer doesWhat they need
identityEnters their name, date of birth and BVN or NIN. At least two name words and the exact date of birth must match the official recordTheir 11-digit BVN or NIN
livenessCentres their face in the camera, then follows prompts: look left, right, up and down, blink, smile. Their face is matched against the BVN or NIN photoA camera, good lighting, no glasses or hat
  • The steps come from your widget. Your widget's setup on the business dashboard decides which steps the customer goes through.
  • Include identity with liveness in your widget. Liveness compares the customer's face with the photo from their BVN or NIN.
  • Completed steps are skipped. If identity or liveness is already completed on the session, the widget skips it. If both are, the customer sees a success screen straight away.

More modules (credit reports, income and Recova mandates) are coming to the React SDK.


Testing​

Use the test public and secret keys from the App section of the dashboard. They work with the same API URL and the same widget address (https://securedwidget.creditchek.africa) as your live keys. The widget runs in live or test mode to match the public key.

Next.js: the package is marked "use client", so you can import it into App Router client components directly. Keep the session routes in a Route Handler or API route, where the secret key stays on the server.


Errors and troubleshooting​

CreditChekError codes (passed to onError and stored in error):

CodeCauseFix
session_failedYour sessionId function threw or returned nothingCheck your server route. The widget is closed for you
invalid_configpublicKey or widgetId is missingFix the options

Error screens inside the widget. If the widget can't start, the customer sees an error screen with a code and their session ID. Closing it closes the widget, so onClose fires.

Code on screenCauseFix
missing / invalidNo session, or it doesn't existPass the sessionId from Step 1 unmodified
expiredThe session expiredCreate the session when the customer clicks, not in advance
inactiveThe session was already completedCreate a new session for each verification
mismatchSession and public key are from different appsUse the secret and public keys of the same app
validate-public-keyThe public key is invalidCheck the key, and that it's from the same live or test pair as the secret key

Common problems

SymptomLikely cause
The modal stays blankYour Content-Security-Policy blocks the widget. Add it to frame-src
The camera doesn't startThe customer denied camera access, or your Permissions-Policy blocks the camera for the widget
The camera doesn't start inside Instagram, WhatsApp or similar appsTheir in-app browsers often block the camera. Ask the customer to open your site in their browser
Liveness can't match the faceliveness ran without identity before it in the same session
Your server shows pendingThe customer closed the widget before finishing

Security checklist​

  • The secret key lives only on your server.
  • You create one session per verification, when the customer starts.
  • Your server stores each sessionId against its customer, and only reports a session's result to that customer.
  • You decide "verified" on your server from GET /auth/widget-session/:sessionId, never from onStep or onClose alone.
  • Your pages are served over HTTPS in production.

Migrating from v1 (for Web Apps)​

v2 follows the new session-based widget, which needs a sessionId created by your server.

v1v2
import creditchekSDK from "creditchek-react-sdk"import { useCreditChek } from "@creditchektechsupport/react-sdk"
creditchekSDK.open({ publicKey, module, onComplete, onClose })const { open } = useCreditChek({ publicKey, widgetId, onStep, onClose }), then open(sessionId)
module: ["identity"]Your widget's setup on the business dashboard decides the steps
"income", "credit", "recova" modulesNot available in v2 yet
onComplete(result) for each steponStep({ module, status })
onClose()onClose({ sessionId, steps })
Opens a new tabOpens a modal on your page
No server neededYour server creates and reads sessions (Step 1, Step 4)

From earlier v2 releases: create sessions with widgetId instead of services, and pass the same widgetId to the SDK, where it is now required. Remove modules and environment: your widget decides the steps, and your public key decides live or test mode. Remove themeColor.


Support​

For help with this library, open an issue on the GitHub repo or email [email protected].