Receive signed events when payments change status.
Webhooks
Set your default endpoint in Developers → Webhooks, or send notify_url with a payment. The URL must be public http(s) — localhost and private networks are blocked.
Events
| Event | When |
|---|---|
payment.completed | Payment confirmed (status: "success") |
payment.failed | Payment failed, cancelled or expired |
Request
1POST https://yourapp.com/webhooks/paylinky2Content-Type: application/json3X-Paylinky-Event-Id: 6f1c0f2e-…4X-Paylinky-Timestamp: 17356435545X-Paylinky-Signature: sha256=3b6a…1{2 "event": "payment.completed",3 "transaction_id": "0b9c7f0e-2d0b-4d76-9f5a-6b8a1c1d2e3f",4 "reference": "PLK-7XK2M9QPLA",5 "merchant_reference": "order_cm_12345",6 "status": "success",7 "payment_method": "mobile_money",8 "amount": 5000,9 "fee_amount": 300,10 "credited_amount": 4700,11 "currency": "XAF",12 "country_code": "CM",13 "operator": "MTN Mobile Money",14 "phone": "+2376XXXXXXXX",15 "sandbox": false,16 "created_at": "2026-01-01T10:00:00.000Z",17 "expires_at": null,18 "timestamp": "2026-01-01T10:00:41.000Z"19}Verify the signature
The signature is a hex HMAC-SHA256 of timestamp + "." + raw_request_body using your whsec_… secret. Verify against the raw body *before* JSON parsing and reject timestamps older than 5 minutes.
1const { Paylinky } = require("./paylinky.js");2 3app.post("/webhooks/paylinky", express.raw({ type: "application/json" }), async (req, res) => {4 const raw = req.body.toString("utf8");5 if (!Paylinky.verifyWebhook(raw, req.headers, process.env.PAYLINKY_WEBHOOK_SECRET)) return res.sendStatus(401);6 res.sendStatus(200); // acknowledge quickly7 const event = JSON.parse(raw);8 if (event.event === "payment.completed") await fulfillOrderOnce(event.merchant_reference, event.transaction_id);9});Best practices
- Respond
2xxquickly (within 8 seconds). - Verify the signature with the raw body.
- Make processing idempotent using
transaction_id/merchant_reference. - Re-check the status with
GET /transaction/{id}before fulfilling high-value orders. - Never trust the browser redirect.
Delivery & retries
Each event is delivered once immediately and logged (status, HTTP code, response excerpt) in Developers → Webhooks, where you can see failures. If your server was down, reconcile with GET /transaction/{id} or GET /transactions?status=success; the Paylinky team can also retry failed deliveries from the admin panel. Rotate the secret any time from the dashboard.
In the sandbox, use POST /sandbox/simulate to trigger payment.completed / payment.failed to your endpoint.