Skip to main content

How to integrate a Node.js server

This guide is for back-office or API integrations where you control a Node.js process — a queue worker, a webhook receiver, a cron job, or an internal admin tool.

At a glance

  1. X-Api-Key authStatic header. Created and scoped through the Admin Console.
  2. Loyalty REST APIMint to accounts, manage reward tiers, list orders.
  3. Your Node serviceA back-office process holding the org-scoped API key.
  4. DPP REST APICreate, publish, certify passports under /v1/dpp/*.
  5. @keyban/sdk-baseOptional — only when signing transactions on agent wallets.
Server-side Node integration: API key auth via X-Api-Key, REST endpoints for DPP and Loyalty admin, optional SDK helper for agent-wallet signing.
No outbound webhooks

Keyban does not push events to partner systems today. To observe state changes (passport certification, on-chain settlement), poll the relevant GET endpoints on a schedule.

1. Install the dependencies

For most server use cases, the only requirement is a JSON-capable HTTP client — fetch is built into Node 18+, no extra package needed. Install @keyban/sdk-base only if you need on-chain transaction signing on an agent wallet.

node --version # 18 or higher

# optional — only if you sign transactions on the server
pnpm add @keyban/sdk-base

2. Configure the API key

Treat the X-Api-Key like any other database password: load it from a secret store (Vault, AWS Secrets Manager, Doppler), never commit it. Bind one key per environment.

const KEYBAN_API = process.env.KEYBAN_API ?? "https://api.prod.keyban.io";
const KEYBAN_API_KEY = process.env.KEYBAN_API_KEY;

if (!KEYBAN_API_KEY) {
throw new Error("KEYBAN_API_KEY is not configured");
}

Create the key from Organization → API keys in the Admin Console. The supported scopes and the permissions shape are listed in the Permissions reference.

3. Make your first call

A small helper centralises the auth header and the JSON content-type so each call site stays focused.

async function keyban<T>(
path: string,
init: RequestInit = {},
): Promise<T> {
const response = await fetch(`${KEYBAN_API}${path}`, {
...init,
headers: {
"X-Api-Key": KEYBAN_API_KEY,
"Content-Type": "application/json",
...init.headers,
},
});

if (!response.ok) {
const problem = await response.json().catch(() => ({}));
throw Object.assign(new Error(problem.title ?? response.statusText), {
status: response.status,
problem,
});
}

return response.json() as Promise<T>;
}

Use it the same way you'd call any other JSON API:

type CreatedPassport = { id: string; status: string; certificationStatus: string };

const passport = await keyban<CreatedPassport>("/v1/dpp/passports", {
method: "POST",
body: JSON.stringify({
granularity: "model",
application: APP_ID,
network: "StarknetSepolia",
modelNumber: "WAL-LEATHER-2026",
name: "Heritage Leather Wallet",
description: "Hand-stitched bifold wallet.",
}),
});

console.log(passport.id, passport.status); // "draft"

4. Mint loyalty points

The mint endpoint is path-specific (singular account) and accepts only the amount:

await keyban<{ newamount: number }>(
`/v1/loyalty/account/${accountId}/mint`,
{
method: "POST",
body: JSON.stringify({ amount: 600 }),
},
);

The on-chain settlement is asynchronous — read back the order list (GET /v1/loyalty/orders?walletId=...) to see the asset transfer materialise.

5. Handle errors gracefully

Keyban returns a structured JSON body on every error response. The helper above attaches the parsed body on the thrown error — propagate it to your logs and your callers. The detailed handling pattern is covered in How to handle Keyban API errors.

For idempotent operations, pass an Idempotency-Key header so a network retry doesn't double-spend. See your endpoint's documentation in /backend-openapi to confirm idempotency support.

6. Poll for asynchronous state changes

Several Keyban operations are asynchronous: passport publication queues an on-chain certification job, mints settle in seconds on most networks. To know when an operation has fully landed, poll the relevant GET endpoint:

async function waitForCertification(passportId: string, timeoutMs = 30_000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const p = await keyban<{ certificationStatus: string }>(
`/v1/dpp/passports/${passportId}`,
);
if (p.certificationStatus === "certified") return p;
await new Promise((r) => setTimeout(r, 1000));
}
throw new Error("Certification timed out");
}

For consumer-side claim flows that prefer a streaming channel, the SSE endpoint /v1/dpp/claim/:jobId/status/sse is available — see the @keyban/sdk-react claim helpers for the canonical client.

Reference

MethodPathAuthPurpose
POST/v1/dpp/passportsX-Api-KeyCreate a DPP passport (model / batch / item, one row).
POST/v1/importsX-Api-KeyBulk-import DPP passports (async, upsert). See Bulk import in the PHP guide for the full flow.
POST/v1/dpp/passports/publishX-Api-KeyBulk-publish drafts matching a query filter.
POST/v1/loyalty/reward-tiersX-Api-KeyCreate a loyalty reward tier.
POST/v1/loyalty/account/:id/mintX-Api-KeyMint points to an account.
GET/v1/loyalty/ordersX-Api-KeyList on-chain order history for a wallet.