Skip to main content
Kotani Pay pushes notifications to your server when key events happen — transaction status changes, refund outcomes, KYC updates, and system notices.

Two Delivery Modes

How notifications are delivered depends on whether a webhook secret is configured on your account.

Signed webhooks

When a webhook secret is configured, all events are delivered through the signed system. Each POST request includes: The body is always wrapped in this envelope:
The signature field in the body mirrors X-Kotani-Signature — the header is the source of truth for verification.

Direct callbacks

If no webhook secret is configured, Kotani Pay posts directly to the callbackUrl set on each transaction at the time it was created. These requests:
  • Are sent as POST with a JSON body
  • Do not include X-Kotani-Signature, X-Kotani-Event, or X-Kotani-Integrator headers
  • Contain the transaction fields directly in the body — no event or signature wrapper
Configure a webhook secret in Settings to switch to signed webhooks.

Supported Events

Settlement events are opt-in. Subscribe to them in Settings → Webhooks.

Verifying Signatures

Always verify the X-Kotani-Signature header before processing any event.
  1. Parse the JSON body.
  2. Remove the signature field from the parsed object.
  3. Compute sha256=HMAC-SHA256(secret, JSON.stringify({event, data})).
  4. Compare with X-Kotani-Signature using a timing-safe comparison.

Event Payloads

Casing conventions: Deposit fields use snake_case (reference_id, wallet_id, customer_key). Withdrawal, onramp, and offramp fields use camelCase (referenceId, walletId, customerKey). Handle both in your webhook handler.

transaction.deposit.status.updated

Fired whenever a deposit changes status — including intermediate states like INITIATED and IN_PROGRESS as well as terminal states (SUCCESSFUL, FAILED, CANCELLED).

Payload fields

string
required
Current deposit status. See Transaction Statuses.
string
required
Your reference ID (or system-generated if not provided).
number
required
Auto-generated sequential reference number.
string
required
Kotani internal record ID.
number
required
Amount the customer was asked to pay.
string
required
ID of the integrator fiat wallet credited.
number
required
Amount actually credited to your wallet after fees.
number
required
Processing fee charged.
string
required
The customer identifier supplied at deposit creation.
string
Callback URL set on the transaction.
string
ISO 8601 creation timestamp.
string
Mobile money receipt code from the network (e.g. Mpesa confirmation code like OEI2AK4D9X). Present on successful mobile money deposits.
string
Provider-level confirmation reference. May differ from telco_id for some providers.
string
Bank name for bank-based deposits (e.g. Capitec, FNB). Only present for bank deposits.
string
Bank code for bank-based deposits. Only present for bank deposits.
string
Card payment brand for card deposits (e.g. VISA, MASTERCARD). Only present for card deposits.
string
Human-readable failure reason. Always present (may be empty string) for non-successful statuses.
string
Detailed provider error description. Always present (may be empty string) for non-successful statuses.
string
Provider error code. Always present (may be empty string) for non-successful statuses.
string
Raw internal error from the processing pipeline. Always present (may be empty string) for non-successful statuses.

Example — successful mobile money deposit

Example — failed deposit

Example — bank deposit (additional fields)


transaction.withdrawal.status.updated

Fired whenever a withdrawal changes status.

Payload fields

string
required
Current withdrawal status.
string
required
Your reference ID.
number
required
Auto-generated sequential reference number.
string
required
Kotani internal record ID.
number
required
Amount requested for withdrawal.
string
required
ID of the integrator fiat wallet debited.
number
required
Amount debited from your wallet including fees.
number
required
Processing fee charged.
string
required
The customer identifier supplied at withdrawal creation.
string
Callback URL set on the transaction.
string
ISO 8601 creation timestamp.
string
Mobile money receipt code from the network. Present on successful mobile money payouts.
string
Provider-level confirmation reference.
number
Additional integrator fee charged on the transaction, if configured.
string
Human-readable failure reason. Always present (may be empty string) for non-successful statuses.
string
Raw error from the processing pipeline. Always present (may be empty string) for non-successful statuses.

Example — successful withdrawal

Example — failed withdrawal


transaction.onramp.status.updated

Fired when a fiat→crypto onramp transaction changes status. Onramp has two independent status fields — fiat collection (depositStatus) and on-chain delivery (onchainStatus).

Payload fields

string
required
Your reference ID for the onramp transaction.
string
required
Combined overall status of the onramp.
string
required
Status of the fiat payment collection leg.
string
required
Status of the on-chain crypto delivery leg.
string
required
Blockchain the crypto was sent on (e.g. POLYGON, STELLAR).
string
required
Token delivered (e.g. USDT, USDC).
number
required
Expected crypto amount to deliver.
number
Actual crypto amount delivered on-chain (may differ from cryptoAmount due to gas).
number
required
Base fiat amount collected (before fee).
number
required
Platform fee on the fiat side.
number
required
Total fiat the customer paid (fiatAmount + fiatFee).
string
On-chain address the crypto was delivered to.
string
Blockchain transaction hash once on-chain delivery completes.
object
Rate used for the conversion (from, to, cryptoAmount).
object
Error details if the onramp failed. Contains message, code, and details.

Example — successful onramp

Example — fiat collected, crypto transfer failed


transaction.offramp.status.updated

Fired when a crypto→fiat offramp changes status.

Payload fields

string
required
Your reference ID for the offramp transaction.
string
required
Overall offramp status (SUCCESSFUL, FAILED, PENDING, etc.).
string
required
Status of the on-chain crypto receipt leg.
number
required
Full fiat amount before fees.
number
required
Amount actually disbursed to the recipient after fees.
number
required
Crypto amount received from the sender.
string
required
Fiat currency code (e.g. KES, GHS).
string
required
Customer identifier.
string
required
On-chain address that sent the crypto.
string
required
Kotani escrow address the crypto was sent to.
string
ID of the integrator fiat wallet used, if applicable.
string
On-chain transaction hash of the crypto receipt.
number
Exact on-chain amount confirmed (may differ from cryptoAmount due to network fees).
object
Rate used for the conversion (from, to, fiatAmount).
boolean
Whether the platform’s own integrated crypto wallet was used.
string
ISO 8601 creation timestamp.
string
ISO 8601 last-updated timestamp.
object
On-chain error details if the crypto receipt failed. Present (may be {}) for non-successful transactions.
object|string
Fiat disbursement error details. Present (may be {}) for non-successful transactions.

Example — successful offramp

Example — fiat disbursement failed


refund.completed

Fired when a crypto refund has been successfully sent back to the sender.

Payload fields

string
required
Reference ID of the original offramp transaction.
string
required
Always REVERSED.
string
required
Always SUCCESSFUL.
string
required
On-chain transaction hash of the refund.
number
required
Amount refunded (in token native units — sats for Lightning, token units for EVM/Solana).
string
required
Chain the refund was sent on.
string
required
Token refunded.
string
required
Fiat currency of the original transaction.
string
required
ISO 8601 timestamp of the refund.

Example


refund.failed

Fired when a refund has exhausted all retry attempts. Manual intervention is required — contact support with the referenceId.

Payload fields

string
required
Reference ID of the original offramp transaction.
string
required
Always FAILED.
number
required
Amount that was attempted for refund.
string
required
Chain the refund was attempted on.
string
required
Token that was being refunded.
string
required
Fiat currency of the original transaction.
string
required
Error message from the last refund attempt.
number
required
Number of refund attempts made before giving up.
string
required
ISO 8601 timestamp of the final failure.

Example


refund.lightning.invoice_needed

Fired when a Lightning offramp’s fiat disbursement fails and Kotani needs a bolt11 invoice to return the funds. You must submit a valid invoice before the refund can proceed. See Offramp Refunds for the full Lightning refund lifecycle.

Payload fields

string
required
Reference ID of the original offramp transaction.
string
required
Status of the offramp (typically FAILED).
string
required
On-chain crypto receipt status (typically SUCCESSFUL — crypto was received).
string
required
Always INVOICE_NEEDED when this event fires.
number
required
Amount to be refunded in millisatoshis.
number
required
Amount to be refunded in satoshis.
string
required
Always LIGHTNING.
string
required
Fiat currency of the original transaction.
boolean
required
Always true — you must submit an invoice.
object
required
Instructions for submitting the invoice. Contains type, description, submitUrl, method, body, and invoiceRequirements.

Example

Submit the invoice to the action.submitUrl:

Settlement events

Settlement events are opt-in — subscribe to them in Settings → Webhooks. All single-settlement events (settlement.approved, settlement.processed, settlement.rejected, settlement.paused) share the same payload shape.

Single settlement payload fields

string
required
Kotani internal settlement ID.
string
required
Settlement reference ID.
string
required
New settlement status (APPROVED, PROCESSED, REJECTED, PAUSED).
number
required
Gross settlement amount.
number
required
Fee charged on this settlement.
number
required
Fee as a percentage of the gross amount.
number
required
Amount disbursed after fee (amount - fee).
string
required
Settlement currency (e.g. KES, GHS).
number
Approximate USD value of the gross amount at time of settlement.
number
Approximate USD value of the fee.
number
Approximate USD value of the net disbursement.
string
Which wallet balance was settled (e.g. DEPOSIT, WITHDRAWAL).
string
Batch ID if this settlement is part of a batch.
string
Sub-reference within a batch, if applicable.
object
Destination bank/wallet details for the disbursement.
string
required
ISO 8601 event timestamp.

Example — settlement.processed

Batch settlement payload fields

Batch events (settlement.batch.approved, settlement.batch.processed, settlement.batch.rejected, settlement.batch.cancelled) carry the same fields plus a settlements array.
string
required
Kotani internal batch ID.
string
required
Human-readable batch reference.
string
required
New batch status.
number
Total approximate USD value of all settlements in the batch.
array
required
Array of settlement summaries in the batch. Each entry contains _id, subReference, status, currency, netAmount, tentativeUsdNetAmount, referenceId, and channels.

Configuring Webhooks

  1. Log in to the dashboard → Settings
  2. Enter a publicly reachable HTTPS endpoint URL
  3. Select the events you want to subscribe to
  4. Copy the generated signing secret and store it securely
  5. Save
You can rotate the signing secret at any time. Update your verification logic with the new secret before applying it in production to avoid a verification gap.

Retries

If your endpoint returns a non-2xx response or times out, Kotani Pay retries delivery with exponential backoff — up to every 2 hours for a maximum of 24 hours. Return 200 OK as quickly as possible and handle processing asynchronously.