Initiate a payout
curl --request POST \
--url https://api.cartevo.co/api/v1/payment/payout \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"operator": "mtn",
"country": "CM",
"phone_number": "237670000000",
"amount": 1000,
"currency": "XAF",
"notify_url": "https://example.com/webhook/payout",
"external_id": "<string>",
"reference_id": "<string>",
"lang": "<string>",
"purpose": "<string>"
}
'import requests
url = "https://api.cartevo.co/api/v1/payment/payout"
payload = {
"operator": "mtn",
"country": "CM",
"phone_number": "237670000000",
"amount": 1000,
"currency": "XAF",
"notify_url": "https://example.com/webhook/payout",
"external_id": "<string>",
"reference_id": "<string>",
"lang": "<string>",
"purpose": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
operator: 'mtn',
country: 'CM',
phone_number: '237670000000',
amount: 1000,
currency: 'XAF',
notify_url: 'https://example.com/webhook/payout',
external_id: '<string>',
reference_id: '<string>',
lang: '<string>',
purpose: '<string>'
})
};
fetch('https://api.cartevo.co/api/v1/payment/payout', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.cartevo.co/api/v1/payment/payout",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'operator' => 'mtn',
'country' => 'CM',
'phone_number' => '237670000000',
'amount' => 1000,
'currency' => 'XAF',
'notify_url' => 'https://example.com/webhook/payout',
'external_id' => '<string>',
'reference_id' => '<string>',
'lang' => '<string>',
'purpose' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.cartevo.co/api/v1/payment/payout"
payload := strings.NewReader("{\n \"operator\": \"mtn\",\n \"country\": \"CM\",\n \"phone_number\": \"237670000000\",\n \"amount\": 1000,\n \"currency\": \"XAF\",\n \"notify_url\": \"https://example.com/webhook/payout\",\n \"external_id\": \"<string>\",\n \"reference_id\": \"<string>\",\n \"lang\": \"<string>\",\n \"purpose\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.cartevo.co/api/v1/payment/payout")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"operator\": \"mtn\",\n \"country\": \"CM\",\n \"phone_number\": \"237670000000\",\n \"amount\": 1000,\n \"currency\": \"XAF\",\n \"notify_url\": \"https://example.com/webhook/payout\",\n \"external_id\": \"<string>\",\n \"reference_id\": \"<string>\",\n \"lang\": \"<string>\",\n \"purpose\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cartevo.co/api/v1/payment/payout")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"operator\": \"mtn\",\n \"country\": \"CM\",\n \"phone_number\": \"237670000000\",\n \"amount\": 1000,\n \"currency\": \"XAF\",\n \"notify_url\": \"https://example.com/webhook/payout\",\n \"external_id\": \"<string>\",\n \"reference_id\": \"<string>\",\n \"lang\": \"<string>\",\n \"purpose\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"message": "Payout initiated successfully",
"data": {
"transaction_id": "550e8400-e29b-41d4-a716-446655440000",
"external_id": "PAYOUT-1234567890-abc12345",
"status": "PENDING",
"amount": 1000,
"currency": "XAF",
"operator": "mtn",
"country": "CM",
"phone_number": "237670000000",
"initiated_at": "2025-01-08T12:00:00.000Z",
"error_message": "<string>"
}
}{
"statusCode": 400,
"message": "Insufficient payout balance. Required: 1020 XAF (including fees)",
"error": "Bad Request"
}Payment Collection
Initiate Payout
Send money from your company’s pay-out wallet to a mobile-money account. Calculates and applies fees, deducts atomically, and auto-refunds on failure.
POST
/
payment
/
payout
Initiate a payout
curl --request POST \
--url https://api.cartevo.co/api/v1/payment/payout \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"operator": "mtn",
"country": "CM",
"phone_number": "237670000000",
"amount": 1000,
"currency": "XAF",
"notify_url": "https://example.com/webhook/payout",
"external_id": "<string>",
"reference_id": "<string>",
"lang": "<string>",
"purpose": "<string>"
}
'import requests
url = "https://api.cartevo.co/api/v1/payment/payout"
payload = {
"operator": "mtn",
"country": "CM",
"phone_number": "237670000000",
"amount": 1000,
"currency": "XAF",
"notify_url": "https://example.com/webhook/payout",
"external_id": "<string>",
"reference_id": "<string>",
"lang": "<string>",
"purpose": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
operator: 'mtn',
country: 'CM',
phone_number: '237670000000',
amount: 1000,
currency: 'XAF',
notify_url: 'https://example.com/webhook/payout',
external_id: '<string>',
reference_id: '<string>',
lang: '<string>',
purpose: '<string>'
})
};
fetch('https://api.cartevo.co/api/v1/payment/payout', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.cartevo.co/api/v1/payment/payout",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'operator' => 'mtn',
'country' => 'CM',
'phone_number' => '237670000000',
'amount' => 1000,
'currency' => 'XAF',
'notify_url' => 'https://example.com/webhook/payout',
'external_id' => '<string>',
'reference_id' => '<string>',
'lang' => '<string>',
'purpose' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.cartevo.co/api/v1/payment/payout"
payload := strings.NewReader("{\n \"operator\": \"mtn\",\n \"country\": \"CM\",\n \"phone_number\": \"237670000000\",\n \"amount\": 1000,\n \"currency\": \"XAF\",\n \"notify_url\": \"https://example.com/webhook/payout\",\n \"external_id\": \"<string>\",\n \"reference_id\": \"<string>\",\n \"lang\": \"<string>\",\n \"purpose\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.cartevo.co/api/v1/payment/payout")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"operator\": \"mtn\",\n \"country\": \"CM\",\n \"phone_number\": \"237670000000\",\n \"amount\": 1000,\n \"currency\": \"XAF\",\n \"notify_url\": \"https://example.com/webhook/payout\",\n \"external_id\": \"<string>\",\n \"reference_id\": \"<string>\",\n \"lang\": \"<string>\",\n \"purpose\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cartevo.co/api/v1/payment/payout")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"operator\": \"mtn\",\n \"country\": \"CM\",\n \"phone_number\": \"237670000000\",\n \"amount\": 1000,\n \"currency\": \"XAF\",\n \"notify_url\": \"https://example.com/webhook/payout\",\n \"external_id\": \"<string>\",\n \"reference_id\": \"<string>\",\n \"lang\": \"<string>\",\n \"purpose\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"message": "Payout initiated successfully",
"data": {
"transaction_id": "550e8400-e29b-41d4-a716-446655440000",
"external_id": "PAYOUT-1234567890-abc12345",
"status": "PENDING",
"amount": 1000,
"currency": "XAF",
"operator": "mtn",
"country": "CM",
"phone_number": "237670000000",
"initiated_at": "2025-01-08T12:00:00.000Z",
"error_message": "<string>"
}
}{
"statusCode": 400,
"message": "Insufficient payout balance. Required: 1020 XAF (including fees)",
"error": "Bad Request"
}Overview
POST /payment/payout initiates a pay-out: it sends money from your company’s pay-out wallet to a mobile-money account. Cartevo:
- Calculates the total cost (
amount+ payout fee). - Verifies the pay-out wallet has sufficient balance.
- Reserves the funds atomically.
- Submits the disbursement to the operator.
- If the disbursement fails permanently, automatically refunds the reserved funds back to your wallet.
status: PENDING; the final outcome reaches you via webhook (or via notify_url).
When to use it
- Disburse salaries, payouts, refunds, or commissions to end users.
- Power any “send money” feature in your product.
POST /payment/collect.
Prerequisites
- Your company has a wallet for the requested
(country, currency)pair. - The wallet’s
payout_balanceis at leastamount + fee. (UsePOST /wallets/calculate-transfer-fees— currently undocumented — or estimate from your fee schedule.) - The combination of
country,currency, andoperatoris supported — see Payment guidelines.
Request
Headers
| Name | Required | Description |
|---|---|---|
Authorization | Yes | Bearer <access_token> |
Content-Type | Yes | application/json |
Body
| Field | Type | Required | Constraints / format |
|---|---|---|---|
operator | string | Yes | One of the supported operators — same enum as collection. Lowercase. |
country | string | Yes | ISO 3166-1 alpha-2, uppercase (e.g. CM). |
phone_number | string | Yes | Country code + local number, no + (e.g. 237670000000). Recipient phone number. |
amount | number | Yes | Minimum 1. Major currency units. Excludes fees (the fee is added on top and deducted from your wallet). |
currency | string | Yes | ISO 4217. Must match a wallet you own. |
external_id | string | No | Idempotency key. Up to 255 chars. Duplicates rejected with 400. Auto-generated if omitted. |
notify_url | string | No | HTTPS URL to receive the final status webhook. |
reference_id | string | No | Free-form reference shown in the dashboard. |
lang | string | No | Two-letter language code. |
purpose | string | No | Free-form description (e.g. "Salary May 2026"). |
{
"operator": "mtn",
"country": "CM",
"phone_number": "237670000000",
"amount": 50000,
"currency": "XAF",
"external_id": "PAYOUT-2026-001",
"purpose": "Salary May 2026"
}
Response
200 — Payout initiated
{
"success": true,
"statusCode": 200,
"message": "Payout initiated successfully",
"data": {
"transaction_id": "550e8400-e29b-41d4-a716-446655440000",
"external_id": "PAYOUT-2026-001",
"status": "PENDING",
"amount": 50000,
"currency": "XAF",
"operator": "mtn",
"country": "CM",
"phone_number": "237670000000",
"initiated_at": "2026-05-09T10:15:00.000Z",
"error_message": null
}
}
| Field | Description |
|---|---|
transaction_id | Cartevo transaction ID (UUID). |
external_id | Echoes your idempotency key (or auto-generated). |
status | One of PENDING, PROCESSING, SUCCESS, FAILED. |
error_message | Always null on initiation. Populated on failure (poll the status endpoint or wait for the webhook). |
Lifecycle and atomic behavior
┌──── operator accepts ────→ SUCCESS
PENDING → PROCESSING ──────────→
(funds reserved) └──── operator rejects ────→ FAILED (funds auto-refunded)
- On
PENDING/PROCESSING: the funds are reserved in your wallet but not yet visible as deducted. - On
SUCCESS: the reserved funds are released to the operator. The recipient is credited. - On
FAILED: the reserved funds are returned to your wallet automatically. No manual reconciliation needed.
Error responses
| Status | message example | Trigger |
|---|---|---|
400 | "Insufficient balance (including fees)" | Pay-out wallet has less than amount + fee. |
400 | "Duplicate external_id" | An earlier call used the same external_id. |
400 | "No wallet for this country and currency" | You don’t own a wallet for the (country, currency) pair. |
400 | "Operator not available for this country/currency" | See Payment guidelines. |
401 | "Unauthorized" | Missing or expired token. |
Webhooks fired
- A status-update webhook is sent on each transition (
PENDING→PROCESSING→SUCCESS/FAILED). - If you supplied
notify_url, it receives the same updates.
Code examples
cURL
curl -X POST https://api.cartevo.co/api/v1/payment/payout \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"operator": "mtn",
"country": "CM",
"phone_number": "237670000000",
"amount": 50000,
"currency": "XAF",
"external_id": "PAYOUT-2026-001"
}'
Node.js (axios)
await axios.post(
"https://api.cartevo.co/api/v1/payment/payout",
{
operator: "mtn",
country: "CM",
phone_number: "237670000000",
amount: 50000,
currency: "XAF",
external_id: `PAYOUT-${Date.now()}`,
notify_url: "https://your-app.com/webhooks/cartevo",
},
{ headers: { Authorization: `Bearer ${token}` } }
);
Related
POST /payment/collect— the reverse direction.GET /payment/transactions/{id}/status— poll for the outcome.GET /payment/balance— check your pay-out wallet balance before calling.
Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Body
application/json
Available options:
mtn, orange, moov, airtel, mpesa, afrimoney, vodacom, wave, wligdicash, expresso, free, tmoney, celtiis, coris Example:
"mtn"
Pattern:
^[A-Z]{2}$Example:
"CM"
Example:
"237670000000"
Required range:
x >= 1Example:
1000
Example:
"XAF"
Example:
"https://example.com/webhook/payout"
Idempotency key (up to 255 chars). Duplicate values are rejected with 400 after the first call. Auto-generated if omitted.
Free-form reference shown in the dashboard (up to 255 chars).
Two-letter language code for operator-side prompts (e.g. en, fr).
Free-form description of the payout's purpose.