Webhooks
A collection settles asynchronously: you create a payment, the customer approves the debit on their handset, and the outcome lands moments (or minutes) later. Webhooks are the reverse channel that pushes that outcome to you the instant it happens, so you do not have to poll GET /payments/{id}/status.
Why webhooks
When you call POST /payments, the response comes back PENDING. The real outcome arrives after the customer enters their Mobile Money PIN. You have two ways to learn it:
- Poll the status endpoint on a timer until it flips to
SUCCESSorFAILED. - Subscribe to a webhook and let Kwik Nkap POST the result to you the moment the payment settles.
Webhooks are the preferred option. They are faster, they cost you no wasted requests against your rate limit, and they free you from running a polling loop. Use them to fulfil orders, mark invoices paid, send receipts, or update a dashboard the moment money moves.
Configure an endpoint
Webhook endpoints are registered and managed in the dashboard, not through the public API. There is no API resource to create an endpoint.
- Open the Developers section of the dashboard.
- Add a webhook endpoint: provide your HTTPS URL and select the events you want to receive.
- Copy the signing secret. It starts with
whsec_and is shown only once. Store it securely.
Endpoints are environment-scoped. An endpoint registered in sandbox only receives events from kn_sk_test_ traffic, and a live endpoint only receives events from kn_sk_live_ traffic. Register a separate endpoint per environment.
Events
There are exactly two events.
| Event | Fires when |
|---|---|
payment.success | A collection completed and money was received. |
payment.failed | A collection or disbursement attempt failed. |
Subscribe an endpoint to one or both. You will not receive any other event types.
The delivery payload
Each delivery is a JSON POST with a consistent envelope: the event name, the data object, a timestamp, and the webhook_id of the delivery.
Code
The data object carries these fields.
| Field | Type | Description |
|---|---|---|
id | string | The public payment ID (knpay_β¦ live, knpay_test_β¦ sandbox). |
amount | integer | Amount in whole XAF francs. |
currency | string | Always XAF. |
direction | string | collection (pull from a customer) or disbursement. |
status | string | Terminal status: SUCCESS or FAILED. |
environment | string | SANDBOX or LIVE. |
business_id | string | Your business identifier. |
transaction_id | string | Internal transaction reference for reconciliation. |
payment_method | string | The settlement rail, e.g. MOBILE_MONEY. |
metadata | object | Any metadata attached to the payment. |
created_at | string | ISO 8601 timestamp when the payment was created. |
updated_at | string | ISO 8601 timestamp when the payment reached this status. |
Headers
Every delivery carries three headers you should read.
| Header | Description |
|---|---|
X-KN-Signature | Hex HMAC-SHA256 of the raw request body, signed with your secret. |
X-KN-Event | The event name, e.g. payment.success. |
X-KN-Webhook-ID | The unique delivery ID, matching webhook_id in the body. |
Verify signatures
Anyone who learns your URL could POST fake events to it. To prove a delivery genuinely came from Kwik Nkap, recompute the HMAC-SHA256 of the raw request body using your whsec_ secret and compare it to the X-KN-Signature header with a timing-safe comparison.
You must hash the exact bytes you received. Do not parse the JSON and re-serialize it first, or the signature will not match. Capture the raw body before any framework deserializes it.
Code
Wire it into an Express handler. Use a raw body parser so you sign the exact received bytes.
Code
Verify the signature on every delivery before acting on it. An unverified webhook is just an unauthenticated HTTP request from the internet. Reject anything whose X-KN-Signature does not match.
Delivery and retries
Kwik Nkap delivers each event with a POST and expects a fast 2xx response.
- Any
2xxstatus code marks the delivery as successful. - The request times out after 30 seconds. Do your real work asynchronously after replying.
- If your endpoint returns a non-
2xxor times out, Kwik Nkap retries up to 5 attempts total. - Retries use exponential backoff (2.5x multiplier) capped at 5 minutes between attempts.
Acknowledge the delivery with 200 immediately, then process the event in a background job. Holding the connection open while you do slow work risks hitting the 30-second timeout and triggering needless retries.
Because retries are possible, design your handler to be idempotent. Key your processing on the payment id (or transaction_id) so that receiving the same payment.success twice does not fulfil an order twice.
Next steps
- Create a payment to generate the events you will receive.
- Check payment status as a fallback when a webhook is missed.
- Test in sandbox with
kn_sk_test_keys and a sandbox webhook endpoint.

