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.

SDKPackageModel
Web / Node@wasaapay/api-clientTyped server-side client for the full REST API (you sign requests).
iOSWasaaPaySDK (SPM)Drop-in checkout UI; you pass a publishable key + amount.
Androidcom.wasaapay:checkout-sdkDrop-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-session client_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, version 0.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 the npm add line 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-client has no createCheckoutSession method yet. Use raw HTTP for the hosted-checkout flow.