Mobile Money Charge
This guide explains the end-to-end flow to charge a customer via mobile money, verify the transaction status, and handle webhook notifications.
When To Use This
Use Mobile Money Charge when you already know the customer's mobile money phone number, currency, and operator. This is a direct API flow for mobile money collections where your application owns the payment form.
Use the Universal Payment SDK instead when you want Honeycoin to host the checkout page or let the customer choose from multiple payment methods.
Happy Path
- Initiate the charge with the amount, currency, phone number, and your
externalReference. - Customer approves the payment prompt or completes any required OTP, redirect, or USSD step.
- Listen for the final webhook or query the transaction status.
- Fulfill the order only after
chargeStatusissuccessful.
1 - Initiate Charge
| Field | Type | Required | Description |
|---|---|---|---|
amount | Number | ✅ | Amount to be charged (e.g. 1500) |
currency | String | ✅ | ISO 4217 currency code (e.g. KES, UGX, BWP) |
externalReference | String | ✅ | Your own unique ID for reconciliation (e.g. invoice number) |
phoneNumber | String | ✅ | Recipient phone number in E.164 without the plus (e.g. 254719624551) |
momoOperatorId | String | ❌ | Mobile money operator code from the Mobile Money Operators guide. Use the code for the customer's mobile money network. |
otpCode | String | ❌ | One-time payment code required by some operators before charge initiation |
voucherPin | String | ❌ | 16-character OTT voucher PIN (required only if currency is BWP and using OTT vouchers) |
walletCurrency | String | ❌ | Currency to settle funds into (defaults to the value of currency) |
walletCountry | String | ❌ | Country scope of the settlement wallet. Required when walletCurrency is XOF; optional for USD |
successRedirectUrl | String | ❌ | URL to return the customer to after a successful provider-hosted redirect, where supported |
failureRedirectUrl | String | ❌ | URL to return the customer to after a failed provider-hosted redirect, where supported |
Note: If the API returns
MOMO_OPERATOR_ID_REQUIRED, includemomoOperatorIdwith the code for the customer's mobile money network and retry. ForBWPcharges, include the 16-charactervoucherPin.
Pre-charge OTPs: Some operators require the customer to generate a one-time payment code before you initiate the charge. Follow the operator-specific customer instructions for the market you are collecting in, then send the generated code as
otpCodein the charge request.
Redirect support:
successRedirectUrlandfailureRedirectUrlare relevant for redirect-capable mobile money rails, such as Wave in Senegal (XOF). Some mobile money operators complete through STK, OTP, USSD, or provider prompts and may ignore these fields. Always use webhooks or the get transaction endpoint to confirm the final status before fulfilling an order.
Wallet country scope: If
walletCountryis omitted andwalletCurrencymatchescurrency, Honeycoin uses the country resolved from the customer's phone number. IfwalletCurrencyis different fromcurrency, Honeycoin uses the wallet currency default where supported, such asUSD->US. PasswalletCountryexplicitly when settling into a non-default supported wallet country, and always pass it whenwalletCurrencyisXOF.
To charge a customer, collect the required payment information and send it to the initiate mobile money charge endpoint.
To choose the correct momoOperatorId, view the Mobile Money Operators guide and use the listed code for the customer's country and operator.
Example Request:
{
"amount": 100,
"currency": "KES",
"externalReference": "order_12345",
"phoneNumber": "254719624551"
}{
"amount": 100,
"currency": "KES",
"externalReference": "order_12345",
"phoneNumber": "254719624551",
"momoOperatorId": "airtel"
}{
"amount": 1000,
"currency": "XOF",
"externalReference": "order_wave_sn_12345",
"phoneNumber": "221771234567",
"momoOperatorId": "wave",
"successRedirectUrl": "https://merchant.example.com/payments/success",
"failureRedirectUrl": "https://merchant.example.com/payments/failed"
}{
"amount": 1000,
"currency": "XOF",
"externalReference": "order_otp_12345",
"phoneNumber": "00000000000",
"momoOperatorId": "operator-id",
"otpCode": "123456"
}{
"amount": 150,
"currency": "BWP",
"externalReference": "order_12345_bwp",
"phoneNumber": "26771234567",
"momoOperatorId": "ott-voucher",
"voucherPin": "1234567890123456"
}Below is an example of the response:
{
"success": true,
"message": "Initiated request.",
"transactionId": "123456789"
}Note: Use the returned transactionId (or your externalReference) to track status. Charge initiation is asynchronous. A required step can arrive later in a
transaction_updatedwebhook or a transaction status response; it may not be included in the initiation response.
2 - Get Transaction Status
Always verify the payment status before providing value to your customer. Use the get transaction endpoint with either:
- The transactionId from the charge response or
- Your externalReference
Here's an example of successful and failed transaction:
{
"success": true,
"data": {
"transactionId": "lBK9bMny2gs4hLsG3XGq",
"amount": 25,
"type": "deposit",
"currency": "KES",
"senderCurrency": "KES",
"senderAmount": 25,
"receiverCurrency": "KES",
"receiverAmount": 25,
"chargeStatus": "successful",
"status": "SUCCESSFUL",
"method": "momo",
"note": "TFH174NFSJ Confirmed. Ksh25.00 sent to Honeycoin 722223344 on 17/06/25 at 1:17 AM. Transaction cost, Ksh0.00.",
"fullTimestamp": "2025-06-17T01:16:44+03:00",
"externalReference": "test",
"thirdPartyReference": "TFH174NFSJ",
"phoneNumber": "254722416788",
"stepRequired": "otp",
"redirectUrl": "https://test.com"
}
}{
"success": true,
"data": {
"transactionId": "173675873400000038",
"amount": 102,
"type": "deposit",
"currency": "KES",
"senderCurrency": "KES",
"senderAmount": 102,
"receiverCurrency": "KES",
"receiverAmount": 102,
"chargeStatus": "failed",
"status": "FAILED",
"method": "mpesa",
"note": "STK Prompt Time Out",
"fullTimestamp": "2025-01-14T12:13:59+00:00",
"externalReference": "173675873400000038",
"phoneNumber": "254700000001"
}
}3 - Handle Webhooks
Configure webhooks to receive real-time transaction updates instead of polling the status endpoint.
Setup
- Configure your webhook URL in your dashboard account.
- Implement webhook endpoint security and validation.
- Handle the webhook notifications in your application
Here's a sample of the webhook response:
{
"event": "transaction_created",
"data": {
"transactionId": "BeOfXV1NVIcZlsSVeQAF",
"status": "pending",
"type": "deposit",
"externalReference": "test"
},
"timestamp": "2024-10-03T16:33:14.600Z"
}{
"event": "transaction_updated",
"data": {
"transactionId": "BeOfXV1NVIcZlsSVeQAF",
"status": "successful",
"type": "deposit",
"externalReference": "test",
"method": "momo",
"thirdPartyReference": "TEQTEYRU"
},
"timestamp": "ISO-8601 timestamp"
}{
"event": "transaction_updated",
"data": {
"transactionId": "BeOfXV1NVIcZlsSVeQAF",
"status": "failed",
"type": "deposit",
"externalReference": "test",
"method": "momo",
"note": "User could not be reached by stk"
},
"timestamp": "ISO-8601 timestamp"
}{
"event": "transaction_updated",
"data": {
"transactionId": "BeOfXV1NVIcZlsSVeQAF",
"status": "pending",
"type": "deposit",
"externalReference": "test",
"method": "momo",
"stepRequired": "otp"
},
"timestamp": "ISO-8601 timestamp"
}{
"event": "transaction_updated",
"data": {
"transactionId": "BeOfXV1NVIcZlsSVeQAF",
"status": "pending",
"type": "deposit",
"externalReference": "test",
"method": "momo",
"stepRequired": "redirect",
"redirectUrl": "https://test.com/"
},
"timestamp": "ISO-8601 timestamp"
}- If
stepRequiredisotp: Prompt the customer to enter the OTP they received on their phone. - If
stepRequiredisredirect: Redirect the customer to the provided redirectUrl to complete the payment. - If
stepRequiredisussd: DisplaystepMetadata.ussdCodeand the supplied description, then wait for final confirmation. See Handle USSD Codes.
4 - Validate OTP
If a webhook with stepRequired: 'otp' is received, you need to collect the OTP from the user and send it to the validate otp endpoint to proceed with the transaction.
This is different from a pre-charge otpCode. If an operator requires otpCode before charge initiation, follow the operator-specific customer instructions and include the generated code when creating the mobile money charge instead of using this endpoint.
URL: /api/b2b/fiat/deposit/:transactionId/validate-otp
| Field | Type | Required | Description |
|---|---|---|---|
| otp | String | ✅ | The one-time password provided by the user |
Example Response:
{
"success": true,
"message": "OTP validated successfully.",
"transactionId": "123456789"
}Note: After a successful OTP validation, the transaction will continue processing in the background. You will receive a final SUCCESSFUL or FAILED webhook when the transaction is complete.
5 - Handle Redirects
When the stepRequired field in a pending transaction status response or webhook is redirect, you must forward the customer to the provided redirectUrl to authorise the payment. Orange Money Senegal can use this flow as well as OTP authorization; follow the returned stepRequired rather than assuming the operator always requires an OTP.
Extract the URL: Get data.redirectUrl from the webhook or the response from the Get Transaction Status endpoint. It is alongside data.stepRequired, not inside stepMetadata. Do not require redirectExternal, which is not included in merchant webhook or Get Transaction responses. If the URL is missing or empty, keep the payment pending and fetch the transaction again; contact support if it remains missing rather than constructing a URL or starting a duplicate charge.
Redirect the User: Open this URL as a top-level page in the customer's browser. They will be taken to a secure, external page (like their bank's or mobile money provider's website) to approve the transaction. Do not collect an OTP or call Validate OTP for a redirect step.
If you supplied successRedirectUrl and failureRedirectUrl, the provider may return the customer to one of those URLs after the redirect flow. Treat that return as a customer navigation event, not final payment confirmation.
Await Final Status: After the user completes the authorization you should wait for the final webhook notification (transaction_updated with a status of successful or failed) to confirm the outcome before providing value to the customer.
6 - Handle USSD Codes
Some mobile money providers return a USSD code after the charge has been initiated. While the transaction is pending, Honeycoin reports stepRequired: "ussd" with the code in stepMetadata. The customer dials this code on their phone and follows the provider's prompts to complete the payment.
You can receive this step through a transaction_updated webhook or retrieve it with Get Transaction. Check for it when loading or resuming your payment screen as well as when receiving updates.
| Field | Type | Description |
|---|---|---|
stepRequired | String | ussd identifies the action required for this payment. |
stepMetadata.ussdCode | String | Code returned for this payment. Display it exactly as returned. |
stepMetadata.description | String | Optional text describing how to complete the payment. Display it as plain text when present. |
stepMetadata.expiry | Number | Optional code expiry, expressed as a Unix timestamp in milliseconds. |
The following shortened response illustrates the fields. <provider-returned-code> is a placeholder; always use the code returned for the transaction instead of hardcoding a country or operator code.
{
"success": true,
"data": {
"transactionId": "ussd_payment_123",
"type": "deposit",
"method": "momo",
"externalReference": "order_12345",
"chargeStatus": "pending",
"status": "PENDING",
"stepRequired": "ussd",
"stepMetadata": {
"ussdCode": "<provider-returned-code>",
"description": "Dial <provider-returned-code> and follow the prompts to complete payment",
"expiry": 1790854200000
}
}
}- Display the returned code and description. You can provide a copy button for the code. There is no numbered instructions array in this step.
- Ask the customer to dial the code on their phone and follow the prompts there. There is no USSD confirmation request to submit to Honeycoin; do not send the code to the OTP validation or card authorisation endpoint.
- Keep tracking the same transaction through webhooks or the status endpoint while the customer completes the phone flow. Avoid starting another charge while the original payment is pending.
- Fulfill the order only after a final webhook reports
status: "successful"or a transaction lookup confirmschargeStatus: "successful". Receiving, copying, or dialling the code is not payment confirmation.
When expiry is present and valid, you can show the remaining time using expiry - Date.now(). If it is absent or invalid, omit the countdown. When the code expires, stop offering it for use and continue checking the transaction's final status. Code expiry alone does not establish that the payment failed.
If a pending USSD step has no usable code, retrieve the current transaction before asking the customer to act. Do not invent a replacement code. Handle successful or failed transactions before displaying any remaining step metadata.
This is different from an operator-specific pre-charge OTP flow: a USSD payment step arrives after charge initiation and the customer completes the action on their phone without submitting an otpCode to your application.
Updated 3 days ago
