Checkout Sessions
The core way to take a payment. Create a session, send your customer to the hosted page, then confirm the result by redirect, polling or webhook. Includes split payouts, POS requests and paybill helpers.
https://api.paysasa.com/api/v1Authx-api-keyFormatJSON
Create a new checkout session. Share the hosted_url with your customer to
complete payment. Paysasa supports post-payment redirects via success_url,
cancel_url, return_url, and optional callback_url for
external apps.
403 with
code business_unverified (or owner_kyc_unverified) until the
business is verified. See Compliance & Trust below.
Redirect fields: success_url is the primary post-payment
destination. return_url is a fallback when success_url is omitted.
callback_url is optional — use the same absolute URL as
success_url for cross-domain apps and POS. Using the same URL for all three
fields is fine. External absolute URLs on another host are preserved (not rewritten to the
Paysasa domain).
Request Body#
| Parameter | Type | Description |
|---|---|---|
amount *
|
integer | Amount in minor units (cents/fils). e.g. KES 100 = 10000 |
currency *
|
string | ISO 4217 currency code. e.g. KES, USD |
business_id *
|
string | Your business ID (from Businesses page) |
description |
string | Human-readable description shown to the customer |
success_url |
string | Redirect URL after successful payment |
cancel_url |
string | Redirect URL if customer cancels |
return_url |
string |
Fallback return URL after checkout. Used when success_url is not set, or
as a secondary hand-off target.
|
callback_url |
string |
Optional. Explicit merchant-app URL for cross-domain integrations. Checked first in
the post-payment redirect chain. Usually the same as success_url; omit if
success_url already points to your external app.
|
allow_methods |
string[] |
Payment methods: wallet, m_pesa (KES only),
paypal (USD sessions). Must match an active business wallet for the
session currency.
|
metadata |
object | Arbitrary key-value data attached to the session (passed in webhook) |
use_split |
boolean |
Enable post-payment split payout. When omitted, saved business default splits apply
automatically if configured in Business Settings. Pass false to disable
defaults for one session.
|
split_mode |
string |
percentage (default) or fixed. Secondary wallets receive up
to 100% (or up to the session total for fixed mode); any remainder stays in the
primary checkout wallet automatically. Business default splits are percentage-based.
|
split_recipients |
object[] |
Required when use_split is true. Percentage mode:
{ wallet_id, percentage } (sum ≤ 100; remainder to primary). Fixed mode:
{ wallet_id, amount } where amount is in minor units (sum ≤
session total; remainder to primary). Recipients must be secondary business wallets
for the same business_id and currency — create additional
same-currency wallets with a display_name via Business Settings.
Cross-business or personal wallets are not supported.
|
seller_wallet_id). After payment succeeds, Paysasa automatically
moves each split portion from that wallet to recipient wallets in the same business.
Use percentage splits for proportional sharing, or fixed amounts when each recipient should
receive an exact value. Configure reusable percentage defaults under
Business Settings → Default Checkout Split, or pass
use_split and split_mode per session.
// Create a checkout session (Node.js)
const res = await fetch("https://api.paysasa.com/api/v1/checkout/sessions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": "apk_live_a1b2c3d4.7f9e0a...",
},
body: JSON.stringify({
amount: 15000, // KES 150.00
currency: "KES",
business_id: "biz_xxxxxxxxxxxxxxxx",
description: "Order #1042 — 2x T-Shirts",
success_url: "https://yourstore.com/order/success?id=1042",
cancel_url: "https://yourstore.com/order/cancel?id=1042",
return_url: "https://yourstore.com/checkout/return?id=1042",
allow_methods: ["wallet", "m_pesa"],
metadata: { orderId: "1042", customerId: "cus_abc" },
// Optional: percentage split across two same-business wallets
use_split: true,
split_mode: "percentage",
split_recipients: [
{ wallet_id: "wal_fulfillment_01", percentage: 60 },
{ wallet_id: "wal_marketing_01", percentage: 40 },
],
// Or fixed amounts (minor units) that sum to amount:
// use_split: true,
// split_mode: "fixed",
// split_recipients: [
// { wallet_id: "wal_ops_01", amount: 10000 },
// { wallet_id: "wal_reserve_01", amount: 5000 },
// ],
}),
});
const { session } = await res.json();
// Redirect customer:
window.location.href = session.hosted_url;
Retry guidance: only failed checkout sessions are retryable. Expired sessions must create a new checkout session. Do not auto-replay completed payments.
// Response 201 Created
{
"session": {
"id": "ses_01hx...",
"reference": "CHK-20240815-0042",
"status": "pending",
"amount": 15000,
"currency": "KES",
"hosted_url": "https://app.paysasa.com/checkout/ses_01hx...",
"expires_at": "2024-08-15T15:00:00.000Z",
"metadata": { "orderId": "1042" },
"return_url": "https://yourstore.com/checkout/return?id=1042",
"success_url": "https://yourstore.com/order/success?id=1042",
"cancel_url": "https://yourstore.com/order/cancel?id=1042"
}
}
Checkout lifecycle: sessions move from pending to
processing when payment starts, then to completed on success or
failed/expired/cancelled when payment does not
complete. Retry is supported only for failed sessions. Expired and cancelled
sessions require a new checkout session. Completed sessions are final and must not be
auto-retried.
Retrieve a checkout session by ID to check payment status. This read endpoint is currently public for hosted checkout pages.
const res = await fetch(
"https://api.paysasa.com/api/v1/checkout/sessions/ses_01hx..."
);
const { session } = await res.json();
console.log(session.status); // "pending" | "paid" | "expired" | "cancelled"
Poll the checkout session status from your backend or hosted checkout page until the session
moves out of pending. Use the same read endpoint you use for the initial session
lookup.
async function waitForPayment(sessionId) {
const deadline = Date.now() + 2 * 60 * 1000;
while (Date.now() < deadline) {
const res = await fetch("https://api.paysasa.com/api/v1/checkout/sessions/" + sessionId);
const { session } = await res.json();
if (session.status !== "pending") {
return session;
}
await new Promise((resolve) => setTimeout(resolve, 3000));
}
throw new Error("Timed out waiting for checkout session status");
}
POS-focused helper endpoint. It creates a checkout session and optional linked payment request
for a recipient identifier (phone/wallet ID). Supports the same redirect fields
(success_url, cancel_url, return_url,
callback_url) and use_split / split_recipients as
/checkout/sessions.
const res = await fetch("https://api.paysasa.com/api/v1/checkout/requests", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": "apk_live_a1b2c3d4.7f9e0a...",
},
body: JSON.stringify({
business_id: "biz_xxxxxxxxxxxxxxxx",
amount: 12500,
currency: "KES",
to: "254712345678",
description: "POS checkout request",
return_url: "https://yourstore.com/checkout/return?id=1042",
success_url: "https://yourstore.com/order/success?id=1042",
cancel_url: "https://yourstore.com/order/cancel?id=1042",
use_split: true,
split_recipients: [
{ wallet_id: "wal_ops_01", percentage: 70 },
{ wallet_id: "wal_reserve_01", percentage: 30 },
],
}),
});
const data = await res.json();
console.log(data.session_id, data.hosted_url, data.checkout_qr_url);
Business paypoint helper: create a custom-amount hosted checkout. Set the account reference to
attribute funds to a WaaS virtual wallet. Use identifier,
paybill_reference, serial_account_number, or
external_id (e.g. WAAS-POLL-1, 22457777, or
pollsoko_user_992). For server-to-server integrations, prefer
/waas/deposits/checkout with API key auth.
// Paypoint / kiosk flow — links checkout to WaaS virtual account when reference matches
const res = await fetch("https://api.paysasa.com/api/v1/checkout/discovery/paybill", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
business_id: "biz_xxxxxxxxxxxxxxxx",
// Any of these can identify the virtual account (explicit fields take precedence):
paybill_reference: "WAAS-POLL-1",
// external_id: "pollsoko_user_992",
// virtual_account_id: "va_xxxxxxxxxxxxxxxx",
amount: 50000,
currency: "KES",
description: "Wallet top-up",
}),
});
const data = await res.json();
// data.waas is set when a virtual account was matched
console.log(data.hosted_url, data.waas);
Initiate payment on an existing checkout session. Use this for cashier-triggered STK push shortcuts.
const res = await fetch("https://api.paysasa.com/api/v1/checkout/sessions/ses_01hx.../pay", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": "apk_live_a1b2c3d4.7f9e0a...",
"x-idempotency-key": crypto.randomUUID(),
},
body: JSON.stringify({
payment_method: "m_pesa",
phone: "254712345678",
customer_name: "Jane Customer",
}),
});
const result = await res.json();
console.log(result.status, result.checkout_request_id);
Cancel an active checkout session when the customer exits payment or requests cancellation.
Cancellable states are typically pending, processing, and
failed. Completed and expired sessions are not cancellable.
const res = await fetch("https://api.paysasa.com/api/v1/checkout/sessions/ses_01hx.../cancel", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": "apk_live_a1b2c3d4.7f9e0a...",
},
body: JSON.stringify({
reason: "Customer cancelled checkout",
}),
});
const data = await res.json();
console.log(data.status, data.message); // "cancelled", "Checkout cancelled"
// Response 200
{
"ok": true,
"status": "cancelled",
"message": "Checkout cancelled"
}