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

  1. Initiate the charge with the amount, currency, phone number, and your externalReference.
  2. Customer approves the payment prompt or completes any required OTP or redirect step.
  3. Listen for the final webhook or query the transaction status.
  4. Fulfill the order only after chargeStatus is successful.

1 - Initiate Charge

FieldTypeRequiredDescription
amountNumberAmount to be charged (e.g. 1500)
currencyStringISO 4217 currency code (e.g. KES, UGX, BWP)
externalReferenceStringYour own unique ID for reconciliation (e.g. invoice number)
phoneNumberStringRecipient phone number in E.164 without the plus (e.g. 254719624551)
momoOperatorIdStringMobile money operator code from the Mobile Money Operators guide. Required for all currencies except KES
otpCodeStringOne-time payment code required by some operators before charge initiation
voucherPinString16-character OTT voucher PIN (required only if currency is BWP and using OTT vouchers)
walletCurrencyStringCurrency to settle funds into (defaults to the value of currency)
successRedirectUrlStringURL to return the customer to after a successful provider-hosted redirect, where supported
failureRedirectUrlStringURL to return the customer to after a failed provider-hosted redirect, where supported

Note: If currency is not KES, you must include momoOperatorId. Additionally, if currency is BWP and you are processing an OTT payment, you must include the voucherPin.

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 otpCode in the charge request.

Redirect support: successRedirectUrl and failureRedirectUrl are 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.

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.

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

  1. Configure your webhook URL in your dashboard account.
  2. Implement webhook endpoint security and validation.
  3. 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 stepRequired is otp: Prompt the customer to enter the OTP they received on their phone.
  • If stepRequired is redirect: Redirect the customer to the provided redirectUrl to complete the payment.

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

FieldTypeRequiredDescription
otpStringThe 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 transaction status response or webhook is redirect, you must forward the customer to the provided redirectUrl to authorise the payment.

Extract the URL: Get the redirectUrl from the webhook or the response from the Get Transaction Status endpoint.

Redirect the User: Redirect your customer to this URL. They will be taken to a secure, external page (like their bank's or mobile money provider's website) to approve the transaction.

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.