How Refunds Work
- On-chain settlement confirmed — Kotani receives the crypto from the sender.
- Fiat disbursement fails — Mobile money, bank, or paybill transfer fails or is cancelled.
- Refund triggered automatically — Kotani attempts to return the crypto to the integrator.
- Webhook fired —
refund.completedorrefund.failedis sent to your webhook endpoint.
senderAddress. No action is needed from you.
For Lightning, Kotani cannot push funds unprompted — it needs a bolt11 invoice to pay. See below.
Lightning Refunds
Option A — Provide a refund invoice at create time (recommended)
Includerefund_config when creating the offramp. If fiat fails, Kotani pays the invoice immediately — no manual intervention needed.
| Field | Required | Notes |
|---|---|---|
bolt11 | Yes | Valid, unpaid Lightning invoice |
payment_hash | Recommended | Used to detect if invoice was already paid |
amount_msat | Yes | Must match the expected refund amount exactly |
expires_at | Yes | ISO 8601 — Kotani uses this to decide whether to call generate_invoice_url |
generate_invoice_url | Recommended | Called when bolt11 is expired; see contract below |
Important: Lightning invoices expire. Always provide generate_invoice_url so Kotani can fetch a fresh one if the refund is triggered after the invoice expires. Without it, and with an expired invoice, Kotani falls back to Option B.
Option B — Submit invoice manually when notified
If norefund_config was provided (or bolt11 is expired and no generate_invoice_url exists), Kotani sends a refund.lightning.invoice_needed webhook and an email asking you to submit an invoice via:
generate_invoice_url Contract
Kotani calls your URL with a POST request when it needs a fresh Lightning invoice:
Request from Kotani
Your expected response
| Field | Required | Notes |
|---|---|---|
bolt11 | Yes | Fresh, unpaid bolt11 invoice |
payment_hash | Recommended | For Kotani to track payment |
amount_msat | Yes | Must match the requested amount_msat |
expires_at | Recommended | ISO 8601 — so Kotani knows when this one expires |
Your endpoint must respond within 10 seconds. If it times out or returns an error, Kotani falls back to the manual notification flow.
Refund Webhooks
refund.completed
Fired when Kotani successfully refunds crypto to the integrator.
status is set to REVERSED once refunded.
refund.failed
Fired when refund attempts are exhausted (after up to 5 retries for general errors, 10 for gas/fund errors).
referenceId if you receive this — manual intervention will be required.
refund.lightning.invoice_needed
Fired when Kotani needs you to submit a Lightning invoice manually (Option B). Includes the exact amount and the submit URL.
Retry Logic
| Scenario | Max Retries | Behaviour |
|---|---|---|
| Gas / insufficient funds | 10 | Auto-retried on next cron cycle |
| Timeout | 5 | Auto-retried on next cron cycle |
| All other errors | 5 | Auto-retried; refund.failed fired when exhausted |
| Lightning — no invoice | ∞ | Waits until invoice submitted; refund.lightning.invoice_needed sent once |
POST /api/v3/offramp/retry-refund/{referenceId} to manually trigger a retry on a FAILED refund.