API Reference · Webhooks
Developer documentation

API Reference · Webhooks

Register one endpoint and receive every payment and payout outcome as a clean, signed event. You integrate once and never touch the underlying provider's payload format.

Events

EventFires when
payment.succeededReconciliation confirms an inbound transfer with the provider. Wallet is credited.
payment.reversedA previously confirmed payment was clawed back. Wallet is debited.
payment.flaggedReconciliation couldn't confirm the payment after retrying — needs manual review.
payout.completedA payout you initiated settled successfully.
payout.failedA payout was rejected by the provider; your wallet was refunded.

Configure

Set your endpoint URL and copy your signing secret (whsec_…, shown once) in Dashboard → Settings.

Verify the signature

Every delivery carries X-Cyrus-Signature, X-Cyrus-Timestamp, and X-Cyrus-Event. The signature covers timestamp + "." + payload— not the payload alone — so the timestamp is part of what's actually signed, not just a sibling header.

// pseudo-code
signedContent = timestamp + "." + rawBody
expected = "sha256=" + hex(hmac_sha256(secret, signedContent))
assert expected === headers["X-Cyrus-Signature"]

Delivery guarantees

  • Idempotent — each delivery carries a stable X-Cyrus-Delivery id; safe to receive more than once.
  • Retried — non-2xx responses are retried with exponential backoff.
  • Respond 2xx once you have stored the event; do slow work asynchronously.
Test deliveries any time from Webhook Testing — no real transfer needed.
Example payload
{
  "event": "payment.succeeded",
  "createdAt": "2026-07-10T08:13:29.663Z",
  "data": {
    "transactionId": "9a61cf0b-…",
    "amountKobo": 15000,
    "feeKobo": 1000,
    "currency": "NGN",
    "status": "SUCCESSFUL",
    "matchStatus": "MATCHED",
    "sessionId": "1000042607…",
    "providerTransactionId": "API-VACT_TRA-…",
    "customerReference": "cust_demo_001",
    "virtualAccountNumber": "2134089159",
    "paidAt": "2026-07-10T08:13:08Z"
  }
}