Skip to main content
Onramp events go to the same webhook URL you set under Dashboard → Webhooks, signed with the same secret and X-Minisend-Signature header as checkout and off-ramp events. You check the signature the same way, and if a message doesn’t get through, we try again up to 8 times over about 33 hours. A successful order sends both onramp.completed and onramp.released. For bank transfer (NGN), onramp.completed always comes first, with onramp.released straight after. For M-Pesa (KES), they arrive moments apart and can come in either order, so don’t rely on the order. Use onramp.completed to know your customer paid, and onramp.released to get the USDC transaction ID. Every message has payment_method, either "mpesa" or "bank_transfer", so you can tell them apart without looking at currency. For bank transfer (NGN) orders, customer_phone and mobile_network are always null.
Bank transfer orders pass through a deposit_received status before completed, but we don’t send a webhook for it. If you want to show “payment received” before the order finishes, check GET /api/onramp/orders/{id}.

onramp.completed

string
required
Always onramp.completed.
string
required
The order this event is about. Use it together with event to spot repeats, since onramp.released has the same order_id.
string
Your reference, if you set one when creating the order.
string
required
mpesa or bank_transfer.
number
required
The USDC sent, or being sent, to release_address.
number
required
The local amount the customer was charged, including the fee.
number
required
Our fee in local currency. It’s already part of amount_local.
number
required
How much local currency 1 USDC was worth for this order.
string
The payer’s phone number for M-Pesa orders. null for bank transfer orders.
string
Safaricom for M-Pesa orders. null for bank transfer orders.
string
The M-Pesa confirmation code for the collection. Not present for bank transfer orders.
string
required
ISO 8601.
string
required
ISO 8601.

onramp.released

The same fields as onramp.completed, sent once the USDC transfer to your address is confirmed. It adds release_tx_hash.
string
required
The ID of the Base transaction that sent the USDC to release_address.

onramp.failed

string
Why the order failed. For M-Pesa, it’s the reason in words, after a stk_failed: or stk_initiation_failed: label (STK is M-Pesa’s name for the payment prompt). For example: stk_failed: The balance is insufficient for the transaction. You can show the text to your team or your customer, but don’t write code that depends on the exact wording. For bank transfer, it’s one of:
For M-Pesa, a failed order means nothing was taken, so there’s nothing to refund. For bank transfer, refunded_to_payer means the naira had arrived and we sent it back. The payer’s money isn’t lost, it just wasn’t converted. Either way, create a new order if your customer wants to try again.

onramp.cancelled

The customer dismissed the M-Pesa prompt on their phone. M-Pesa only.
Nothing was taken and no USDC is sent. If your customer still wants to pay, create a new order and a new prompt goes to their phone.
If your code only handles onramp.failed, add onramp.cancelled too. A cancelled order sends this event instead, so without it the order will look like it never finished.

onramp.expired

Your customer didn’t pay in time.

Handling pattern

Same as checkout and off-ramp webhooks: reply 200 straight away, do your processing afterwards, and check for repeats using the order and the event together.
This matters most for the onramp. onramp.completed and onramp.released have the same order_id and the same status: "completed", so a check on the order alone would treat the second one as a repeat and skip it. You’d never get release_tx_hash. Use order_id and event together and you’ll get both.