webhook
Kyshi webhooks send real-time HTTP POST notifications to your configured webhook URL when important events happen on your account, such as successful or failed charges, virtual account payments, invoice events, and subscription lifecycle updates.
Webhooks are sent as JSON and include signature headers so you can verify that the request came from Kyshi.
Setup
Configure your webhook URLs from your business settings in your merchant dashboard:
| Field | Description |
|---|---|
| Test Webhook | Webhook URL used for test-mode transactions and events |
| Live Webhook | Webhook URL used for live-mode transactions and events |
Webhook Endpoint Requirements
Your webhook endpoint must:
- Accept
POSTrequests - Accept
Content-Type: application/json - Respond with a
2xxHTTP status when the webhook is processed successfully - Respond within
10 seconds - Be publicly reachable over
HTTPS
Webhook Headers
Every webhook request includes these headers:
| Header | Description |
|---|---|
| Content-Type | Always application/json |
| User-Agent | Kyshi-Webhook/1.0 |
| X-Kyshi-Event-ID | Unique ID for the webhook delivery attempt |
| X-Kyshi-Signature | HMAC-SHA256 signature of the webhook payload |
| X-Kyshi-Timestamp | Unix timestamp in milliseconds when the webhook was sent |
Validating Webhooks
Kyshi signs each webhook using HMAC-SHA256.
To Validate a Webhook
- Get the raw JSON request body.
- Create an
HMAC-SHA256hash using your mode-specific Kyshi signing secret/API key reference. - Compare your generated hash with the
X-Kyshi-Signatureheader. - Process the webhook only if both signatures match.
Important Note
- Use your test configuration for test-mode webhooks.
- Use your live configuration for live-mode webhooks.
- Process webhooks idempotently; the same business event may be delivered more than once.
- Verify high-value state changes with the API before giving value.
Retry Behavior
Kyshi considers a webhook delivered when your endpoint returns any 2xx status code.
If your endpoint returns a 5xx response or the request fails due to a network error, Kyshi retries up to 3 times
4xx responses are treated as failed delivery attempts and are not automatically retried.
Events
| Event | Description |
|---|---|
| charge.success | A charge or virtual account collection was successful |
| charge.failed | A charge or virtual account collection failed |
| invoice.created | A subscription invoice was created |
| invoice.updated | A subscription invoice was updated |
| invoice.payment_succeeded | A subscription invoice payment succeeded |
| invoice.payment_failed | A subscription invoice payment failed |
| subscription.active | A subscription became active |
| subscription.past_due | A subscription moved to past due |
| subscription.cancelled | A subscription was cancelled |
| subscription.not_renewing | A subscription will not renew at period end |
| subscription.completed | A subscription completed its invoice/payment limit |
Charge Webhook Payload
{
"reference": "KYSHI-123456789",
"amount": 10000,
"customer": {
"id": "customer-id",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "[email protected]",
"phone": "+2348012345678"
},
"originator": null,
"authorization": {
"authorization_code": "AUTH_abc123",
"bin": "408408",
"last4": "4081",
"exp_month": "12",
"exp_year": "2030",
"channel": "card",
"card_type": "visa",
"bank": "Test Bank",
"country_code": "NG",
"brand": "visa",
"reusable": true
},
"log": {},
"meta": {
"localCurrency": "NGN",
"localAmount": 10000,
"fxRate": 1500,
"settlementAmount": 6.67,
"settlementCurrency": "USD",
"settlementSymbol": "$",
"netAmount": 9750,
"feeBearer": "CUSTOMER",
"taxChargeable": "EXCLUSIVE",
"mode": "live",
"transactionId": "transaction-id",
"providerStatus": "success",
"kyshiEventId": "event-id",
"kyshiWebhookSentAt": "2026-05-08T12:00:00.000Z",
"feeBreakdown": {
"vat": 10,
"fee": 200,
"allInclusiveKyshiFee": 200,
"processorFee": 50,
"stampDuty": 0,
"others": {},
"totalFees": 260
}
},
"event": "charge.success",
"status": "success"
}
Important Fields
| Field | Description |
|---|---|
| event | Webhook event name, for example charge.success |
| status | Event status, usually success or failed |
| reference | Unique transaction reference |
| amount | Total local amount charged/collected |
| customer | Customer details |
| originator | Originator details for virtual account/bank transfer payments, when available |
| authorization | Payment authorization details |
| log | Provider/API activity log |
| meta.localCurrency | Local collection currency |
| meta.localAmount | Local amount collected |
| meta.fxRate | FX rate used, when applicable |
| meta.settlementAmount | Settlement amount, when applicable |
| meta.settlementCurrency | Settlement currency, when applicable |
| meta.netAmount | Amount after fees |
| meta.feeBearer | Party responsible for fees |
| meta.taxChargeable | Tax charging mode |
| meta.mode | test or live |
| meta.transactionId | Kyshi transaction ID |
| meta.kyshiEventId | Kyshi webhook event ID |
| meta.kyshiWebhookSentAt | Timestamp when the webhook was sent |
| meta.feeBreakdown | Detailed fee breakdown |
Best Practices
- Verify every webhook signature before processing.
- Use
referenceormeta.transactionIdfor idempotency. - Return
2xxonly after your system has safely accepted the webhook. - Do not rely only on
X-Kyshi-Event-IDfor transaction deduplication because retry attempts may have different delivery IDs. - Log failed signature checks and return a non-
2xxresponse for invalid webhooks.
Updated 2 days ago
