Premium Cloud Hosting!

Contact Info

Hostnali Webhost Limited

+254 748285257

[email protected]

ORDER NOW

Before you start

You need four things in place. Two are on your server, two come from Safaricom.

WhatDetail
WHMCSVersion 8.x, with cron running normally.
PHP8.1 or newer, with the curl, json and mbstring extensions. The module checks these on activation and refuses to install without them.
A public HTTPS addressSafaricom delivers payment results to your server. If your WHMCS is behind a login wall, on an internal network, or reachable only over plain HTTP, payments will be taken but never recorded.
A Daraja appA production app on the Safaricom developer portal, tied to your own paybill or till, giving you a consumer key and consumer secret.
Lipa na M-Pesa OnlineEnabled on your shortcode, which gives you the passkey used for payment requests.
Optional: an API operatorAn initiator name and security credential. Only needed for refunds, live balance checks and verifying customer-supplied codes. Everything else works without them.
Paybill or till? Both work, but they are not equivalent. A paybill asks the payer for an account number, and that number is what lets the module place a payment automatically. A Buy Goods till collects no account number at all, so more payments arrive anonymous and pass through the queue. If you have a choice, use a paybill.

Installation

  1. Upload the files The download contains a modules folder. Upload its contents into your WHMCS installation so that you end up with modules/addons/mpesapro/ and modules/gateways/mpesapro.php alongside your existing modules. Uploading over an earlier version is safe — nothing is deleted.
  2. Restart PHP If your server uses OPcache, which most do, restart PHP-FPM or LiteSpeed after uploading. Skipping this is the most common reason an upgrade appears not to have taken effect.
  3. Activate the addon In WHMCS go to System Settings → Addon Modules, find M-Pesa Payments Pro and activate it. Tick the admin roles that should have access. The module creates its own database tables at this point.
  4. Open the module once Addons → M-Pesa Payments Pro. This confirms the tables exist and shows the dashboard. It will be empty until you add credentials.

First-time setup

1. Credentials

Open Settings → Credentials and fill in:

FieldWhere it comes from
ModeSandbox for testing, Live for real money. Sandbox never moves money.
Consumer key / secretYour app on the Safaricom developer portal.
ShortcodeYour paybill number, or for Buy Goods the store (head office) number.
Shortcode typePaybill or Till. This changes how payments are requested and what the instructions say.
Till numberBuy Goods only. Leave blank if your till and store numbers are the same.
STK passkeyIssued with Lipa na M-Pesa Online for that shortcode.

Press Test connection. It fetches an access token and moves no money. If it fails, the message tells you which credential Safaricom rejected. Do not continue until this passes.

2. Register your callback address

Go to Settings → Paybill. You will see two addresses, both ending in mpp.php/confirmation and mpp.php/validation. Press Register these addresses.

Safaricom refuses some addresses. Any URL containing the words mpesa, m-pesa, safaricom, exe, cmd or sql is rejected outright, which is why the module's endpoint has a deliberately neutral name. If your own domain contains one of those words, the settings screen warns you before you press the button, and the Address Safaricom should call field lets you point at another hostname serving the same site.

Safaricom stores one pair of addresses per shortcode. Registering replaces whatever was there, and the developer portal usually wants the old pair deleted first. If you have used another M-Pesa module before, its addresses are almost certainly the ones on file — check Self Service → URL Management on the portal before assuming payments will reach you.

3. Turn on the gateway

Go to System Settings → Payment Gateways, activate M-Pesa, and set:

Display NameWhat clients see in the payment method dropdown.
Button labelThe text on the button that opens the payment window.
Button CSS classesLeave as btn btn-primary unless your theme uses something else.
Button colourA hex colour, so the button reads as M-Pesa green whatever your theme does. Clear it to inherit your theme instead.
SetupNot a field. It holds an explanatory note; leave the box empty.

4. Prove it end to end

  1. Open one of your own unpaid invoices and pay a small amount with a real phone.
  2. Watch the invoice page — it should turn paid on its own without you reloading.
  3. Open API log in the module. You should see an outgoing payment request and an incoming result within seconds of each other.
  4. If your shortcode is a paybill, also send a small payment directly to it, quoting an invoice number as the account number, and check that it appears in Transactions as applied.

How money reaches you

Payment requests from the invoice page

The client presses your Pay Now button, a window opens, they enter their number and approve the payment on their phone. The page watches for the result and refreshes itself when the invoice is settled. If they close the window, the module keeps watching and reopens it when the result arrives.

The amount requested is always what is still owing, not the invoice total, so a partly paid invoice asks only for the remainder. Safaricom accepts whole shillings only, so an amount with cents is rounded up — see Amounts for how that is absorbed.

Payments made straight to the paybill

Someone can pay your paybill from their phone with no browser involved. Safaricom sends the payment to your registered address, the module records it and works out which invoice it belongs to. If the payer had your invoice open at the time, that page updates itself too.

One payment can produce two records. On a paybill shortcode, a payment request also arrives as a paybill confirmation, because the money lands in the paybill with an account number. Both normally carry the same M-Pesa receipt, so the second is recognised and closed automatically. You may occasionally see one marked Second record of one payment — that is the module doing its job, not a double charge.

Settings reference

Settings are grouped into sections down the left of the Settings tab. Each section saves on its own, so you can change one area without touching another.

Taking payments

SettingWhat it does
Show M-Pesa on the invoice pageWhether clients can start a payment at all. Turn off to leave only the paybill instructions.
Show paybill instructionsAdds step-by-step instructions inside the payment window, for anyone whose prompt does not arrive.
M-Pesa logo addressOptional. Points at your own copy of the logo. Leave blank to use the one supplied.
Account number clients quoteInvoice number (recommended), client ID, or a custom prefix followed by the invoice number.
Seconds to wait for the payerHow long the invoice page waits before giving up on a request. Default 90.
Smallest amount to acceptSafaricom rejects amounts below 1 KES.
Update the invoice page on its ownThe page watches for payment and refreshes itself. Default on.
Check every / Keep checking forHow often, and for how long, before an idle tab stops asking. Defaults 8 seconds and 15 minutes.

Paybill

Accept paybill paymentsWhether payments made straight to your paybill are recorded.
When the account number is wrongAccept everything and sort it out afterwards, or turn the payment away at the till. Turning payments away only works if Safaricom has enabled external validation on your shortcode.
If our server is unreachableWhat Safaricom should do when it cannot deliver a confirmation. Completed lets the payment stand; Cancelled reverses it. Use Completed.
Address Safaricom should callOnly needed if your normal address contains a word Safaricom refuses.

Amounts and fees

When less than the balance arrivesRecord it and leave the rest owing, or hold it for review.
When more than the balance arrivesSettle the invoice and add the extra to client credit, record the lot against the invoice, or hold it.
Ignore small overpayments up toAbsorbs the rounding forced by whole-shilling payments. Default 1.
Invoices in other currenciesRefuse them, or convert using your WHMCS exchange rates.
Record what the payment cost youNone, a percentage, a flat amount, or both. Stored against the payment so net revenue stays accurate. Never charged to the customer.

Matching

Apply confident matches on their ownMaster switch. Off means every payment waits for a person.
Match on the account numberIncluding common typing mistakes. Leave on.
Match on the phone numberLooks for a client whose account carries that number, or who has paid from it before.
Match on the amountOnly helps when exactly one open invoice has that balance. Turning this off removes a lot of noise.
Apply on its own aboveConfidence out of 100. See How matching works for what each figure means.
Clear of the next best guess byHow far ahead the best candidate must be. Stops the module choosing between two near-identical invoices.
Scores are capped at 99, so a threshold of 100 means nothing will ever be applied automatically. If your queue suddenly fills with obviously correct payments, check this first.

Records and privacy

Admin username for paymentsPayments are recorded as this WHMCS admin, and the name appears in the invoice history. Leave blank to use the first active admin.
Hide the middle of phone numbersAdmins see 254712***641 in lists and logs.
Keep API logs forDays. Older entries are removed by the daily cron.
Callback IP allowlistOptional. Comma-separated IPs or CIDR ranges from Safaricom. Blank accepts any source.
Verbose loggingRecords full request and response bodies. Turn off once live.

How matching works

When a payment arrives without being tied to an invoice, the module scores every plausible invoice and records the reasons. Each candidate starts from whichever rule found it, then gains or loses points.

Starting pointScore
The account number matches an invoice exactly70
It matches after repairing a typo50
The phone belongs to a client, or has paid before40
Exactly one open invoice has that balance35
One of several invoices with that balance15
AdjustmentChange
Two rules point at the same invoice+10
The amount equals the balance exactly+20
Paid from the number on that client's account+15
It would leave a balance owing+5
It is more than the balance−10
That invoice is already settled−45

Scores are capped at 0–99. A payment is applied automatically only when all three of these hold: the best score clears your threshold, the invoice still has a balance, and the best candidate beats the runner-up by your margin.

What that means in practice

If you match on the account number only, which is the common setup, the everyday outcomes are:

SituationScoreAt a threshold of 85
Right invoice number, exact amount90Applied automatically
Right invoice number, part payment75Queued
Typo repaired, exact amount70Queued
Right invoice number, overpaid60Queued
Right invoice number, already settled25Queued

Lowering the threshold to 70 lets typo repairs and part payments through while still stopping overpayments and payments against settled invoices — which are exactly the cases that need a human decision.

Typo repair

The module strips spaces, dashes, hashes and dots, removes your configured prefix, and maps the characters people actually mistype on a keypad: O, Q and D become zero; I, L and the pipe become one; S becomes five; Z becomes two; B becomes eight; G becomes six. If the result is not entirely digits, it is not treated as an invoice number — so "HOSTNALI" and "rent for july" correctly match nothing rather than being forced into a number.

Payments that arrive before their invoice

Unmatched payments are retried automatically for seven days, so someone who pays before the invoice is generated is picked up once it exists.

Working the matching queue

The Matching tab lists everything waiting, with a badge showing the count. Each card gives the payer, the account number they gave, the amount, the reason it is waiting, and the candidates with their scores and reasons. The invoice the payer actually named is always shown first and tagged, even when it scores badly.

ActionWhen to use it
ApplyRecord the payment against that candidate invoice.
Apply to that invoiceType any invoice number and apply it there.
Hold as client creditPut the money on the client's account rather than an invoice. The usual answer for a genuine duplicate payment.
Already recordedThe payment is already on an invoice in WHMCS, added by hand or by something else. Closes the row and adopts what WHMCS believes. It refuses if the receipt is on no invoice.
Set asideTake it out of the queue without recording it anywhere.
PayloadSee exactly what Safaricom sent.
Try matching them all againRe-runs matching across everything waiting. Useful after creating a missing invoice.

Rows marked Paid, not yet applied are payments Safaricom confirmed but never named with a receipt. They usually resolve themselves; if not, Reconciliation can recover the receipt from your statement.

Amounts, fees and currency

Rounding

Safaricom accepts whole shillings only. An invoice of KES 3,499.50 therefore always collects 3,500 and overpays by fifty cents — every single time. The Ignore small overpayments tolerance absorbs that silently. Set it to 0 only if you want every cent tracked as credit.

Fees

M-Pesa charges you, the merchant, separately. Recording a percentage or flat cost here does not charge the customer anything — it stores the cost alongside the payment so your net revenue in WHMCS is accurate.

Other currencies

By default, invoices billed in anything other than Kenya Shillings are refused. Switching to convert uses your WHMCS exchange rates, always routing through your default currency. If a rate is missing, the payment is refused rather than converted with a guessed figure.

Reconciliation

Safaricom provides no way to fetch a statement automatically, so this works from the CSV you export from the M-Pesa portal.

  1. Export a statement from the M-Pesa portal covering the period you want to check.
  2. Upload it on the Reconciliation tab. Column names are matched loosely, and only money-in rows are read.
  3. Read the four figures that come back.
ResultMeaning
MatchedPresent in both the statement and WHMCS. Nothing to do.
In the statement onlyMoney you received that WHMCS knows nothing about. Press Bring it in to record it and let matching place it.
In WHMCS onlyRecorded here but absent from the statement. Usually the statement simply does not cover the whole period.
Missing a receiptPayments confirmed by Safaricom but never named. Pick the matching statement line and the payment is recorded properly.

Clearing an imported statement removes only the imported lines. No payment is affected.

Refunds

Refunds send money out of your paybill, so they need the API operator credentials from Settings → Refunds: an initiator name and a security credential.

The security credential is not something Safaricom hands you. It is your API operator's password, encrypted with Safaricom's production certificate and base64-encoded. The password comes from the M-Pesa organisation portal where that operator was created. Sandbox and production certificates differ, and the encrypted output is different every time you generate it — all versions are valid. Never paste your initiator password into an online generator.

The guards

Only the paying numberAlways on, and not configurable. Money can only go back to the number that paid; there is no field to type a destination.
Approval above an amountLarge refunds wait for an admin to approve them.
A different admin must approveThe person who asked cannot approve their own request.
Daily ceilingAcross every admin. Set to 0 for no ceiling.
Record in WHMCSAdds the refund to the client account so your books agree.

A refund cannot exceed the original payment less anything already refunded. Safaricom confirms the outcome separately, usually within a minute, and the log shows the result either way.

Payment codes and tills

A Buy Goods till collects no account number, and many tills never deliver a confirmation to the seller at all. The answer is the one every Kenyan shop already uses: the customer reads out the code from their M-Pesa message.

Switch on Settings → Payment codes and an "Already paid? Enter the M-Pesa code" box appears in the payment window. A code is treated as a claim, not proof, and is checked in this order:

  1. Have we already recorded that receipt? Then it is spoken for.
  2. Did a confirmation for it arrive? Match it and apply.
  3. Is it on a statement you have imported? Same.
  4. Otherwise ask Safaricom directly, if the refund credentials are set.

What stops abuse

  • A code already used against another invoice is refused outright.
  • Safaricom's answer is checked to confirm the money came to your shortcode.
  • A code paid from a number not on the client's account waits for a person, rather than applying itself. You can relax this if your clients routinely pay from a friend's phone or an agent.
  • Six attempts per client per fifteen minutes.
  • Codes must look like codes, so prose never reaches your queue.

Anything unresolved appears at the top of the Matching tab, where you can ask Safaricom again or set it aside.

Paybill balance

Your shortcode balance appears on the dashboard, on each transaction and in the payment email. It reaches the module two ways.

  • With each paybill payment. Safaricom includes the balance on paybill confirmations at no extra cost. If most of your payments come through the invoice page rather than directly to the paybill, this figure will rarely move.
  • By asking. With the refund credentials set, the module asks Safaricom about once an hour, and there is a button to ask immediately.

The figure always states where it came from and when, so a stale number cannot be mistaken for a live one.

Notifications

Three events, each switched on or off separately: a payment received and recorded, a payment that needs matching, and a payment request that did not complete. The third is off by default because it fires on every cancelled prompt.

Notifications go out through your WHMCS SMTP settings, so they come from the same address as your invoices. The settings screen shows which sending method it detected before you send anything, and the Send test email button reports which path was used and why if it failed.

Notifications are queued and sent after Safaricom's callback has been answered, never during it. A slow mail server can therefore never delay or endanger a payment. Anything that could not be sent at the time is retried by the cron.

Client area

Clients get a page listing every M-Pesa payment on their account, with totals and receipt codes, at index.php?m=mpesapro.

Show M-Pesa payments to clientsThe page itself. Turning this off makes the page refuse, not merely hide the link.
Add it to the client area menuThe menu link only. Turn off to keep the page but hide it.
Page titleUsed as both the heading and the menu link.
How many payments to showMost recent first.

Roles and permissions

Off by default: every admin who can open the module can use all of it. Switching on Settings → Access lets you assign fourteen capabilities per WHMCS admin role — seven for seeing each tab, seven for actions such as matching, reconciling, requesting a refund, approving one, changing settings and removing data.

Full Administrators always keep complete access and are not listed, so nobody can lock themselves out. Admins whose role has no access see a plain message rather than an error, and are sent to the first tab they are allowed to see.

Licensing

One licence covers one WHMCS domain. Enter your key either on the addon's configuration screen or under Settings → Licence — both write to the same place. The status panel shows the licensed domain and when it was last checked, with a button to check immediately.

SituationWhat happens
Licence activeEverything works.
Licence server unreachableNothing changes for seven days. A working install is never interrupted by a network problem at our end.
Licence expired or invalidAdmin screens are replaced by a renewal notice, and clients cannot start new payments. Payments already on their way are still received and recorded, and the cron keeps running. No data is deleted.
Local developmentlocalhost and .test, .local and .internal domains bypass licensing entirely.

Accounting

M-Pesa payments are recorded through WHMCS's own payment routine, so any accounting module that reads WHMCS payments picks them up with no extra work — and the module deliberately does not post its own journal, which would double your revenue.

If you use Ledgerly, map the mpesapro gateway to your M-Pesa settlement account under Ledgerly → Tools → Payment Method Mapping, so receipts land in the right account rather than your default bank account.

Upgrading and removing

Upgrading

Upload the new files over the old ones and restart PHP. The database updates itself the next time you open the module. Settings, transactions and history are preserved. No release has ever required a manual database step.

Deactivating

Deactivating the addon stops the module but keeps every record. Reactivating picks up exactly where you left off.

Removing everything

Under Settings → Access, the danger zone removes every table the module created: transactions, statements, refunds, notifications, the API log and your settings. Payments already recorded against invoices in WHMCS are untouched, but the record of how they arrived is gone. It requires typing REMOVE and is available only to Full Administrators.

Troubleshooting

"Invalid ValidationURL — URL has the word MPESA"

Safaricom refuses any callback address containing mpesa, m-pesa, safaricom, exe, cmd or sql. If your own domain contains one, put another hostname pointing at the same site into Address Safaricom should call and register again.

Payments are taken but nothing is recorded

Your callback address is either unregistered or pointing somewhere else. Check Self Service → URL Management on the Daraja portal. If you used another M-Pesa module before, its addresses are probably still on file — Safaricom stores only one pair per shortcode. Delete the old pair, then press Register in the module.

A payment is held even though the client gave the right invoice number

Almost always because that invoice has nothing owing — it was already settled, usually by an earlier payment. The queue states the reason on the card. If it is a genuine duplicate, hold it as client credit or refund it.

Nothing is being applied automatically

Check Apply on its own above. Scores are capped at 99, so any threshold of 100 stops everything. Also confirm Apply confident matches on their own is switched on.

The service stays Pending after payment, though cPanel was created

This is WHMCS provisioning, not the payment. Check Utilities → Logs → Module Log at that moment. If you find a create failure alongside an account that plainly exists, the provisioning attempt was cut off part-way — usually a slow server. To clear an affected order, accept it with Run Module Create unticked, then set the service active. Note that the order staying Pending until an admin accepts it is normal WHMCS behaviour and does not stop the service being active.

The paybill balance is not moving

The free balance figure only arrives with payments made directly to your paybill. If your clients pay from the invoice page, it will sit still. Set the initiator name and security credential and the module will ask Safaricom hourly instead.

Notifications arrive from an odd address

Something like [email protected] means WHMCS is not configured to send through SMTP, so mail is going out through the server's mail program. Set SMTP under Configuration → System Settings → Mail. The module's test email names the path it used.

The same payment appears twice

Expected on a paybill shortcode: a payment request also arrives as a paybill confirmation. The second record is recognised and closed as Second record of one payment. Only one is ever applied to the invoice.

Locked out by the licence check

Press Check the licence now under Settings → Licence. If the admin area is inaccessible, clear the cached status directly:

DELETE FROM mod_mpp_settings
WHERE skey IN ('license_status','license_message','license_lastcheck','license_lastgood');

Leave license_key alone, then load the module page once from the licensed domain.

An upgrade appears to have done nothing

Restart PHP-FPM or LiteSpeed. OPcache will keep serving the previous files otherwise. The version number in the module header tells you which build is actually running.

Technical reference

Callback addresses

All routes live under one endpoint, using a path rather than a query string because Daraja is particular about both:

https://your-whmcs.example/modules/gateways/callback/mpp.php/confirmation
https://your-whmcs.example/modules/gateways/callback/mpp.php/validation
https://your-whmcs.example/modules/gateways/callback/mpp.php/stk

Refund, balance and code-verification results use further routes on the same file, sent automatically with each request; nothing else needs registering.

Database tables

mod_mpp_transactionsEvery payment, with the M-Pesa receipt as a unique key.
mod_mpp_settingsModule configuration.
mod_mpp_api_logCalls to and from Safaricom, secrets removed.
mod_mpp_notificationsQueued emails.
mod_mpp_statementImported statement lines.
mod_mpp_refundsRefund requests and their outcomes.
mod_mpp_claimsPayment codes submitted by clients.
mod_mpp_permissionsRole capabilities.

What the cron does

  • Closes payment requests whose result never arrived, by asking Safaricom.
  • Retries unmatched payments for seven days.
  • Recognises payments WHMCS already has recorded and closes them.
  • Sends anything left in the notification queue.
  • Refreshes the paybill balance, at most hourly.
  • Prunes API logs past your retention setting, and rechecks the licence nightly.

Exports

The Transactions tab exports the current filtered view as CSV, including the split amounts, recorded costs, matching confidence and the paybill balance at the time.

M-Pesa Payments Pro by Hostnali — $4.20 or KSh 500 per month. Still stuck after reading this? Email [email protected] with your WHMCS version and the relevant lines from the module's API log.

M-Pesa and Safaricom are trademarks of Safaricom PLC. This module is not affiliated with or endorsed by Safaricom.