Togo Mobile Money Gate
Accept Moov Money (Flooz) and Mixx by Yas pay-ins on a hosted page where the payer approves a push on their phone, and send mobile-money payouts in West African CFA Franc (XOF).
When to use it
The Togo Mobile Money Gate accepts mobile-money pay-ins (Moov Money, branded Flooz, and Mixx by Yas) on a MagicPayments-hosted page, and sends mobile-money payouts to a phone number. All amounts are in XOF (West African CFA Franc).
| Operation | Rail | How the integration looks |
|---|---|---|
| Pay-in | Mobile money (push to payer) | Create an invoice, redirect the payer to the hosted page. |
| Pay-out | Mobile money (phone) | Server-to-server call to the phone payout endpoint. |
The CFA Franc has 0 decimal places, so the minor unit equals the major one:
"amount": 5000 means 5,000 F CFA — not 50.00. Sending 500000 for a
5,000 F CFA order overcharges the payer by 100×. This is the single most common mistake on this
corridor.
You need your signing credentials from Getting started,
one pay-in gate_id per operator (Moov and Yas are separate gates), and
one pay-out cascade_id per operator. Your account manager provides
all of them.
Pay-in — hosted mobile-money page
The payer never touches your servers with their payment details. You create an invoice; we host the page where they enter their own mobile-money number and approve the charge on their handset.
Each operator is its own gate, because the operator is fixed before the page opens — the payer is not
asked to pick one. Choose the gate_id by the payer's number. Togolese mobile numbers are
8 digits with no leading zero; the country code is 228.
| Operator | Number starts with | Gate |
|---|---|---|
| Moov Money (Flooz) | 78, 79, 96, 97, 98, 99 | your Moov gate_id |
| Mixx by Yas | 70, 71, 72, 73, 90, 91, 92, 93 | your Yas gate_id |
-
Create the invoice
Call the gate endpoint with the operator's
gate_idand the amount in XOF. If you already know the payer's number, pass it incustomer.phone_number— the page pre-fills it. -
Redirect the payer
Send the customer's browser to the
invoice_urlwe return. The page is branded for that operator, accepts only that operator's numbers, and shows that operator's approval steps in French (English is selectable). The payer confirms the number and presses Pay. -
The payer approves on their phone
The operator pushes a confirmation request to the handset and the payer enters their mobile-money PIN.
If nothing arrives, the page tells them to dial the operator's USSD code (
*155#for Moov,*145#for Yas) and approve the pending request there. -
Receive the result
We call your
callback_urlon every status change and you can poll invoice status as a fallback.
/api/invoice{
"gate_id": "tg-moov-gate",
"invoice": {
"invoice_id": "order-tg-2026-000123",
"currency": "XOF",
"amount": 5000,
"description": "Wallet top-up",
"ttl_minutes": 30
},
"customer": {
"id": "cust-77120",
"full_name": "Kossi Mensah",
"email": "[email protected]",
"phone_number": "+22896123456"
},
"workflow_hooks": {
"callback_url": "https://merchant.example.com/mp/callbacks",
"return_success_url": "https://merchant.example.com/orders/123/done",
"return_decline_url": "https://merchant.example.com/orders/123/retry"
}
}
Python
resp = signed_post("/api/invoice", {
"gate_id": "tg-moov-gate", # the Moov gate — +228 96… number below
"invoice": {
"invoice_id": "order-tg-2026-000123",
"currency": "XOF",
"amount": 5_000, # 5,000 F CFA — XOF has 0 decimal places
"description": "Wallet top-up",
"ttl_minutes": 30,
},
"customer": {
"id": "cust-77120",
"full_name": "Kossi Mensah",
"email": "[email protected]",
"phone_number": "+22896123456",
},
"workflow_hooks": {
"callback_url": "https://merchant.example.com/mp/callbacks",
"return_success_url": "https://merchant.example.com/orders/123/done",
"return_decline_url": "https://merchant.example.com/orders/123/retry",
},
})
invoice = resp.json()
redirect_url = invoice["invoice_url"] # send the payer here
Response
{
"request_id": "0f0b1d2e-...",
"request_status": "success",
"invoice_id": "62051f64-9a79-4edb-8a9e-95c86c55ee4e",
"merchant_invoice_id": "order-tg-2026-000123",
"invoice_status": "unpaid",
"invoice_url": "https://stage.example-mp.com/public/invoice/62051f64-.../gate",
"message": "Invoice (Gate PayIn) with internal uid=62051f64-... has been created."
}
The operator sees the charge under customer.id; use the same id for the same payer across
orders so repeat payments are recognised as one customer. full_name and email are
optional on this corridor but are forwarded when present. The payer's IP address is captured from the
hosted page — you don't need to send it.
Tracking the pay-in
Look up the invoice by your own id (or our invoice_uid). It reaches paid on success.
/api/invoice/statusresp = signed_post("/api/invoice/status", {
"merchant_invoice_id": "order-tg-2026-000123"
})
invoice = resp.json()
# invoice["status"] -> "paid" | "payment_failed" | "expired" | "canceled" | ...
# invoice["actual_payment"]["processing_info"]["amount_acquired"] -> settled XOF
The charge sits in processing until the payer approves the push on their handset, which
can take a couple of minutes. Set a ttl_minutes that gives them room, and treat
processing as "not finished yet" rather than as a failure.
Pay-out — mobile money to a phone
Payouts go server-to-server. The destination is a phone number plus the operator that
serves it. Like pay-ins, payouts are split per operator: the cascade_id decides
which operator the payout is sent through, and phone.provider must name that same
operator. Always pass cascade_id explicitly — relying on your wallet's default cascade
sends every payout through one operator regardless of the number.
| Operator | phone.provider | Cascade |
|---|---|---|
| Moov Money (Flooz) | moov | your Moov payout cascade_id |
| Mixx by Yas | yas | your Yas payout cascade_id |
/api/payment/payout/phone{
"cascade_id": "tg-yas-payout",
"payment": {
"payment_id": "payout-tg-2026-000045",
"currency": "XOF",
"amount": 30000,
"description": "Affiliate payout"
},
"phone": {
"provider": "yas",
"phone_number": "+22890123456"
},
"customer": {
"id": "partner-2391",
"full_name": "Afi Lawson"
},
"workflow_hooks": {
"callback_url": "https://merchant.example.com/mp/callbacks"
}
}
Python
resp = signed_post("/api/payment/payout/phone", {
"cascade_id": "tg-yas-payout", # the Yas cascade — +228 90… number below
"payment": {
"payment_id": "payout-tg-2026-000045",
"currency": "XOF",
"amount": 30_000, # 30,000 F CFA
"description": "Affiliate payout",
},
"phone": {"provider": "yas", "phone_number": "+22890123456"},
"customer": {"id": "partner-2391", "full_name": "Afi Lawson"},
"workflow_hooks": {"callback_url": "https://merchant.example.com/mp/callbacks"},
})
payout = resp.json()
payout_uid = payout["payment_id"] # our uid; status starts at "initiated"
A payout for a Yas number sent through the Moov cascade is forwarded to Moov as a Moov payout and
fails at the operator. Decide the cascade from the number's prefix (table above) before you call, and
keep phone.provider in step with it.
Tracking the pay-out
Payouts use the payment status endpoint (not the invoice one).
/api/payment/statusresp = signed_post("/api/payment/status", {
"merchant_payment_id": "payout-tg-2026-000045"
})
# resp.json()["status"] -> "success" | "processing" | "decline" | "error"
Phone number format
Send the destination as the local 8-digit 9XXXXXXX / 7XXXXXXX form, the full
2289XXXXXXX form, or in international format (+228 96 12 34 56) — spaces, dashes and
parentheses are accepted and stripped, and the 228 country code is added when missing. There is
no trunk zero in Togo; a leading 0 is dropped if you send one.
Testing & go-live
- On stage, use the test
gate_id/cascade_idfrom your account manager; no real funds move. - Verify you can drive a pay-in to
paidand a payout tosuccess, and that your callback handler verifies the signature and is idempotent. - Confirm your XOF amount conversion once more before go-live:
amountis a whole number of francs, with no ×100 anywhere in your code. - Check both operators end to end with both gates and both payout cascades — an integration that works on one is not proof the other is routed. Sending a Yas number to the Moov gate is rejected on the page, not by the operator.
- Per-transaction limits apply on this corridor (pay-in roughly 200 to 1,000,000 F CFA, payout roughly 500 to 1,000,000 F CFA); your account manager confirms the exact range for your terminal.
- Swap base URL and credentials to production; the request shapes are identical.