Collect a payment
A charge (charge) is a payment request sent to a SAMA Money customer. The customer confirms it with their secret code in the app or through #600#; you are notified of the result. You never handle the customer’s secret code, and there is no route for it.
The flow
Section titled “The flow”- Your server creates the charge with
POST /v1/charges: it ispending. - The customer receives the request on their phone and confirms or declines it. Until they do one or the other, the charge stays
pending. - SAMA Money notifies you of the result:
charge.succeededorcharge.failed. - Your server verifies the notification, then fulfills the order.
1. Create the charge
Section titled “1. Create the charge”| Field | Required | Description |
|---|---|---|
client_phone |
yes | Customer number with the country code, 6 to 20 characters (for example 22370000000). |
amount_xof |
yes | Amount in CFA francs: an integer greater than 0. |
description |
no | Label of the charge. |
reference |
no | Your own reference (an order number), stored with the charge and returned in partner_reference. |
The Idempotency-Key header is optional for a charge, but recommended: without it, every call creates a new request. The examples use the test number 22370000000.
curl -sS -X POST https://api.sama.money/v1/charges \ -H "Authorization: Bearer $SAMA_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "client_phone": "22370000000", "amount_xof": 15000, "description": "Order 1042", "reference": "ORD-1042" }'import { randomUUID } from "node:crypto";
const response = await fetch("https://api.sama.money/v1/charges", { method: "POST", headers: { Authorization: `Bearer ${process.env.SAMA_API_KEY}`, "Idempotency-Key": randomUUID(), "Content-Type": "application/json", }, body: JSON.stringify({ client_phone: "22370000000", amount_xof: 15000, description: "Order 1042", reference: "ORD-1042", }),});const data = await response.json();if (!response.ok) throw new Error(`${data.error?.code} (request ${data.error?.request_id})`);console.log(data.reference, data.status);import osimport uuid
import requests
response = requests.post( "https://api.sama.money/v1/charges", headers={ "Authorization": f"Bearer {os.environ['SAMA_API_KEY']}", "Idempotency-Key": str(uuid.uuid4()), }, json={ "client_phone": "22370000000", "amount_xof": 15000, "description": "Order 1042", "reference": "ORD-1042", }, timeout=30,)data = response.json()if not response.ok: raise SystemExit(f"{data['error']['code']} (request {data['error']['request_id']})")print(data["reference"], data["status"])<?phpfunction uuid_v4(): string { $b = random_bytes(16); $b[6] = chr((ord($b[6]) & 0x0f) | 0x40); $b[8] = chr((ord($b[8]) & 0x3f) | 0x80); return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($b), 4));}
$ch = curl_init("https://api.sama.money/v1/charges");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer " . getenv("SAMA_API_KEY"), "Idempotency-Key: " . uuid_v4(), "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "client_phone" => "22370000000", "amount_xof" => 15000, "description" => "Order 1042", "reference" => "ORD-1042", ]),]);$body = curl_exec($ch);$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);$data = json_decode($body, true);if ($status !== 200) { exit("Error {$status}: " . ($data["error"]["code"] ?? "unknown") . " (request " . ($data["error"]["request_id"] ?? "?") . ")\n");}echo $data["reference"], " ", $data["status"], PHP_EOL;The API answers 200 with the pending charge:
{ "reference": "chg_8f3a1c9e2b7d4e60a1c3", "object": "charge", "mode": "test", "amount_xof": 15000, "currency": "XOF", "client_phone": "22370000000", "status": "pending", "description": "Order 1042", "partner_reference": "ORD-1042", "account": null, "error_message": null, "created": 1790848800}Store the reference returned (chg_…) with your order: it is used to read the charge back and it appears in notifications. Your own reference comes back in partner_reference. mode is test with a test key. Other fields may be added to the response; ignore the ones you do not know.
2. The customer confirms
Section titled “2. The customer confirms”The customer receives the request in the SAMA Money app or, if they have no smartphone, in the #600# menu. They confirm it with their secret code or decline it. Until they do one or the other, the request stays pending. Left unanswered for 90 days, it expires: its state becomes expired.
On your side, show a clear waiting screen, for example “Confirm the payment of 15,000 F on your phone”. Never treat a pending request as paid.
3. Receive the result
Section titled “3. Receive the result”Two ways, to be combined:
- Webhook (recommended): SAMA Money sends
charge.succeededorcharge.failedto the address you registered. How to set it up is described in the full documentation, reserved for registered developers. - Reading the status with
GET /v1/charges/{reference}, without polling it in a loop, to catch up on a missed notification or to confirm the state before an important decision:
curl -sS https://api.sama.money/v1/charges/chg_8f3a1c9e2b7d4e60a1c3 \ -H "Authorization: Bearer $SAMA_API_KEY"const reference = "chg_8f3a1c9e2b7d4e60a1c3";const response = await fetch(`https://api.sama.money/v1/charges/${reference}`, { headers: { Authorization: `Bearer ${process.env.SAMA_API_KEY}` },});const data = await response.json();console.log(response.status, data.status);import os
import requests
reference = "chg_8f3a1c9e2b7d4e60a1c3"response = requests.get( f"https://api.sama.money/v1/charges/{reference}", headers={"Authorization": f"Bearer {os.environ['SAMA_API_KEY']}"}, timeout=30,)print(response.status_code, response.json().get("status"))<?php$reference = "chg_8f3a1c9e2b7d4e60a1c3";$ch = curl_init("https://api.sama.money/v1/charges/" . rawurlencode($reference));curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("SAMA_API_KEY")],]);$body = curl_exec($ch);$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);$data = json_decode($body, true);echo $status, " ", $data["status"] ?? "?", PHP_EOL;4. Handle the special cases
Section titled “4. Handle the special cases”| Situation | Status or code | What to do |
|---|---|---|
| The customer confirms | succeeded |
Fulfill the order. |
| The charge does not go through | failed |
Offer another payment method. |
| The customer does not answer | pending |
The request stays pending until it is confirmed or declined. Do not fulfill the order. Before offering a new attempt, read the state with GET /v1/charges/{reference}. |
| Your request was cut off before the response | unknown | Replay the same request with the same Idempotency-Key. |
5. Test in the sandbox
Section titled “5. Test in the sandbox”With a test key, no customer is contacted and the request stays pending. How to simulate the customer’s confirmation is described in the full documentation, reserved for registered developers (see the Reference).
6. Go live
Section titled “6. Go live”Live keys are activated once the account is approved (Accounts and verification). The fee on a charge is 0.3%, paid by the merchant, never by the customer: see the pricing.

