Node.js
Official server-side SDK (@wajub/node) for Node.js 18+ — payments, payouts, webhooks, and the full merchant API with TypeScript types.
Node.js is Wajub's official server-side SDK. It is published as @wajub/node on npm.
Use it on your backend (Next.js route handlers, Express, Fastify, Edge runtimes with fetch) with
your private key (sk_test_… / sk_…). Pair it with
@wajub/js or @wajub/react in the browser.
Browser vs server
@wajub/js runs in the browser with publishable keys.
@wajub/node runs on the server with private keys and webhook
verification — never expose sk_ keys to the client.
Install
npm install @wajub/nodeRequires Node.js 18+ (native fetch). Works in Bun and Deno when fetch is available.
Quick start
import { Wajub } from '@wajub/node';
export const wajub = new Wajub({
privateKey: process.env.WAJUB_SECRET_KEY!,
// webhookSecret: process.env.WAJUB_WEBHOOK_SECRET,
});Create a payment session
const payment = await wajub.payments.create({
amount: 15000,
currency: 'XAF',
email: 'buyer@example.com',
callback: 'https://shop.example.com/order/complete',
});
// Redirect the customer
return Response.redirect(payment.authorization_url);const payment = await wajub.payments.create({
amount: 15000,
currency: 'XAF',
metadata: { order_id: '123' },
});
// Return authorization_token to your frontend as sessionId
return { sessionToken: payment.authorization_token };Callback URL
Pass callback only for redirect checkout. For inline or overlay
embeds, omit it — confirmation stays inside the iframe and your frontend handles
onSuccess. See Sessions & security.
Configuration
| Option | Description |
|---|---|
privateKey / apiKey | Secret API key (sk_test_… / sk_…) |
webhookSecret | Signing secret (whsec_…) for webhooks.constructEvent() |
fetchOptions | Extra RequestInit passed to every fetch (proxy, custom dispatcher) |
idempotencyKeyPrefix | Prefix for auto-generated Idempotency-Key headers |
Resources
@wajub/node mirrors the merchant REST API. Each resource is available on the
Wajub instance:
| Getter | Methods |
|---|---|
wajub.global | ping, channels, countries, currencies |
wajub.payments | create, initialize, retrieve, list, cancel, process, processSplit, listRefunds |
wajub.customers | CRUD + block, unblock, activate, deactivate, listTaxIds, createTaxId, deleteTaxId |
wajub.refunds | create, retrieve, list |
wajub.transfers | create, retrieve, list |
wajub.beneficiaries | CRUD + list |
wajub.links | CRUD + list |
wajub.invoices | CRUD + send, markPaid, cancel |
wajub.accounts | CRUD + regenerateToken |
wajub.webhookEndpoints | CRUD + rotateSecret |
wajub.balance | retrieve |
wajub.events | list, retrieve, resend |
wajub.disputes | list, retrieve, submitEvidence, accept, close, sendMessage |
wajub.identity | resolve, validate |
wajub.tax | settings, rates, calculate, reports, codes, registrations, jurisdictions, thresholds |
wajub.shield | settings, stats, blocklist |
wajub.listen | config, auth |
wajub.webhooks | constructEvent |
List methods return a paginated result with data, meta, has_more, and getNextPage():
const page = await wajub.payments.list({ status: 'completed', per_page: 20 });
for (const payment of page.data) {
console.log(payment.id, payment.amount);
}
if (page.has_more) {
const next = await page.getNextPage();
}
// Or iterate all pages
for await (const payment of page) {
console.log(payment.id);
}Common patterns
Next.js App Router — create session
import { NextResponse } from 'next/server';
import { wajub } from '@/lib/wajub';
export async function POST(req: Request) {
const { amount, email, orderId } = await req.json();
const payment = await wajub.payments.create(
{
amount,
currency: 'XAF',
email,
callback: `${process.env.APP_URL}/payment/return`,
metadata: { order_id: orderId },
},
{ idempotencyKey: `order-${orderId}` },
);
return NextResponse.json({
sessionToken: payment.authorization_token,
authorizationUrl: payment.authorization_url,
});
}Charge a payment server-side
For direct API integrations (Mobile Money push, card on server):
const payment = await wajub.payments.create({ amount: 5000, currency: 'XAF' });
const result = await wajub.payments.process(payment.id, {
channel: 'cm.mobile',
data: { phone: '+237670000000' },
});Pay out (transfer)
const transfer = await wajub.transfers.create(
{
amount: 100000,
currency: 'XAF',
beneficiary: {
phone: '+237670000000',
channel: 'cm.mtn',
name: 'Amina',
},
},
{ idempotencyKey: crypto.randomUUID() },
);Sync (Connect)
Operate on behalf of a connected merchant account with the X-Sync header:
await wajub.payments.create(
{ amount: 5000, currency: 'XAF' },
{ sync: 'acct_sync_reference' },
);See Sync for account setup and capabilities.
Webhooks
Verify incoming webhook signatures with webhooks.constructEvent() — you need the raw
(unparsed) request body:
import express from 'express';
import { wajub } from './wajub';
const app = express();
app.post(
'/webhooks/wajub',
express.raw({ type: '_/_' }),
(req, res) => {
try {
const event = wajub.webhooks.constructEvent(
req.body,
req.headers['x-wajub-signature'] as string,
req.headers['x-wajub-timestamp'] as string,
);
switch (event.type) {
case 'payment.succeeded':
// fulfill order
break;
}
res.sendStatus(200);
} catch {
res.sendStatus(400);
}
},
);Raw body required
Signature verification fails if the body was parsed as JSON first. Use
express.raw() on the webhook route, not express.json(). See Signature
verification.
@wajub/node scope
@wajub/node covers the merchant API authenticated with sk_ keys. The checkout flow
(embedded session, overlay) is handled with
@wajub/js on the client side after
payments.create() on the server.
Idempotency
Mutating calls (POST, PUT) automatically send an Idempotency-Key header when you do not
pass one. Override per request:
await wajub.payments.create(params, { idempotencyKey: `order-${orderId}` });Supported on POST /payments and POST /transfers. See
Idempotency.
Types & errors
TypeScript types ship with the package — no @types package required.
Failed API calls throw WajubError with message, code, status, and optional field
errors:
import { WajubError } from '@wajub/node';
try {
await wajub.payments.create({ amount: 0, currency: 'XAF' });
} catch (err) {
if (err instanceof WajubError) {
console.log(err.code, err.errors);
}
}Local development
The SDK always calls https://api.wajub.com. Use the Wajub CLI to forward webhooks to localhost:
wajub listen --forward-to localhost:3000/webhooks/wajub
wajub trigger payment.succeededRaw REST API
You can always call the REST API directly with fetch if you prefer not to add a
dependency. @wajub/node is a thin typed wrapper over the same endpoints.
Related pages
Was this page helpful?