Guide · 08 / 10
SDKs
Quickstarts for Web, iOS, and Android.
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.
iOS — WasaaPaySDK
A drop-in checkout that presents WasaaPay's payment UI and returns a typed result. Requires iOS 15+.
Install (Swift Package Manager)
Add the WasaaPaySDK package and link the WasaaPaySDK product.
// In Package.swift
dependencies: [
.package(url: "https://github.com/web-masters-ke/wasaapay-ios-sdk.git", from: "1.0.0"),
],
targets: [
.target(name: "YourApp", dependencies: [
.product(name: "WasaaPaySDK", package: "wasaapay-ios-sdk"),
]),
]
Publishing caveat. The SDK is not yet published with a tagged SPM release, and its
Package.swiftcurrently lives in the repo'sWasaaPaySDK/subdirectory (SPM expects a root manifest). The sample app consumes it by local path. Confirm the exact package URL / version with WasaaPay before wiring the line above; until then, add it via File → Add Packages… → Add Local… pointing at a checkout of the SDK.
Present the checkout (UIKit)
import WasaaPaySDK
let checkout = WasaaPayCheckout(publishableKey: "pk_live_…") // environment: .production by default
checkout.present(
from: viewController,
amount: 150_000, // Int64, integer minor units — KES 1,500.00
currency: "KES",
reference: "order_10293"
) { result in
switch result {
case .success(let transaction):
print("Paid:", transaction.id, transaction.providerReference ?? "")
case .failure(let error):
print("Failed:", error.errorCode ?? "-", error.requestID ?? "-")
case .cancelled:
print("Shopper dismissed checkout")
}
}
The full initializer is
WasaaPayCheckout(publishableKey:environment:configuration:). The result type is
WasaaPayResult with three cases: .success(Transaction), .failure(WasaaPayError),
and .cancelled. WasaaPayError exposes errorCode (the backend catalog code)
and requestID for support.
SwiftUI
WasaaPayCheckoutView(
checkout: checkout,
amount: 150_000,
currency: "KES",
reference: "order_10293"
) { result in
// same WasaaPayResult switch as above
}
Environment toggle
WasaaPayCheckout(publishableKey: "pk_test_…", environment: .sandbox)
// .production → https://api.wasaapay.com/v1
// .sandbox → https://sandbox.api.wasaapay.com/v1
// .custom(baseURL:) → your own gateway; the URL must include /v1
For a plain-HTTP local gateway, also relax pinning:
WasaaPayCheckout(publishableKey:, environment: .custom(baseURL:), configuration: CheckoutConfiguration(pinning: .systemTrustOnly)).
Android — com.wasaapay:checkout-sdk
A Compose checkout screen (WasaaPayCheckoutScreen) plus a WasaaPayCheckout
config object. Requires minSdk 24.
Install (Gradle)
Resolve from Maven Central + Google:
// settings.gradle.kts
dependencyResolutionManagement {
repositories { google(); mavenCentral() }
}
// app/build.gradle.kts
dependencies {
implementation("com.wasaapay:checkout-sdk:1.0.0")
}
Present the checkout (Compose)
WasaaPayCheckout is a configuration holder; presentation is done through the
WasaaPayCheckoutScreen composable, which you embed in a bottom sheet, dialog, or
destination.
import com.wasaapay.checkout.WasaaPayCheckout
import com.wasaapay.checkout.WasaaPayCheckoutScreen
import com.wasaapay.checkout.CheckoutResult
val checkout = WasaaPayCheckout(publishableKey = "pk_live_…") // environment defaults to Production
WasaaPayCheckoutScreen(
checkout = checkout,
amountMinorUnits = 150_000, // Long, integer minor units — KES 1,500.00
currency = "KES",
reference = "order_10293",
) { result ->
when (result) {
is CheckoutResult.Success -> println("Paid: ${result.transaction.id}")
is CheckoutResult.Failure -> println("Failed: ${result.error.errorCode} ${result.error.requestId}")
CheckoutResult.Cancelled -> println("Shopper dismissed checkout")
}
}
CheckoutResult is a sealed interface with Success(transaction),
Failure(error), and the Cancelled object. error (a WasaaPayException)
exposes errorCode and requestId.
Environment toggle
import com.wasaapay.checkout.core.WasaaPayEnvironment
import com.wasaapay.checkout.security.CertificatePinningConfiguration
// Hosted sandbox:
WasaaPayCheckout(publishableKey = "pk_test_…", environment = WasaaPayEnvironment.Sandbox)
// Local gateway from the emulator (10.0.2.2 = host localhost), plain HTTP:
WasaaPayCheckout(
publishableKey = "pk_test_…",
environment = WasaaPayEnvironment.Custom("http://10.0.2.2:3000/v1"),
configuration = CheckoutConfiguration(pinning = CertificatePinningConfiguration.SYSTEM_TRUST_ONLY),
)
WasaaPayEnvironment is Production (https://api.wasaapay.com/v1), Sandbox
(https://sandbox.api.wasaapay.com/v1), or Custom(baseUrl) (must include /v1).
Production card tokenization. On both mobile SDKs the default card tokenizer is a sandbox placeholder. A production build must inject the certified card processor's tokenizer via
CheckoutConfiguration(cardTokenizer = …)— never let raw card data reach your app.