Documentation menu
Accepting payments

Receive signed events when payments change status.

View raw

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

EventWhen
payment.completedPayment confirmed (status: "success")
payment.failedPayment failed, cancelled or expired

Request

http
1POST https://yourapp.com/webhooks/paylinky
2Content-Type: application/json
3X-Paylinky-Event-Id: 6f1c0f2e-…
4X-Paylinky-Timestamp: 1735643554
5X-Paylinky-Signature: sha256=3b6a…
json
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.

javascript
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 quickly
7 const event = JSON.parse(raw);
8 if (event.event === "payment.completed") await fulfillOrderOnce(event.merchant_reference, event.transaction_id);
9});

Best practices

  1. Respond 2xx quickly (within 8 seconds).
  2. Verify the signature with the raw body.
  3. Make processing idempotent using transaction_id / merchant_reference.
  4. Re-check the status with GET /transaction/{id} before fulfilling high-value orders.
  5. 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.