POST to your server when a payment, refund or payout succeeds or fails.
Set up an endpoint
1
Build a route on your server
It must accept a
POST with a JSON body at a public https:// address, for example https://api.yourshop.mw/webhooks/primepay.2
Add it in the dashboard
Open your account in the dashboard, go to Settings › Webhooks and add the address. Choose which events it receives.
3
Save the signing secret
Each endpoint has its own signing secret, starting with
whsec_. Store it on your server like a password. You use it to check every webhook.4
Send a test event
Send a test event from the endpoint’s page in the dashboard. Your endpoint receives a
webhook.test event; answer 200 and the dashboard shows it as delivered.- It must start with
https://, on port 443 or 8443. - It must use a public host name, not an IP address or
localhost. - It can’t contain a user name or password.
- An account can have up to 5 endpoints.
Events
Transfers have no events: a transfer’s result is in the API response.
What a webhook looks like
Webhook bodies use
snake_case names and send amounts as text with two decimals ("25000.00"). The API itself uses camelCase and numbers. merchant_reference is the merchantOrderId you sent.
Inside
data.object:
- Payments have
customer.msisdn(the payer’s number, partly hidden),checkout_sessionwhen the payment came through a hosted checkout or payment link, andsub_merchant(the account code) on aggregator payments. - Refunds have
payment, the id of the payment refunded, anddestination. - Payouts have
destination, where the money went. metadatais whatever you sent when you created it.
Check the signature
Anyone can send aPOST to your address, so check every webhook before you trust it. The PrimePay-Signature header has a timestamp and one or more signatures:
- Read
tand everyv1from the header. - Refuse the webhook if
tis more than 5 minutes from now. This stops an old webhook being replayed. - Compute an HMAC-SHA256 of
t, a full stop, and the raw request body, using your whole signing secret (whsec_…) as the key. Write it as lowercase hex. - Accept the webhook if your result equals any
v1, compared in constant time.
Changing a signing secret
You can change an endpoint’s secret in the dashboard. If you keep the old one working during the change, PrimePay signs with both for 24 hours and the header carries twov1 values. Accepting a webhook when any v1 matches, as the code above does, means nothing breaks while you switch.
Answer quickly
- Answer with any
2xxstatus as soon as you have stored the event. Do slow work (emails, stock updates) afterwards. - PrimePay waits up to 10 seconds for your answer. No answer in that time counts as a failure.
- Redirects are not followed. A
3xxanswer counts as a failure. - Answer with JSON or an empty body. If your address answers with a web page, the dashboard flags it, because that usually means a website answered and not your webhook code.
Retries
If your endpoint doesn’t answer2xx, PrimePay tries again, up to 9 times over about 3 days:
If you answer with a
Retry-After header, PrimePay waits at least that long, up to 24 hours.
After 5 failures in a row you get an email. An endpoint that has failed every delivery for 3 days is switched off; fix it, switch it back on in the dashboard and resend what you missed from the endpoint’s delivery list.
Handle each event once
Because of retries, the same event can reach you more than once. Before acting on an event, check whether you have already handled itsid (also in the PrimePay-Event-Id header), and answer 2xx without doing the work again if you have.
Events can also arrive out of order. Don’t rely on the order; use the status in the event, or fetch the payment with GET /v1/payments/{reference}.
A payment’s own callbackUrl
When you create a payment you can send acallbackUrl. That payment’s events are then sent there as well as to your endpoints.
- The address must be HTTPS and its host must be one of your endpoints’ hosts, or a host listed under Settings › Security as an allowed callback host. Otherwise the delivery is skipped.
- These deliveries are signed with the secret of your account’s default endpoint, so check them the same way.
Testing
- A test event from the dashboard sends
webhook.testto one endpoint. - Sandbox payments, refunds and payouts send webhooks too, with
"livemode": false. - Every delivery, with your server’s answer, is listed under Settings › Webhooks for each endpoint, and you can resend one from there.

