MAA INVEST Pay Docs
Launch & support

Errors & limits

Every error message, webhook troubleshooting and the limits at a glance.

When you post to /pay/start.php#

Message shownHTTPCause and fix
Invalid request405The request was not a POST. Post the form; do not link to the URL.
We could not verify this payment request403Unknown or suspended Merchant ID, or the signature does not match. See Signing. (The sandbox adds a hint.)
Request expired400timestamp is more than 30 minutes from our clock. Generate it when you build the form and sync your server clock.
Invalid invoice reference400invoice_id must be 1 to 64 characters of letters, digits, . _ -.
Only KES payments are supported400Send currency=KES.
Amount must be between KES 1 and KES 250,000400Check amount.
Invalid return_url / cancel_url400Use a full http(s) URL without a username or password.
notify_url must be a public HTTPS URL400Use HTTPS on a public host (a tunnel while developing). No localhost, private IPs, or plain http.
M-Pesa payments are temporarily unavailable for this merchant400Live only: the merchant licence has expired. Renew on Merchant Gateway.
This merchant is not ready to accept M-Pesa payments yet400Live only: no paybill or till is connected on Payout Destinations.

On the checkout page#

MessageMeaning
Enter a valid Safaricom numberThe phone number format is not valid.
Too many attempts5 prompts were already sent for this checkout. Start a new one.
Please wait a few seconds…Prompts must be 15 seconds apart (sandbox: 5).
This invoice is already paidThe checkout for this invoice and amount is already paid.
This checkout has expiredMore than 1 hour passed. Start a new checkout.
Could not send the M-Pesa promptLive only: Safaricom rejected the request. Check the number and your Daraja credentials.
Other text from M-PesaPassed through as the failure reason (for example the customer cancelled or had insufficient funds).

Webhook problems#

  • Never arrives: notify_url must be public HTTPS with a valid certificate, must not redirect, and must answer 2xx within 20 seconds. In the sandbox the Merchant Gateway page shows the HTTP status or error of the last delivery.
  • Signature check fails on your side: compute it over the raw body, not re-encoded JSON, with timestamp + "." + body and the secret that matches the Merchant ID.
  • Arrives twice: normal. Retries and manual resends repeat the same receipt: process each receipt once.
  • Customer paid but order still unpaid: call the Status API for the invoice_id; if it says paid, process it exactly as you would the webhook.

Limits at a glance#

ItemValue
CurrencyKES only
Amount per paymentKES 1 to 250,000 (rounded up to whole shillings for the prompt)
Signed request validity30 minutes
Checkout validity1 hour
Prompts per checkout5, at least 15 seconds apart (sandbox: 5 seconds)
Webhook deadline20 seconds to answer 2xx; up to 8 attempts over about 2 days (sandbox: no automatic retry)
Status API timestamp window5 minutes
Sandbox data500 test checkouts per day, kept 30 days
Stuck on an integration? Open a ticket from the Support page in your account and include your invoice_id, the time, and the HTTP status you returned.