Sao Pay API · v1

Take MoMo, card and wallet payments in an afternoon.

One API for MTN Mobile Money, Telecel Cash, AirtelTigo, cards and Sao Wallet, with payouts, refunds and webhooks. Read the guide, or tell Ask Sao what you're building and get a setup flow written for you.

✦ Ask SaoGrounded in these docs first, the web second
or type
Shopify + MoMoSchool fees linksBulk payouts

Quickstart

Four calls take you from nothing to a confirmed MoMo payment. Everything below runs against the sandbox with test numbers, so no money moves.

  1. Create a sandbox account and copy your API key from the dashboard.
  2. Initiate a payment with the customer's phone number and amount.
  3. The customer approves the prompt on their phone (in sandbox, the test number decides the outcome).
  4. Confirm the status, or let a webhook tell you.
curlNodePHPPython
curl -X POST https://sandbox.api.saopayglobal.com/v1/payments/momo \ -H "Authorization: Bearer sk_test_51Kx…" \ -H "Content-Type: application/json" \ -d '{ "amount": 2500, "currency": "GHS", "phone": "233244123456", "network": "mtn", "reference": "ORDER-10482", "description": "Kumasi Kicks order 10482", "callback_url": "https://shop.example.com/webhooks/sao" }'

Amounts are in pesewas, so 2500 is GH₵ 25.00. The response returns a payment object with status pending and an id you'll use for everything else.

{ "id": "pay_01J8ZK4Q7M2N", "status": "pending", "amount": 2500, "currency": "GHS", "network": "mtn", "phone": "233244123456", "reference": "ORDER-10482", "created_at": "2026-09-14T10:42:11Z" }

Authentication

Every request carries a bearer token. Test keys start with sk_test_, live keys with sk_live_. Keys belong to a business, not a person, and can be rotated from the dashboard without downtime: the old key keeps working for 24 hours.

Never put a secret key in a browser or mobile app. For client-side checkout use a publishable key with payment links or the hosted checkout page.

Environments and test data

EnvironmentBase URLKeysMoney
Sandboxhttps://sandbox.api.saopayglobal.com/v1sk_test_…None. Outcomes are decided by test numbers.
Livehttps://api.saopayglobal.com/v1sk_live_…Real. Needs a verified business.

See Test data for the phone numbers and cards that produce each outcome, including timeouts and insufficient funds.

Mobile money

POST/v1/payments/momo

Sends a payment prompt to the customer's phone. Supported networks: mtn, telecel, airteltigo. If you omit network, Sao detects it from the number prefix.

FieldTypeRequiredNotes
amountintegeryesPesewas. Minimum 100.
phonestringyesE.164 without the plus, e.g. 233244123456
networkstringnomtn, telecel, airteltigo
referencestringyesYour unique id. Repeating it returns the original payment (idempotent).
callback_urlstringnoOverrides the webhook endpoint for this payment.
metadataobjectnoUp to 20 keys, returned on every event.

Prompts expire after 90 seconds. Most approvals arrive within 10 seconds; design for the customer walking away, and offer a "resend prompt" button.

Cards

POST/v1/payments/card

Cards use 3D Secure. You receive an authorization_url to send the customer to, and a webhook when they return. Use the hosted checkout unless you are PCI certified.

POST/v1/links

Create a link for an amount or an open amount, share it on WhatsApp or SMS, and get paid without writing checkout code. Links can be single-use or reusable, and can collect the payer's name and a note.

Confirming status

GET/v1/payments/{id}

Status is one of pending, success, failed, expired. Poll no more than once every 3 seconds, and stop after 2 minutes. Prefer webhooks; polling is a fallback for when your endpoint is down.

Webhooks

POST/v1/webhooks

Register an HTTPS endpoint and Sao posts events to it: payment.success, payment.failed, refund.completed, payout.completed. Each request carries an X-Sao-Signature header, an HMAC-SHA256 of the raw body with your webhook secret.

const crypto = require('crypto'); function verify(rawBody, header, secret) { const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex'); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header)); }

Respond with 200 within 5 seconds. Sao retries failed deliveries with backoff for 24 hours, and you can replay any event from the dashboard.

Refunds

POST/v1/refunds

Refund all or part of a successful payment. Money goes back the way it came; MoMo refunds land in minutes, card refunds in 3 to 7 days. The Sao fee is returned on full refunds.

Payouts

POST/v1/payouts

Send money to a MoMo number or bank account, one at a time or in batches of up to 1,000. Name lookup is free: call GET /v1/lookup/momo?phone=… first and show the registered name before you send.

Go-live checklist

Errors

CodeMeaningWhat to do
400 invalid_phoneNumber is not a Ghanaian mobileValidate before sending
402 insufficient_fundsCustomer's wallet is shortShow the message, offer another method
409 duplicate_referenceReference already used with different dataUse a new reference per attempt
429 rate_limitedOver 50 requests per secondBack off and retry with jitter
503 network_unavailableThe telco is downRetry later, offer another network
Stuck?

Tell Ask Sao what you're building and get a step-by-step flow, or open a support ticket and a person replies within the hour.