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 asonramp.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.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: reply200 straight away, do your processing afterwards, and check for repeats using the order and the event together.

