SDKs
SDKs
Three client SDKs, one for each surface. Pick a platform below — every snippet is taken straight from the SDK sources.
WasaaPay ships three client SDKs. Their signatures below are taken directly from the SDK sources.
| SDK | Package | Model |
|---|---|---|
| Web / Node | @wasaapay/api-client | Typed server-side client for the full REST API (you sign requests). |
| iOS | WasaaPaySDK (SPM) | Drop-in checkout UI; you pass a publishable key + amount. |
| Android | com.wasaapay:checkout-sdk | Drop-in Compose checkout screen; you pass a publishable key + amount. |
Two different auth models — read this first.
- The Web client authenticates like the raw API: your full merchant API key plus an HMAC signer you inject. It runs server-side (the secret must never reach a browser).
- The mobile SDKs authenticate with a publishable key (
pk_…) and build the collection themselves from the amount/currency/reference you pass — they do not consume a checkout-sessionclient_token, and they never carry your secret. Publishable-key auth is the mobile SDKs' documented design; if you are wiring the mobile SDKs against your own backend, confirm publishable-key acceptance end-to-end before going live (the server gateway documented in this guide authenticates with the full API key + HMAC).- None of the SDKs expose the checkout-session endpoints yet. Drive those over raw HTTP — see Collections → Hosted checkout.
Web / Node — @wasaapay/api-client
A fully typed client covering every REST operation (collections, refunds,
transactions, sub-merchants, wallets, split rules, payouts, webhooks, FX). It
returns a Result union and never throws on API errors.
Availability. This package is currently an internal workspace package (
"private": true, version0.1.0) — it is not published to the public npm registry yet. Inside the WasaaPay monorepo you depend on it as"@wasaapay/api-client": "workspace:*". Treat thenpm addline below as the intended public form once it's published.
// package.json (monorepo today)
"dependencies": { "@wasaapay/api-client": "workspace:*" }
Initialize with an API key + signer
The client does not compute HMAC itself — you inject a signer callback, so the
secret stays in your code, not in the bundle. The signer computes the same
`${timestamp}.${body}` HMAC described in Authentication.
import { createHmac } from 'node:crypto';
import {
WasaaPayClient, SANDBOX_BASE_URL,
type RequestSigner, type SignatureInput,
} from '@wasaapay/api-client';
// The signing secret is the part of your key after the first "."
const createSigner = (secret: string): RequestSigner =>
({ timestamp, body }: SignatureInput) =>
createHmac('sha256', secret).update(`${timestamp}.${body}`).digest('hex');
const client = new WasaaPayClient({
baseUrl: SANDBOX_BASE_URL, // or PRODUCTION_BASE_URL, or your own
auth: {
mode: 'api-key',
apiKey: process.env.WASAAPAY_API_KEY!, // full "wp_<prefix>.<secret>"
signer: createSigner(process.env.WASAAPAY_API_SECRET!), // the <secret> part
},
});
The client automatically sets X-WasaaPay-Key, X-WasaaPay-Timestamp, and
X-WasaaPay-Signature on every request, and attaches an auto-generated
Idempotency-Key to mutating calls (override via options.idempotencyKey).
Create a collection
const result = await client.createCollection({
amount: 150000, // minor units — KES 1,500.00
currency: 'KES',
rail: 'mobile_money',
payer_phone: '254700000001',
reference: 'order_10293',
});
if (result.ok) {
console.log(result.data.id, result.data.status); // Transaction
} else {
console.error(result.error.code, result.error.httpStatus, result.error.requestId);
// Retry the SAME logical attempt with the returned idempotency key:
// await client.createCollection(body, { idempotencyKey: result.idempotencyKey });
}
Other common calls
await client.getCollection(id);
await client.createRefund(collectionId, { amount: 50000, reason: 'partial' });
await client.createPayout({ wallet_id, destination_type: 'mobile_money', destination_ref, amount: 500000, currency: 'KES' });
await client.listTransactions({ status: 'success', limit: 50 });
await client.createWebhookEndpoint({ url, subscribed_events: ['transaction.success'] });
Pagination helpers make cursor loops trivial:
import { createPager, collectAllPages } from '@wasaapay/api-client';
const pager = createPager((cursor) => client.listTransactions({ cursor }));
const first = await pager.next();
if (pager.hasMore) { const second = await pager.next(); }
const all = await collectAllPages((cursor) => client.listPayouts({ cursor }));
Checkout sessions:
@wasaapay/api-clienthas nocreateCheckoutSessionmethod yet. Use raw HTTP for the hosted-checkout flow.