Common MoMo and Airtel Money API Errors in Uganda (and How to Fix Them)
The most common MoMo and Airtel Money API errors fall into five categories: (1) authentication failures (wrong API keys, expired tokens, incorrect Base64 encoding), (2) callback delivery failures (localhost URLs, HTTPS issues, server downtime), (3) transaction errors (insufficient balance, wrong phone format, duplicate references), (4) timeout errors (customer never confirmed), and (5) environment mismatches (sandbox credentials in production or vice versa). Most can be diagnosed by logging every request and response, and fixed by checking credentials, callback URL accessibility, and request formatting.
Authentication Errors (401 and 403)
These are the errors you will hit first and the ones that cause the most frustration. If you cannot authenticate, nothing else works.
MoMo API Authentication Errors
- 401 Unauthorized when getting an access token: Your API User ID or API Key is wrong. Double-check: did you Base64-encode the credentials correctly? The format is Base64(apiUserId:apiKey). A single extra character or missing character breaks it.
- 401 with a valid token: Your token has expired. MoMo tokens have a limited lifespan (typically 3600 seconds). Your code must check expiry and refresh before making API calls.
- 401 on the subscription key: You are using the wrong Ocp-Apim-Subscription-Key, or you have not subscribed to the correct API product (Collections vs. Disbursements). Go back to the developer portal and verify your subscription.
- 403 Forbidden: You are trying to access a production endpoint with sandbox credentials, or vice versa. Check your X-Target-Environment header and base URL.
Airtel Money Authentication Errors
- 401 on OAuth token request: Your client ID or client secret is wrong. Airtel Money uses a different authentication mechanism from MoMo. Verify you are using the correct credentials format for the Airtel Money OAuth endpoint.
- Token format mismatch: Make sure you are sending the token as "Bearer {token}" in the Authorization header, not just the raw token string.
General advice: test authentication in isolation before building anything else. Use Postman, curl, or a simple script to get a token. If that works, your credentials are correct. If it fails, do not move on until it works.
Callback Delivery Failures
The callback is where MoMo or Airtel Money tells your server whether the payment succeeded. If your server never receives the callback, your application never knows the payment result. This is the most common integration headache.
- Callback URL is localhost: MoMo and Airtel Money cannot reach localhost. During development, use ngrok, Cloudflare Tunnel, or a similar tool to create a public URL that tunnels to your local server. In production, your server must be deployed with a public domain.
- Callback URL is HTTP, not HTTPS: Some providers require HTTPS for callback URLs. Use an SSL certificate. Let's Encrypt provides free certificates.
- Server is not returning 200 quickly enough: Your callback handler must return a 200 OK response quickly (within a few seconds). If your handler does heavy processing (database writes, sending emails, calling other APIs) before responding, the provider may time out and retry. Process the callback data, respond with 200, then do the heavy work asynchronously.
- Firewall blocking the callback: If your server is behind a firewall or security group, it may be blocking incoming POST requests from the provider's IP range. Check your firewall rules.
- Wrong callback URL in the request: You specify the callback URL when you send the payment request (or configure it on the developer portal). If the URL has a typo, trailing slash mismatch, or wrong port, the callback goes to the wrong place.
Debugging tactic: add logging to your callback endpoint that records every incoming request, including headers and body. If nothing appears in your logs, the callback is not reaching your server (a network/URL issue). If the callback appears but your app is not processing it, the issue is in your handler code.
Always implement status polling as a backup. After sending a payment request, poll the provider's status endpoint every few seconds. Even if the callback fails, polling gives you the result.
Transaction Errors
These errors occur during the payment flow itself, after authentication succeeds.
- Insufficient balance: The customer's mobile money account does not have enough funds. Your app should display a clear message suggesting the customer top up and retry. Do not retry automatically.
- Invalid phone number format: The MSISDN must include the country code (256 for Uganda). "0771234567" is wrong; "256771234567" is correct. Validate and format the number before sending the request.
- Duplicate external ID / reference: Each transaction must have a unique reference (UUID v4). If you accidentally reuse a reference from a previous transaction, the API will reject it. Generate a fresh UUID for every payment request.
- Amount too small or too large: Providers have minimum and maximum transaction limits. If your amount falls outside these limits, the request will fail. Check current limits on the provider's documentation.
- Wrong currency code: For Uganda, use "UGX". Sending "USD" or "RWF" will either fail or process in the wrong currency.
- Subscriber not found: The phone number is not registered for mobile money with that provider. Either the number is wrong, or the customer has not activated mobile money on their account.
For each error, the provider's response body usually contains an error code and a reason field. Log these. The error messages can be cryptic, but they narrow down the problem significantly.
Timeout and Pending State Issues
Timeouts are not bugs in your code. They are a normal part of mobile money payment flows. The customer might not have their phone nearby, might be in an area with poor network, or might simply decide not to confirm.
- Transaction stays in PENDING state: This means the customer has not confirmed yet. Do not treat PENDING as FAILED. Give the customer time (2-3 minutes is reasonable) and then show a timeout message with a retry option.
- Callback never arrives for a PENDING transaction: The customer may have let the prompt expire. Poll the status endpoint to get the final state. If the final state is still PENDING after your timeout, treat it as a timeout and let the customer retry.
- Race condition between callback and polling: Your callback handler and your polling logic might both try to update the transaction at the same time. Use database-level locking or an idempotent update (only update if the current state is PENDING) to prevent inconsistencies.
Design principle: never block the user indefinitely. Set a client-side timeout (show a "Still waiting..." message after 30 seconds, a "Payment timed out" message after 2-3 minutes) and a server-side timeout (stop polling after a defined period). Give the customer a clear path to retry or cancel.
Environment and Configuration Mismatches
These errors are easy to introduce and hard to diagnose because the error messages often look like authentication failures:
- Sandbox credentials in production (or vice versa): Your API keys, base URL, and X-Target-Environment must all match the same environment. If you are using sandbox keys with the production URL, you get a 401 that looks like a credential problem but is actually an environment problem.
- Hardcoded values instead of environment variables: If you hardcoded "sandbox" as your target environment during development and forgot to change it for production, all your production requests will fail. Use environment variables for base URL, target environment, and all credentials.
- Mixed credentials: Using the API User ID from one environment and the API Key from another. This happens when developers copy credentials from the portal and paste them into the wrong config file.
Prevention: use a .env file (never committed to git) with clearly named variables like MOMO_API_USER_ID, MOMO_API_KEY, MOMO_SUBSCRIPTION_KEY, MOMO_TARGET_ENV, and MOMO_BASE_URL. Load these in your application code. Have separate .env files for development (sandbox) and production.
For a complete guide on managing this transition, see our sandbox testing guide.
A Systematic Debugging Strategy
When something is not working and you are not sure why, follow this sequence:
- Check the error response. Read the full response body, not just the status code. The error message or reason field usually points to the problem.
- Check your credentials and environment. Are you using the right API keys for the right environment? Is your target environment header correct? Is your base URL correct?
- Test authentication in isolation. Can you get an access token? Use curl or Postman to test, removing your application code from the equation.
- Test the API call in isolation. With a valid token, can you send a simple Request to Pay via curl or Postman? If yes, the issue is in your code. If no, the issue is in your request format.
- Check your callback endpoint. Is it publicly reachable? Can you hit it manually from a different network? Is it returning 200?
- Check your logs. Every request you send and every response/callback you receive should be logged. If you do not have logs, add them. Then reproduce the error.
If you are still stuck, the developer communities around MoMo and the aggregators can help. Post your error code (never post your API keys), the request you are making (with credentials redacted), and the response you are getting. Experienced developers can usually spot the issue from the error code and request format.
McTaba's M-Pesa Integration course (KES 9,999, approximately UGX 280,000) includes modules on debugging mobile money integrations, covering the systematic approach to isolating and fixing callback, authentication, and transaction errors. The debugging patterns transfer to MoMo and Airtel Money.
Key Takeaways
- ✓Most MoMo and Airtel Money API errors are authentication or configuration problems, not code logic problems. Check your credentials and environment settings first.
- ✓Log every API request (headers, body) and every response (status code, body). Without logs, debugging mobile money integrations is guesswork.
- ✓Callback delivery failures are the single most common integration issue. Your callback URL must be publicly accessible via HTTPS, and your server must return a 200 status quickly.
- ✓Environment mismatches (sandbox keys in production, or the wrong X-Target-Environment header) cause confusing errors that look like credential problems.
- ✓When stuck, test the simplest possible request manually (using Postman or curl) to isolate whether the issue is in your code or in your API configuration.
Frequently Asked Questions
- Why does my MoMo API call return 401 even though my credentials are correct?
- Most likely an environment mismatch. You are using sandbox credentials with a production URL (or vice versa), or your access token has expired. Check that your base URL, X-Target-Environment header, and credentials all match the same environment. Also verify your token has not expired.
- Why is my callback endpoint not receiving any requests?
- The most common causes are: your URL is localhost (use ngrok for development), your URL is HTTP instead of HTTPS, your server is behind a firewall blocking incoming requests, or the callback URL in your payment request has a typo. Test by hitting your callback URL manually from a different device or network.
- How do I handle a transaction that is stuck in PENDING?
- Poll the status endpoint with the transaction reference ID. If the status remains PENDING beyond your timeout period (2-3 minutes is reasonable), treat it as a timeout. Show the customer a message explaining the payment was not confirmed and offer a retry option. Do not create a new order; let the customer retry payment on the same order.
- Are MoMo and Airtel Money error codes the same?
- No. Each provider has its own error codes and response formats. However, the categories of errors are the same (authentication, transaction, callback, timeout). Your code should map provider-specific error codes to your own internal error types so your application logic is not tied to one provider's error format.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon