Skip to Content
Webhooks

Webhooks

Cashonrails sends webhooks when transaction and payout states change. Webhooks are asynchronous, so your application should treat them as the final confirmation channel and use verify endpoints for reconciliation.

Configure URLs

Configure separate test and live webhook URLs in Settings > API Keys and Webhook. Use HTTPS endpoints only.

Delivery Contract

Cashonrails sends collection and payout webhooks to the URL configured for the request mode. A webhook can be retried or manually resent, so every handler must be idempotent.

Respond with HTTP 200 quickly after storing or queuing the event. Do expensive processing after acknowledgement.

Outbound Webhook Security

Merchant-facing webhook deliveries include signature headers generated from merchant credentials. Verify signatures against the raw request body before trusting the event.

HeaderMeaning
AuthorizationBearer token containing your configured webhook key when present.
payloadsignatureHMAC-SHA512 of the raw payload using your secret key.
merchantsignatureHMAC-SHA512 of your public key using your secret key.
timestampDelivery timestamp. Use this in your replay-protection policy.
X-Webhook-ReferenceTransaction or payout reference associated with the event.

Payload signature verification:

expected = HMAC_SHA512(raw_request_body, secret_key)

Compare expected with the payloadsignature header using a constant-time comparison. Do not parse and reserialize JSON before computing the HMAC; sign the exact raw body bytes received by your server.

Merchant signature verification:

expected = HMAC_SHA512(public_key, secret_key)

Collection Webhook Example

{ "event": "transaction", "data": { "id": 13787, "business_id": 46, "currency": "NGN", "amount": "100", "trx": "202503311338792079044", "reference": "COR-kxkv7lsvpaktxuhhuiytn", "channel": "banktransfer", "type": "transaction", "domain": "live", "fee": "1.6", "requested_amount": "100", "status": "success", "gateway": "vfd", "paid_at": "2025-03-31 13:38:34", "customerinfo": { "customer_code": "CUS_qgfaagpfpbu8cms", "email": "customer@example.com", "status": "active" } }, "payment_account": null, "sender_details": { "sender_account_name": "CASHONRAILS INFRASTRUCTURE LIMITED", "sender_account_number": "0114314606", "sender_bank_code": "090286", "sessionId": "090286250331133812177637260116" } }

Payout Webhook Example

{ "status": "00", "event": "payout", "currency": "NGN", "amount": "50", "fee": "25", "account_name": "JOHN DOE", "account_number": "9113146539", "bank_code": "100004", "bank_name": "OPAY", "narration": "test", "trx": "2025032514551037062256", "reference": "COR-2025032514551037062256", "sessionid": "090110250325145519844017129603", "domain": "live", "statusMessage": "success", "note": null, "timestamp": "2025-03-25T14:55:17.000000Z" }

Handler Pattern

  1. Read the raw request body before JSON parsing.
  2. Verify payloadsignature; optionally verify Authorization, merchantsignature, timestamp freshness, and source IP controls.
  3. Persist the raw event, headers, event type, reference, and signature validation result.
  4. Return HTTP 200 quickly once the event is safely stored or queued.
  5. Process idempotently using event, reference, trx, and current local state.
  6. Verify the final state through GET /api/v1/transaction/verify/{reference} or GET /api/v1/fetch_transfer_details/{reference} before irreversible fulfilment.

Resending Webhooks

Authenticated resend endpoints are available for recovery and testing:

GET /api/v1/resend_transaction_webhook/{reference} GET /api/v1/resend_payout_webhook/{reference}

These endpoints are throttled. Use them for targeted recovery, not routine polling.

Testing

Test-mode webhooks use test-mode credentials and contain domain: "test". Your QA suite should cover:

  • Valid signed collection webhook.
  • Valid signed payout webhook.
  • Duplicate webhook with the same reference.
  • Invalid payloadsignature.
  • Missing signature headers.
  • Old timestamp according to your replay window.
  • Webhook arriving before the customer redirect completes.
  • Webhook retry after your endpoint returns a non-200 response.
Last updated on