@systynlabs/vaultnuban
v0.2.0
Published
Official TypeScript/Node SDK for the VaultNUBAN virtual account API
Maintainers
Readme
@systynlabs/vaultnuban
Official TypeScript/Node SDK for the VaultNUBAN virtual account API. Works in Node ≥18 (plain Node, NestJS, Express, Next.js API routes).
npm install @systynlabs/vaultnubanQuick start
import { VaultNuban } from "@systynlabs/vaultnuban";
const vn = new VaultNuban({ apiKey: process.env.VAULTNUBAN_API_KEY! });
// Onboard a customer and give them a dedicated NUBAN
const customer = await vn.customers.create({
external_ref: "user_12345",
display_name: "Amaka Obi",
identity: { bvn_masked: "****78901", kyc_tier: 1 },
});
const va = await vn.virtualAccounts.provision(customer.id);
console.log(`Fund ${va.nuban} (${va.bank_name})`);
// Check the wallet
const balance = await vn.transactions.balance(customer.id);
console.log(balance.balance_ngn);
// Pay out
const payee = await vn.withdrawals.resolvePayee("058", "0123456789");
await vn.withdrawals.create(customer.id, {
amount_kobo: 250_000,
destination_bank_code: "058",
destination_account_number: payee.account_number,
destination_account_name: payee.account_name,
});Receiving webhooks
Register an endpoint once, then verify every delivery with the raw request
body and the X-VaultNUBAN-Signature header:
import express from "express";
import * as webhooks from "@systynlabs/vaultnuban/webhooks";
await vn.webhooks.createEndpoint({
url: "https://example.com/vaultnuban/webhook",
secret: process.env.VAULTNUBAN_WEBHOOK_SECRET!,
});
const app = express();
app.post("/vaultnuban/webhook", express.raw({ type: "*/*" }), (req, res) => {
let event;
try {
event = webhooks.constructEvent(
req.body, // raw Buffer — do not JSON-parse before verifying
req.header("X-VaultNUBAN-Signature"),
process.env.VAULTNUBAN_WEBHOOK_SECRET!,
);
} catch {
return res.sendStatus(400);
}
if (event.event_type === "payment_success") {
// credit received on event.transaction.nuban
}
res.sendStatus(200); // 2xx acknowledges; anything else is retried
});Behavior
- Idempotency — every
POST/PATCH/DELETEautomatically sends anIdempotency-Key(override viaoptions.idempotencyKey). Replays within 24 h return the cached response. - Retries — 429 and 5xx responses and network failures are retried with exponential backoff (default 2 retries; safe because of the idempotency key).
- Pagination —
list()returns one page ({ data, next_cursor });listAll()returns an async iterator over every item:for await (const tx of vn.transactions.listAll({ direction: "credit" })) { … } - Errors — non-2xx responses throw typed errors (
AuthenticationError,NotFoundError,ValidationError,ConflictError,RateLimitError,ServerError) carrying the RFC-9457 problem body andX-Request-ID.
NestJS
No separate package needed — inject the client as a provider:
@Module({
providers: [
{
provide: VaultNuban,
useFactory: () => new VaultNuban({ apiKey: process.env.VAULTNUBAN_API_KEY! }),
},
],
exports: [VaultNuban],
})
export class VaultNubanModule {}Development
npm install
npm run typecheck
npm test
npm run build