Mobile money is the backbone of payment collection for African ISPs and WISPs. In most markets, the majority of your subscribers do not own a credit card, but almost all of them carry a mobile money wallet. The challenge is rarely accepting the money. It is reliably linking each incoming payment to the right subscriber account and reactivating service automatically, without a staff member squinting at SMS alerts at midnight. This guide walks through integrating M-Pesa Daraja, MTN MoMo, and Airtel Money, and the auto-reconciliation logic that turns a raw payment into a renewed PPPoE or hotspot session.
Why Auto-Reconciliation Matters
Manual reconciliation is where most small ISPs bleed time and goodwill. A subscriber pays, then waits for an agent to confirm the transaction and flip their account back on. The fix is a closed loop: the payment provider notifies your billing system over a webhook, your system matches the payment to a subscriber, marks the invoice paid, and instructs your RADIUS server to authorize the connection. With a RADIUS Disconnect or Change of Authorization (CoA) message, you can push reactivation to a MikroTik NAS in seconds instead of forcing the customer to reconnect manually.
The hardest part of that loop is mapping. Every method below ultimately succeeds or fails on one question: how do you know which subscriber a payment belongs to?
M-Pesa Daraja: STK Push, Paybill and Till
Safaricom's Daraja API is the dominant rail in Kenya and the one most ISPs start with. There are two collection patterns you will use.
STK Push (Lipa na M-Pesa Online). This is the cleanest customer experience. From your self-service portal or an agent screen, you call the STK Push endpoint with the subscriber's phone number, the amount, your shortcode, the passkey, and an AccountReference. The customer gets a prompt on their handset, enters their M-Pesa PIN, and confirms. The request is queued, and Daraja later POSTs the result to your registered CallBackURL.
Store the CheckoutRequestID returned in the synchronous response. When the asynchronous callback arrives, you match on that CheckoutRequestID. A successful callback carries ResultCode 0 and a CallbackMetadata Item array containing Amount, MpesaReceiptNumber, TransactionDate, and PhoneNumber. A non-zero ResultCode means failure, such as 1032 when the user cancels the prompt or 1037 when the handset does not respond. Because you initiated the push against a known subscriber, mapping is trivial: you already know whose payment this is before the money moves. Always respond to the callback with HTTP 200, because Safaricom retries aggressively when it does not receive an acknowledgement.
C2B Paybill and Till (Buy Goods). This covers customers who pay on their own from the M-Pesa menu, which is still the most common behaviour in practice. You register two endpoints with the C2B Register URL API: a ValidationURL and a ConfirmationURL, both mapped to your shortcode. Note that validation is only invoked if external validation is enabled on your shortcode; otherwise M-Pesa completes the payment and calls only your confirmation endpoint.
- •When a customer pays, Daraja first hits your ValidationURL. You inspect the payload, decide whether to accept, and respond. To accept, return ResultCode 0 with ResultDesc Accepted. To reject an unknown or invalid account, return a documented rejection code such as C2B00012 (Invalid Account Number) so M-Pesa cancels the transaction.
- •After the payment completes, Daraja calls your ConfirmationURL with the final transaction. Acknowledge it by responding with ResultCode 0.
For Paybill, the critical field is BillRefNumber, the account number the customer types when paying. This is your mapping key. Train subscribers to enter their account number or the phone number on their account, and the reconciliation is automatic. For Buy Goods Till numbers there is no BillRefNumber, so you fall back to matching the payer MSISDN against the phone number on file, or you dedicate a Till per plan. Validation can also enforce business rules, for example rejecting underpayments before they ever land.
MTN MoMo: Collections and Request to Pay
In West and Central Africa and parts of East Africa, MTN Mobile Money is the equivalent rail. The relevant product is the Collections API, and its core call is RequestToPay, the conceptual twin of STK Push.
You authenticate with OAuth, generate an access token, then POST a RequestToPay with the amount, currency, the payer MSISDN, payer and payee messages, and an X-Reference-Id. That X-Reference-Id is a UUID version 4 you generate, and it serves two purposes: it makes the request idempotent (reusing it returns HTTP 409 Conflict rather than charging twice) and it is the key you use to query transaction status or correlate the callback. Requests also carry the Ocp-Apim-Subscription-Key and an X-Target-Environment header (sandbox or production). Production credentials come from the MTN OVA management dashboard after you complete KYC; develop against the sandbox first. Because you initiate the request against a chosen subscriber, mapping is again deterministic. For customers who push payments without a prior request, poll or reconcile by reference and payer number.
Airtel Money: USSD Push and Callbacks
Airtel Money is widely used across Airtel's African footprint and follows a similar shape. Register an application on the Airtel Developers Portal and authenticate using OAuth 2.0. The collection flow is a USSD push: you send a request with the amount, the customer MSISDN, your transaction reference, plus currency and country code. The customer receives a USSD prompt and authorizes by entering their Airtel Money PIN.
Airtel posts transaction status changes to your callback URL as a JSON object, and you can also query status on demand. Use your own external reference (trans_ref) as the mapping key, correlate it with the returned trans_id, and reconcile by reference plus payer number, exactly as with MoMo.
Mapping Payments to Subscribers
Across all three providers the same principles hold:
- •For initiated payments (STK Push, RequestToPay, USSD push) you already know the subscriber, so persist your own reference and match the callback to it.
- •For customer-initiated Paybill payments, treat the account reference field (BillRefNumber on M-Pesa) as the primary key and validate it before accepting.
- •Where no account field exists (Till, some push payments), match on the payer phone number stored against the subscriber.
- •Always store the provider receipt number (MpesaReceiptNumber and equivalents) to make every payment idempotent and auditable, and to prevent double-crediting on webhook retries.
- •Secure your webhook endpoints: serve them over HTTPS, restrict access to the provider's source ranges where published, and respond quickly with the expected acknowledgement so the provider does not retry needlessly.
For the Kenyan case specifically, M-Pesa billing integration details Paybill versus Till reconciliation, STK Push from the portal, and what happens in the seconds between the Daraja callback and the subscriber coming back online.
Closing the Loop into RADIUS and MikroTik
Once a payment is mapped and the invoice is marked paid, the final step is restoring service. If the subscriber was suspended, your RADIUS server should now authorize them again on the next authentication, returning Access-Accept or moving them out of a walled-garden address pool.
To make reactivation feel instant you need to act on the live session, and here the mechanism differs by service type on MikroTik RouterOS. For PPPoE and other PPP sessions, the reliable approach is to send a RADIUS Disconnect-Message: the session drops and immediately re-establishes, picking up the restored profile and full rate limits on reconnect. RouterOS has historically applied PPP rate-limit changes on reconnect rather than altering a live queue, so a disconnect is the dependable trigger. For HotSpot sessions, a RADIUS CoA can update the live session in place without dropping it. In both cases you must first enable incoming RADIUS on the router (under /radius incoming) and open the incoming RADIUS port on the firewall so the server can reach the NAS.
This is precisely what Iterative Billing automates. It connects to MikroTik RouterOS v6 and v7, ships a built-in RADIUS server with disconnect and CoA support for PPPoE and hotspot authentication, and integrates M-Pesa Daraja (STK Push, Paybill and Till), Co-op Bank, KopoKopo, Flutterwave, SasaPay, Pesapal and Paystack, with automated invoicing, hotspot voucher generation, SMS notifications, a customer self-service portal, and white-label branding. Payments reconcile to subscribers automatically and reactivate service in seconds. You can try it free at https://billing.iterativebilling.com/register.
Practical Rollout Tips
Start with one provider and one collection pattern, usually M-Pesa STK Push from your portal, and prove the full loop end to end before adding Paybill or other networks. Test exhaustively in each provider's sandbox, especially the failure paths: rejected validation, timeouts, duplicate callbacks, and underpayments. Log every raw webhook payload so you can replay and reconcile manually if a provider has an outage. Finally, communicate the account number convention to customers clearly on the payment screen, because clean reconciliation begins with the customer typing the right reference.
Frequently asked questions
- What is the difference between M-Pesa STK Push and Paybill C2B for ISP billing?
- STK Push is a payment you initiate from your system: you send the subscriber's phone number and amount, they get a prompt and enter their PIN, and Daraja sends the result to your callback URL. Because you started it against a known account, mapping is automatic. Paybill C2B is customer-initiated from the M-Pesa menu; the customer types your shortcode and an account number (BillRefNumber), and you map the payment using that reference. STK Push gives a smoother experience, while Paybill matches how many customers already pay on their own. Note that the C2B validation step only fires if external validation is enabled on your shortcode; otherwise M-Pesa calls only your confirmation endpoint.
- How do I automatically match a mobile money payment to the right subscriber?
- Use a stable key for each method. For payments you initiate (STK Push, MTN RequestToPay, Airtel USSD push), store your own reference or X-Reference-Id and match the callback to it. For M-Pesa Paybill, use the BillRefNumber account number the customer enters, and validate it before accepting (reject invalid accounts with a code such as C2B00012). For Till numbers or unsolicited push payments where no account field exists, match the payer phone number against the number on the subscriber's record. Always save the provider receipt number to keep payments idempotent and prevent double-crediting on webhook retries.
- How does a confirmed payment reactivate a suspended internet connection?
- After the payment is mapped and the invoice is marked paid, your billing system authorizes the subscriber again in RADIUS, so the next authentication returns Access-Accept or removes them from a walled-garden pool. To act on the live session on a MikroTik RouterOS NAS, the mechanism depends on the service: for PPPoE and other PPP sessions, send a RADIUS Disconnect-Message so the session drops and instantly reconnects with the restored profile and full rate limits; for HotSpot sessions, a RADIUS Change of Authorization (CoA) can update the live session in place. You must enable incoming RADIUS on the router and open its incoming RADIUS port first. Iterative Billing performs this loop automatically.