Webhook Events

The complete catalogue of events Kyshi sends, when each one fires, and what it carries.

This is the full list of events Kyshi delivers to your webhook URL. For endpoint setup, headers, signature verification, and retry behaviour, see Webhook.

Every payload carries an event field naming the event, and a meta.kyshiEventId that is unique per delivery. Key your deduplication on meta.kyshiEventId.

Collections

Fired when money comes in, whether through checkout, a direct charge, a payment link, or a virtual account.

EventFires when
charge.successA charge or virtual account collection succeeded.
charge.completedA charge reached a completed state at the provider.
charge.failedA charge or virtual account collection failed.
virtual_account.creditA virtual account received a credit.

charge.success is the event most integrations act on. Note that a single customer payment can produce both virtual_account.credit and charge.success — deduplicate on meta.kyshiEventId and treat the transaction reference as the business key.

Always check collectionStatus on virtual account collections before fulfilling. A credit can arrive as PARTIAL, OVERPAID, or REVIEW. See Statuses And Lifecycles.

Payouts

EventFires when
transfer.successA payout reached the beneficiary.
transfer.failedA payout failed.
transfer.reversedA previously successful payout was reversed.

transfer.reversed can arrive well after transfer.success. If your system marks a payout final on success, make sure it can also unwind that decision.

Subscriptions

EventFires when
subscription.activeA subscription became active.
subscription.past_dueA renewal failed and retries began.
subscription.payment_issueA subscription has a payment problem needing attention.
subscription.not_renewingA subscription will not renew at period end.
subscription.completedA subscription reached its invoice or payment limit.
subscription.cancelledA subscription was cancelled.
subscription.invoice_payment_linkA payment link was issued for a subscription invoice.

subscription.invoice_payment_link matters for non-card subscriptions. Subscriptions billed by bank_transfer, bank, or mobile_money cannot be auto-charged, so Kyshi issues a link for the customer to pay each cycle.

Invoices

EventFires when
invoice.createdA subscription invoice was created.
invoice.updatedA subscription invoice changed.
invoice.payment_succeededAn invoice payment succeeded.
invoice.payment_failedAn invoice payment failed.

invoice.payment_failed carries the failure category for the attempt. See Failure Codes.

Payload Shape

All events share the same envelope. Fields that do not apply to an event are omitted or null.

{
  "reference": "KYSHI-123456789",
  "amount": 10000,
  "customer": { "id": "customer-id", "email": "[email protected]" },
  "meta": {
    "localCurrency": "NGN",
    "localAmount": 10000,
    "settlementCurrency": "USD",
    "settlementAmount": 6.67,
    "netAmount": 9750,
    "feeBearer": "CUSTOMER",
    "mode": "live",
    "transactionId": "transaction-id",
    "kyshiEventId": "event-id",
    "kyshiWebhookSentAt": "2026-05-08T12:00:00.000Z"
  },
  "event": "charge.success",
  "status": "success"
}

The full field reference, including authorization and meta.feeBreakdown, is on the Webhook page.

Handling Events Safely

Three rules that cover most of what goes wrong:

Deduplicate. The same business event can be delivered more than once. Store meta.kyshiEventId and ignore repeats.

Do not trust the payload alone for high-value decisions. Treat the webhook as a notification that something changed, then verify against the API before releasing goods or money.

Ignore events you do not recognise. New events are added over time. Return 2xx for anything you do not handle, or Kyshi will retry a delivery your system was never going to process.

app.post('/webhooks/kyshi', async (req, res) => {
  if (!isValidSignature(req)) return res.sendStatus(401);

  // Acknowledge fast, process out of band.
  res.sendStatus(200);

  const { event, meta } = req.body;
  if (await alreadyProcessed(meta.kyshiEventId)) return;

  switch (event) {
    case 'charge.success':
      return handleChargeSuccess(req.body);
    case 'transfer.reversed':
      return handleTransferReversed(req.body);
    default:
      return; // Unknown events are acknowledged, not processed.
  }
});

Your endpoint must respond within 10 seconds, so acknowledge first and do the work afterwards.


Did this page help you?