Initiate a Mobile Money deposit
curl --request POST \
--url https://api.winampay.de/api/v1/deposits \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"player_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"msisdn": "+237670123456",
"amount_xaf": 101,
"operator": "mtn",
"reference": "<string>",
"callback_url": "https://sportsbook.example.com/webhooks/payments",
"lang": "fr"
}
'import requests
url = "https://api.winampay.de/api/v1/deposits"
payload = {
"player_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"msisdn": "+237670123456",
"amount_xaf": 101,
"operator": "mtn",
"reference": "<string>",
"callback_url": "https://sportsbook.example.com/webhooks/payments",
"lang": "fr"
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
player_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
msisdn: '+237670123456',
amount_xaf: 101,
operator: 'mtn',
reference: '<string>',
callback_url: 'https://sportsbook.example.com/webhooks/payments',
lang: 'fr'
})
};
fetch('https://api.winampay.de/api/v1/deposits', 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.winampay.de/api/v1/deposits",
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([
'player_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'msisdn' => '+237670123456',
'amount_xaf' => 101,
'operator' => 'mtn',
'reference' => '<string>',
'callback_url' => 'https://sportsbook.example.com/webhooks/payments',
'lang' => 'fr'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$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.winampay.de/api/v1/deposits"
payload := strings.NewReader("{\n \"player_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"msisdn\": \"+237670123456\",\n \"amount_xaf\": 101,\n \"operator\": \"mtn\",\n \"reference\": \"<string>\",\n \"callback_url\": \"https://sportsbook.example.com/webhooks/payments\",\n \"lang\": \"fr\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
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.winampay.de/api/v1/deposits")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"player_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"msisdn\": \"+237670123456\",\n \"amount_xaf\": 101,\n \"operator\": \"mtn\",\n \"reference\": \"<string>\",\n \"callback_url\": \"https://sportsbook.example.com/webhooks/payments\",\n \"lang\": \"fr\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.winampay.de/api/v1/deposits")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"player_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"msisdn\": \"+237670123456\",\n \"amount_xaf\": 101,\n \"operator\": \"mtn\",\n \"reference\": \"<string>\",\n \"callback_url\": \"https://sportsbook.example.com/webhooks/payments\",\n \"lang\": \"fr\"\n}"
response = http.request(request)
puts response.read_body{
"winam_tx_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"reference": "<string>",
"status": "provider_acknowledged",
"amount_xaf": 5000,
"operator": "mtn",
"expires_at": "2023-11-07T05:31:56Z",
"collection_mode": "merchant_initiated",
"instructions": {
"ussd_string": "#150*14*123456*659521872*10000#",
"dial_uri": "tel:%23150*14*123456*659521872*10000%23",
"copy_text": "<string>",
"merchant": {
"msisdn": "<string>",
"code": "<string>",
"display_name": "<string>"
},
"polling_interval_seconds": 123,
"steps": [
{
"order": 123,
"text": "<string>"
}
],
"illustration_url": "<string>",
"illustration_alt": "<string>"
}
}Payments
Deposits
Create a Mobile Money deposit. The response tells you how the payment is collected and what to show the player.
POST
/
api
/
v1
/
deposits
Initiate a Mobile Money deposit
curl --request POST \
--url https://api.winampay.de/api/v1/deposits \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"player_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"msisdn": "+237670123456",
"amount_xaf": 101,
"operator": "mtn",
"reference": "<string>",
"callback_url": "https://sportsbook.example.com/webhooks/payments",
"lang": "fr"
}
'import requests
url = "https://api.winampay.de/api/v1/deposits"
payload = {
"player_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"msisdn": "+237670123456",
"amount_xaf": 101,
"operator": "mtn",
"reference": "<string>",
"callback_url": "https://sportsbook.example.com/webhooks/payments",
"lang": "fr"
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
player_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
msisdn: '+237670123456',
amount_xaf: 101,
operator: 'mtn',
reference: '<string>',
callback_url: 'https://sportsbook.example.com/webhooks/payments',
lang: 'fr'
})
};
fetch('https://api.winampay.de/api/v1/deposits', 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.winampay.de/api/v1/deposits",
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([
'player_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'msisdn' => '+237670123456',
'amount_xaf' => 101,
'operator' => 'mtn',
'reference' => '<string>',
'callback_url' => 'https://sportsbook.example.com/webhooks/payments',
'lang' => 'fr'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$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.winampay.de/api/v1/deposits"
payload := strings.NewReader("{\n \"player_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"msisdn\": \"+237670123456\",\n \"amount_xaf\": 101,\n \"operator\": \"mtn\",\n \"reference\": \"<string>\",\n \"callback_url\": \"https://sportsbook.example.com/webhooks/payments\",\n \"lang\": \"fr\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
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.winampay.de/api/v1/deposits")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"player_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"msisdn\": \"+237670123456\",\n \"amount_xaf\": 101,\n \"operator\": \"mtn\",\n \"reference\": \"<string>\",\n \"callback_url\": \"https://sportsbook.example.com/webhooks/payments\",\n \"lang\": \"fr\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.winampay.de/api/v1/deposits")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"player_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"msisdn\": \"+237670123456\",\n \"amount_xaf\": 101,\n \"operator\": \"mtn\",\n \"reference\": \"<string>\",\n \"callback_url\": \"https://sportsbook.example.com/webhooks/payments\",\n \"lang\": \"fr\"\n}"
response = http.request(request)
puts response.read_body{
"winam_tx_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"reference": "<string>",
"status": "provider_acknowledged",
"amount_xaf": 5000,
"operator": "mtn",
"expires_at": "2023-11-07T05:31:56Z",
"collection_mode": "merchant_initiated",
"instructions": {
"ussd_string": "#150*14*123456*659521872*10000#",
"dial_uri": "tel:%23150*14*123456*659521872*10000%23",
"copy_text": "<string>",
"merchant": {
"msisdn": "<string>",
"code": "<string>",
"display_name": "<string>"
},
"polling_interval_seconds": 123,
"steps": [
{
"order": 123,
"text": "<string>"
}
],
"illustration_url": "<string>",
"illustration_alt": "<string>"
}
}How it works
A deposit is collected in one of two modes, returned ascollection_mode. The request is the same in both; what you show the player differs. See Deposit collection modes for the full integration guide.
- merchant_initiated
- customer_initiated
1
Call POST /deposits
Your backend sends the player’s MSISDN, the amount and a unique
reference.2
Payment request sent
winam-payments sends a payment request to the player’s phone.
3
Player confirms
The player confirms with their Mobile Money PIN.
4
Webhook sent
Your
callback_url receives payment.succeeded, payment.failed or payment.expired.1
Call POST /deposits
Your backend sends the player’s MSISDN, the amount and a unique
reference.2
Display the instructions
The response carries an
instructions object: the USSD code to dial, a dial link and localized steps.3
Player dials the code
The player dials the code from their own phone and confirms with their Mobile Money secret code.
4
Webhook sent
Your
callback_url receives payment.succeeded once the payment is confirmed, or payment.expired.| Operator | Collection mode |
|---|---|
| MTN Mobile Money | merchant_initiated |
| Orange Money | customer_initiated |
The mode is configured per payment channel. Always read
collection_mode from the response instead of deriving it from the operator.Request
| Field | Description |
|---|---|
player_id | Player UUID on your side |
msisdn | Player’s Mobile Money number. For a customer-initiated deposit, the player must pay from this number |
amount_xaf | Amount in XAF, minimum 100. XAF has no sub-unit |
operator | mtn or orange |
reference | Your unique reference for this payment (idempotency key) |
callback_url | Optional. URL that receives the outcome webhook. If omitted, the default callback URL configured for your application is used (if any) |
lang | Optional. Language of instructions.steps: fr (default) or en. Any other value is rejected with 422 |
Use
POST /api/v1/msisdn/detect to detect the player’s operator from their phone number. The deposit endpoint rejects with 422 any request whose msisdn prefix clearly contradicts operator (MTN: 67x/68x/650-654 · Orange: 69x/655-659).The
reference field is your idempotency key. Two calls with the same reference return the same transaction (and the same instructions) without creating a duplicate. Use a different reference for each distinct payment. A reference already used by another application returns 409. See Idempotency.Response
| Field | Description |
|---|---|
winam_tx_id | Transaction identifier. Use it with GET /api/v1/transactions/{winam_tx_id} |
reference | Your reference, echoed back |
status | State after creation (see below) |
amount_xaf, operator | Echoed from the request |
expires_at | Deadline for the payment. After it, the deposit becomes expired |
collection_mode | merchant_initiated or customer_initiated |
instructions | Player instructions for a customer_initiated deposit while it is pending; null otherwise |
status after creation
collection_mode | status | Meaning |
|---|---|---|
merchant_initiated | pending | Payment request sent or being sent; waiting for the player |
merchant_initiated | provider_acknowledged | The provider acknowledged the request; waiting for the player |
merchant_initiated | failed | The operator rejected the request at initiation |
customer_initiated | pending | Waiting for the player to dial the code and pay |
HTTP
200 does not mean the deposit succeeded. A 200 only means the request was processed. A merchant-initiated deposit that the operator rejects at initiation (invalid number, amount below the operator minimum, USSD error…) comes back as 200 with status: "failed"; the reason is in the state_reason field of GET /api/v1/transactions/{winam_tx_id}.| Event | Meaning |
|---|---|
payment.succeeded | Payment received |
payment.failed | Player refused, or provider error |
payment.expired | No payment seen before expires_at |
instructions object
| Field | Description |
|---|---|
ussd_string | USSD code the player dials. Its amount is always the transaction amount |
dial_uri | tel: URI opening the phone dialer with the code (# encoded as %23) |
copy_text | Text to copy into the dialer when dial_uri cannot be opened |
merchant.code | Point-of-sale code, when the operator requires one. May be null |
merchant.msisdn | Number receiving the payment, 9 local digits |
merchant.display_name | Recipient name shown on the operator confirmation screen. May be null |
steps | Localized steps {order, text}, to display in order |
illustration_url, illustration_alt | Optional illustration of the dialing flow and its alt text. null when not configured |
polling_interval_seconds | Suggested interval for polling GET /api/v1/transactions/{winam_tx_id} |
Examples
Values are placeholders.{
"winam_tx_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"reference": "platform-bet-slip-98765",
"status": "pending",
"amount_xaf": 5000,
"operator": "mtn",
"expires_at": "2025-01-01T12:30:00Z",
"collection_mode": "merchant_initiated",
"instructions": null
}
{
"winam_tx_id": "9b2f6c1e-3d4a-4f5b-8c7d-1e2f3a4b5c6d",
"reference": "platform-bet-slip-98766",
"status": "pending",
"amount_xaf": 5000,
"operator": "orange",
"expires_at": "2025-01-01T12:30:00Z",
"collection_mode": "customer_initiated",
"instructions": {
"ussd_string": "#150*14*000000*659000000*5000#",
"dial_uri": "tel:%23150*14*000000*659000000*5000%23",
"copy_text": "#150*14*000000*659000000*5000#",
"merchant": {
"code": "000000",
"msisdn": "659000000",
"display_name": "EXAMPLE SHOP"
},
"steps": [
{"order": 1, "text": "From phone +237690000001, dial the USSD code shown or tap “Dial”."},
{"order": 2, "text": "On iPhone, if “Dial” does not open, tap “Copy” and paste the code into the Phone app."},
{"order": 3, "text": "Check the recipient EXAMPLE SHOP and the amount of 5,000 FCFA, then confirm with your Mobile Money secret code."},
{"order": 4, "text": "Stay on this page: your account is credited automatically as soon as the operator's confirmation SMS is received."}
],
"illustration_url": null,
"illustration_alt": null,
"polling_interval_seconds": 5
}
}
Customer-initiated deposits: what to implement
1
Show the instructions
Display the
steps, the code, a Dial button opening dial_uri and a Copy button copying copy_text.2
Keep the deposit pending
The transaction stays
pending until the payment is confirmed.3
Poll or wait for the webhook
Poll
GET /api/v1/transactions/{winam_tx_id}?lang=en every polling_interval_seconds (the response carries the same collection_mode and instructions while pending), or wait for payment.succeeded on your callback_url.iOS: a
tel: URI containing * or # may not open the dialer. Always offer the Copy button so the player can paste the code into the Phone app.- The player must pay from the number sent in
msisdnand must not change the amount in the code. See the matching rules. - The player’s Mobile Money secret code is typed on their phone only. Never ask for it, transmit it or store it.
- A customer-initiated deposit that
expiredcan still endsucceededif the player paid late: you receivepayment.expired, thenpayment.succeededfor the samewinam_tx_id. See Webhooks.
503 — channel temporarily unavailable
A customer-initiated deposit is refused with 503, and no transaction is created, when the channel cannot confirm payments at the moment. Retry later or offer another payment method.GET /api/v1/{country}/providers/status then reports the provider as degraded or unavailable.
The detail is a fixed message:
{ "detail": "Deposits are temporarily unavailable for this operator. Please retry later or use another payment method." }
Body
application/json
Response
Successful Response

