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
- X-Api-Key authStatic header. Created and scoped through the Admin Console.
- Loyalty REST APIMint to accounts, manage reward tiers, list orders.
- Your Node serviceA back-office process holding the org-scoped API key.
- DPP REST APICreate, publish, certify passports under /v1/dpp/*.
- @keyban/sdk-baseOptional — only when signing transactions on agent wallets.
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
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST | /v1/dpp/passports | X-Api-Key | Create a DPP passport (model / batch / item, one row). |
POST | /v1/imports | X-Api-Key | Bulk-import DPP passports (async, upsert). See Bulk import in the PHP guide for the full flow. |
POST | /v1/dpp/passports/publish | X-Api-Key | Bulk-publish drafts matching a query filter. |
POST | /v1/loyalty/reward-tiers | X-Api-Key | Create a loyalty reward tier. |
POST | /v1/loyalty/account/:id/mint | X-Api-Key | Mint points to an account. |
GET | /v1/loyalty/orders | X-Api-Key | List on-chain order history for a wallet. |
Related
- PHP integration — Create a passport — same JSON shape, works as a reference for any language.
- Tutorial — Automate Loyalty via API
- How to handle Keyban API errors
- Browse the JavaScript SDK reference