Webhooks

Events & payload

What LokiPay sends to your app, and when.

LokiPay sends an HTTP POST to your webhook URL (Settings → Webhook) whenever a payment reaches an outcome. Webhooks are written in the same database transaction as the status change (outbox pattern), so an event is never lost or sent for a change that didn’t happen.

Events#

typeWhenDo this
payment.verifiedA MoMo SMS verified the payment, or an admin approved it.Fulfil the order. Use receivedAmountPesewas.
payment.reviewThe payment needs an admin decision (see reviewReason).Show the customer “we’re checking your payment”.
payment.expiredThe reference window ended without payment.Optional: remind the customer. It can still be verified by a late SMS or claim.
payment.rejectedAn admin rejected the payment in review.Tell the customer; do not fulfil.

Request#

http
POST /api/payments/lokipay/webhook HTTP/1.1
Content-Type: application/json
User-Agent: LokiPay-Webhook/1.0
X-LokiPay-Event: payment.verified
X-LokiPay-Event-Id: evt_6f1c0a9e2b8d4f7a9c3e5b1d2f4a6c8e
X-LokiPay-Signature: t=1762077672,v1=5f2b…c91e

Payload#

json
{
  "id": "evt_6f1c0a9e2b8d4f7a9c3e5b1d2f4a6c8e",
  "type": "payment.verified",
  "created": "2026-11-02T10:01:12.000Z",
  "data": {
    "intentId": "cmvymj4a1000621twmsz5y6i5",
    "shortRef": "Q9N2LPU7",
    "purpose": "payment",
    "reference": "ORDER-1001",
    "description": "Order #1001",
    "customer": { "ref": "cus_42", "name": "Ama" },
    "metadata": { "cart": "c_93" },
    "paymentLinkId": null,
    "payer": null,
    "amountPolicy": "AT_LEAST",
    "status": "VERIFIED",
    "expectedAmountPesewas": 15000,
    "receivedAmountPesewas": 15000,
    "claimedTransactionId": "92481234567",
    "reviewReason": null,
    "verifiedAt": "2026-11-02T10:01:12.000Z",
    "transaction": {
      "transactionId": "92481234567",
      "provider": "MTN",
      "amountPesewas": 15000,
      "reference": "Q9N2LPU7",
      "payerName": "AMA SERWAA",
      "payerPhone": "0241112233",
      "receivedAt": "2026-11-02T10:01:10.000Z"
    }
  }
}

For payment-link payments, purpose is payment_link, paymentLinkId is set and payer holds the name/phone collected on the page.

Responding#

  • Verify the signature first — see verify signatures.
  • Return any 2xx within 10 seconds. Do slow work (emails, stock updates) after responding.
  • Be idempotent: store id (or data.intentId + type) and ignore repeats. LokiPay may deliver an event more than once.