Developer documentation

The BaobaPay API for collecting payments across the CEMAC (XAF) and WAEMU (XOF) zones — mobile money, card and payment links. Your customer's payment is routed to your merchant account.

Introduction

The API is organised around REST resources, with JSON responses and standard HTTP codes. All requests go over HTTPS.

BASE URL
https://api.baobapay.com/api/v1           // production
https://api.sandbox.baobapay.com/api/v1   // sandbox

Two environments: sandbox (keys sk_sb_…, no real money) and production (keys sk_live_…, activated when the contract is signed).

The XAF (CEMAC) and XOF (WAEMU) zones are separate currencies: each collection is denominated in your market's currency.

Quickstart

Take your first mobile money payment in the sandbox, in three steps.

1. Create a mobile money pay-in

cURL
curl https://api.sandbox.baobapay.com/api/v1/payins/mobile-money \
  -H "Authorization: Bearer sk_sb_9f2a…" \
  -H "Idempotency-Key: 8f14e45f" \
  -H "Content-Type: application/json" \
  -d '{
    "reference": "CMD-10412",
    "amount": { "currency": "XAF", "value": 24500 },
    "operator": "MTN",
    "phone": "+237671224188"
  }'

2. Read the response

200 · JSON
{
  "id": "pi_10482",
  "status": "PENDING",
  "amount": { "currency": "XAF", "value": 24500 }
}

3. Receive the confirmation

When the customer confirms on their phone, BaobaPay sends payin.succeeded to your webhook endpoint — see Webhooks below.

Event · JSON
{
  "type": "payin.succeeded",
  "data": { "id": "pi_10482", "status": "SUCCEEDED" }
}

Authentication

Every request is authenticated with a secret key passed in the Authorization header, in the Bearer format. Never expose a sk_live_ key on the client side.

HEADER
Authorization: Bearer sk_sb_9f2a3b7c1d…
PrefixEnvironment
sk_sb_…Sandbox — active immediately
sk_live_…Production — after the contract is signed
An unauthenticated key returns 401 unauthorized. Manage and regenerate your keys in the dashboard → Developers.

Idempotency

All POST requests that create movements accept an Idempotency-Key header. Replaying the same key returns the original response without creating a duplicate — essential for network retries and mobile money retries.

HEADER
Idempotency-Key: 8f14e45f-ceea-467d-9c1b-2a…

The key is retained for 24 h. Use a unique UUID per payment intent.

Amounts & currencies

Amounts are represented by a Money object — a currency and an integer value. XAF and XOF are currencies without minor units: the value is expressed in whole francs (no cents).

Money
{ "currency": "XAF", "value": 24500 }   // 24,500 CFA francs

Fund model

Understanding where the money sits at any moment is the prerequisite to any integration.

Wallet topology

Two wallets are created when a platform opens on a third-party vertical — marketplace, freelance, crowdfunding, pots, travel, B2B. In direct sale (e-commerce), only the main wallet exists.

WalletHoldsWho debits it
WLT-MAINYour commissionsYou, to your settlement accounts
WLT-BENEFFunds belonging to your third partiesYou, to the third party's account only
WLT-SM-xxxxA verified third party's balancePayout to their own account
Individual wallets create themselves. A wallet is opened automatically at a holder's KYC validation — never before, and without any call on your side. Listen to wallet.created rather than calling POST /wallets.

Segregation

A withdrawal from WLT-MAIN can only go to a settlement account of the platform. A payout from a third-party wallet can only go to that third party's account. The API refuses every cross combination — a marketplace owner cannot drain their sellers' wallet to their own account.

Withdrawal lifecycle

Sequence
POST /withdrawals            → status: PENDING_APPROVAL
  ↓  (internal approval)
withdrawal.approved          → the funds have not left yet
  ↓  (execution by the operator)
withdrawal.paid              → the wallet balance is decremented here
      or
withdrawal.failed            → the balance stays intact, the request can be replayed
Do not decrement your own counter on `approved`. The balance only moves on paid. A payout approved then failed at the operator leaves the funds in place.

Cash collections

Cash declarations are merchant data: no wallet is credited, no flow can be reconciled in bank. They are kept and exportable, but do not enter the balance computation.

Errors

The API uses conventional HTTP codes and returns a structured error object.

422 · JSON
{
  "type": "invalid_request",
  "code": "insufficient_kyc",
  "message": "The merchant account must be verified to collect in production."
}
CodeMeaning
400 / 422Invalid request or business rule not met
401Missing or invalid API key
402Payment declined (operator unavailable, cap…)
403Action not allowed (frozen flows, KYC)
409Conflict — idempotency key replayed with a different body
429Too many requests (rate limit)

E-commerce plugins & modules

Get paid without writing code: official modules maintained by BaobaPay. Each module installs the hosted checkout (mobile money, card, link) and the automatic reconciliation of orders via webhooks.

WooCommerce
WordPress · 5-minute install · baobapay-woocommerce.zip
PrestaShop
Official module · 1.7+ · baobapay-prestashop.zip
Shopify
App Store · redirected hosted payment

Common setup: paste the public API key + the webhook URL generated by the module. The order status changes automatically to « paid » on payin.succeeded. Sandbox supported in every module.

Endpoints

The collection resources of the aggregator API.

MethodEndpointUsage
POST/payins/mobile-moneyCollect by mobile money (Orange, MTN, Express Union…)
POST/payins/cardCollect by card (with 3-DS/SCA)
POST/payins/recurringRecurring payment (on file / mandate)
GET/payins/{payInId}Retrieve a collection — status, method, credited wallet
POST/payins/recurring/registrationsCreate a recurring payment mandate
POST/preauthorizationsCard pre-auth / deposit (deferred capture)soon
POST/payment-linksCreate a hosted payment link / QR
POST/refundsRefund a collection
GET/transactionsUnified collection log (PAYIN / REFUND types)
GET/reportsReconciliation reports and exports
POST/developers/webhooksManage webhook endpoints
GET/developers/api-keysList API keys — metadata only, never the value
Holders & verification
POST/usersCreate a holder (seller, beneficiary, payer)
GET/usersList holders
GET/users/{userId}Retrieve a holder and their KYC level
POST/users/{userId}/kyc/documentsSubmit a KYC/KYB document
Wallets
GET/walletsList wallets — main, beneficiaries, individual
POST/walletsCreate a wallet (automation; creation normally happens at KYC validation)
GET/wallets/{walletId}Read a wallet — total, committed and available balance
GET/wallets/{walletId}/transactionsA wallet's movements — cursor pagination
Fund exit
GET/settlement-accountsList the merchant's settlement accounts
POST/settlement-accountsDeclare a settlement account — step-up authentication required
PATCH/settlement-accounts/{settlementAccountId}Update an account — step-up authentication required
POST/settlement-accounts/{settlementAccountId}/verifyRe-run the holder verification
GET/withdrawalsList withdrawal requests
POST/withdrawalsRequest a withdrawal to a verified settlement account
DELETE/withdrawals/{withdrawalId}Cancel a request still awaiting approval
Cash, disputes & context
GET/cash-declarationsList declared cash collections
POST/cash-declarationsDeclare a cash collection — no wallet credited
POST/cash-declarations/{id}/refundDeclare a cash refund
GET/disputesList disputes
GET/disputes/{disputeId}Retrieve a dispute and its evidence
GET/operator-enrollmentsList operator enrollments
GET/operator-enrollments/{enrollmentId}Read an enrollment
GET/me/platformsPlatforms the user can access
POST/session/active-platformSwitch the session context — step-up authentication on first opening
GET/platformPlatform configuration — vertical, features, zone

Webhooks

BaobaPay notifies your server of asynchronous events (payment confirmation, KYC change, dispute…). Each delivery is signed and replayed on failure (backoff up to 24 h).

Verify the signature

Every request carries a BaobaPay-Signature header — an HMAC-SHA256 of the raw body, computed with your endpoint secret. Compare it before processing the event.

Node.js
const sig = req.headers['baobapay-signature'];
const expected = crypto
  .createHmac('sha256', endpointSecret)
  .update(rawBody)
  .digest('hex');
if (sig !== expected) return res.status(400).end();
// valid signature → process the event

Event catalogue

EventTriggered when…
EVENT payin.succeededa collection is confirmed
EVENT payin.faileda collection fails (decline, timeout, balance)
EVENT refund.donea refund is executed
EVENT kyc.updateda customer's verification status changes
EVENT kyb.updateda company's KYB status changes
EVENT dispute.openeda dispute / chargeback is opened
EVENT dispute.closeda dispute is closed (outcome known)
EVENT user.blockedcompliance suspends a holder — they can no longer collect or be paid
EVENT user.unblockeda holder's suspension is lifted
EVENT user.closeda holder is closed (data kept under legal retention)
Wallets
EVENT wallet.createda wallet is created — automatically at a holder's KYC validation
EVENT wallet.balance_updateda wallet's balance changes — collection, payout, withdrawal
EVENT wallet.frozena wallet is frozen by compliance
Withdrawals
EVENT withdrawal.approveda withdrawal request is approved — the funds have not left yet
EVENT withdrawal.paidthe withdrawal is confirmed by the operator — the balance is decremented at that moment
EVENT withdrawal.rejectedthe request is refused before execution
EVENT withdrawal.failedthe operator could not execute the payout — invalid number, closed account
Settlement accounts
EVENT settlement_account.verifieda settlement account is verified — it becomes usable after the safety delay
EVENT settlement_account.rejecteda settlement account is refused — holder not matching the KYB
Cash, operators & terms
EVENT cash.declareda cash collection is declared — no wallet is credited
EVENT cash.refund_declareda cash refund is declared
EVENT operator_enrollment.updatedan operator enrollment's status changes
EVENT terms.publishednew terms and conditions are published
Delivery order is not guaranteed. withdrawal.paid can precede withdrawal.approved. Treat each event as an independent fact, and rely on the body's timestamp rather than the order of receipt. Deliveries are retried: your endpoint must be idempotent on event.id.
Example event
{
  "type": "payin.succeeded",
  "created": 1752931320,
  "data": {
    "id": "pi_10482",
    "amount": { "currency": "XAF", "value": 24500 },
    "operator": "MTN"
  }
}

Sandbox & testing

The sandbox reproduces the entire API without real money. Trigger successes and failures deterministically.

Test cards

NumberResult
4970 1000 0000 0055Successful payment (Visa)
4000 0000 0000 0002Card declined
4000 0000 0000 9995Insufficient balance

Test mobile money

  • +237 6 71 22 41 88 → successful confirmation
  • +237 6 00 00 00 00 → timed out (not confirmed)
From the dashboard → Sandbox kit, you can emit webhook events on demand to test your integration.