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. EachPOST request includes:
| Header | Value |
|---|---|
X-Kotani-Signature | sha256=<hmac> — use this to verify authenticity |
X-Kotani-Event | The event name (e.g. transaction.deposit.status.updated) |
X-Kotani-Integrator | Your integrator ID |
Content-Type | application/json |
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 thecallbackUrl set on each transaction. These requests:
- Are sent as
POSTwith a JSON body - Do not include
X-Kotani-Signature,X-Kotani-Event, orX-Kotani-Integratorheaders - Contain the transaction fields directly in the body — no
eventorsignaturewrapper
Supported Events
| Event | When it fires |
|---|---|
transaction.deposit.status.updated | A deposit changes status |
transaction.withdrawal.status.updated | A withdrawal changes status |
transaction.onramp.status.updated | An onramp (fiat→crypto) changes status |
transaction.offramp.status.updated | An offramp (crypto→fiat) changes status |
kyc.status.changed | A customer’s KYC verification outcome changes |
refund.completed | Crypto refund successfully sent back to the sender |
refund.failed | Refund exhausted all retry attempts |
refund.lightning.invoice_needed | Lightning offramp needs a bolt11 invoice to process the refund |
settlement.approved | A settlement request was approved |
settlement.processed | A settlement was completed and funds disbursed |
settlement.rejected | A settlement request was rejected |
settlement.paused | A settlement was paused pending review |
settlement.batch.approved | A settlement batch was approved |
settlement.batch.processed | A settlement batch completed (fully or partially) |
settlement.batch.rejected | A settlement batch was rejected |
settlement.batch.cancelled | A settlement batch was cancelled |
system.event | Operational notices and maintenance alerts |
transaction.status.updated | (Deprecated) Use the specific events above |
Settlement events are opt-in. Subscribe to them in Settings → Webhooks.
Verifying Signatures
Always verify theX-Kotani-Signature header before processing any event.
- Parse the JSON body.
- Remove the
signaturefield from the parsed object. - Compute
sha256=HMAC-SHA256(secret, JSON.stringify({event, data})). - Compare with
X-Kotani-Signatureusing 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
Current deposit status. See Transaction Statuses.
Your reference ID (or system-generated if not provided).
Auto-generated sequential reference number.
Kotani internal record ID.
Amount the customer was asked to pay.
ID of the integrator fiat wallet credited.
Amount actually credited to your wallet after fees.
Processing fee charged.
The customer identifier supplied at deposit creation.
Callback URL set on the transaction.
ISO 8601 creation timestamp.
Mobile money receipt code from the network (e.g.
OEI2AK4D9X). Present on successful mobile money deposits.Provider-level confirmation reference. May differ from
telco_id for some providers.Bank name for bank-based deposits (e.g.
Capitec, FNB). Only present for bank deposits.Bank code for bank-based deposits. Only present for bank deposits.
Card payment brand for card deposits (e.g.
VISA, MASTERCARD). Only present for card deposits.Human-readable failure reason. Always present (may be empty string) for non-successful statuses.
Detailed provider error description.
Provider error code.
Raw internal error from the processing pipeline.
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
Current withdrawal status.
Your reference ID.
Auto-generated sequential reference number.
Kotani internal record ID.
Amount requested for withdrawal.
ID of the integrator fiat wallet debited.
Amount debited from your wallet including fees.
Processing fee charged.
The customer identifier supplied at withdrawal creation.
Callback URL set on the transaction.
ISO 8601 creation timestamp.
Mobile money receipt code from the network. Present on successful mobile money payouts.
Provider-level confirmation reference.
Additional integrator fee charged on the transaction, if configured.
Human-readable failure reason. Always present (may be empty string) for non-successful statuses.
Raw error from the processing pipeline.
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
Your reference ID for the onramp transaction.
Combined overall status of the onramp.
Status of the fiat payment collection leg.
Status of the on-chain crypto delivery leg.
Blockchain the crypto was sent on (e.g.
POLYGON, STELLAR).Token delivered (e.g.
USDT, USDC).Expected crypto amount to deliver.
Actual crypto amount delivered on-chain (may differ from
cryptoAmount due to gas).Base fiat amount collected (before fee).
Platform fee on the fiat side.
Total fiat the customer paid (
fiatAmount + fiatFee).On-chain address the crypto was delivered to.
Blockchain transaction hash once on-chain delivery completes.
Rate used for the conversion (
from, to, cryptoAmount).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
Your reference ID for the offramp transaction.
Overall offramp status (
SUCCESSFUL, FAILED, PENDING, etc.).Status of the on-chain crypto receipt leg.
Full fiat amount before fees.
Amount actually disbursed to the recipient after fees.
Crypto amount received from the sender.
Fiat currency code (e.g.
KES, GHS).Customer identifier.
On-chain address that sent the crypto.
Kotani escrow address the crypto was sent to.
ID of the integrator fiat wallet used, if applicable.
On-chain transaction hash of the crypto receipt.
Exact on-chain amount confirmed (may differ from
cryptoAmount due to network fees).Rate used for the conversion (
from, to, fiatAmount).Whether the platform’s own integrated crypto wallet was used.
ISO 8601 creation timestamp.
ISO 8601 last-updated timestamp.
On-chain error details if the crypto receipt failed.
Fiat disbursement error details.
Example — successful offramp
Example — fiat disbursement failed
refund.completed
Fired when a crypto refund has been successfully sent back to the sender.
Payload fields
Reference ID of the original offramp transaction.
Always
REVERSED.Always
SUCCESSFUL.On-chain transaction hash of the refund.
Amount refunded (in token native units — sats for Lightning, token units for EVM/Solana).
Chain the refund was sent on.
Token refunded.
Fiat currency of the original transaction.
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
Reference ID of the original offramp transaction.
Always
FAILED.Amount that was attempted for refund.
Chain the refund was attempted on.
Token that was being refunded.
Fiat currency of the original transaction.
Error message from the last refund attempt.
Number of refund attempts made before giving up.
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
Reference ID of the original offramp transaction.
Status of the offramp (typically
FAILED).On-chain crypto receipt status (typically
SUCCESSFUL — crypto was received).Always
INVOICE_NEEDED when this event fires.Amount to be refunded in millisatoshis.
Amount to be refunded in satoshis.
Always
LIGHTNING.Fiat currency of the original transaction.
Always
true — you must submit an invoice.Instructions for submitting the invoice. Contains
type, description, submitUrl, method, body, and invoiceRequirements.Example
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
Kotani internal settlement ID.
Settlement reference ID.
New settlement status (
APPROVED, PROCESSED, REJECTED, PAUSED).Gross settlement amount.
Fee charged on this settlement.
Fee as a percentage of the gross amount.
Amount disbursed after fee (
amount - fee).Settlement currency (e.g.
KES, GHS).Approximate USD value of the gross amount at time of settlement.
Approximate USD value of the fee.
Approximate USD value of the net disbursement.
Which wallet balance was settled (e.g.
DEPOSIT, WITHDRAWAL).Batch ID if this settlement is part of a batch.
Destination bank/wallet details for the disbursement.
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.
Kotani internal batch ID.
Human-readable batch reference.
New batch status.
Total approximate USD value of all settlements in the batch.
Array of settlement summaries. Each entry contains
_id, subReference, status, currency, netAmount, tentativeUsdNetAmount, referenceId, and channels.Configuring Webhooks
- Log in to the dashboard → Settings
- Enter a publicly reachable HTTPS endpoint URL
- Select the events you want to subscribe to
- Copy the generated signing secret and store it securely
- Save
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.