Refund webhooks and refund details flow
This guide explains how to listen for refund webhooks and how to respond when Ramp asks you to provide refund account details for a buy transaction.
Refund webhooks are sent to your app's configured webhook_url. Public refund API requests use your private API key in the X-Private-Key header.
Configure your webhook URL
Set your webhook URL from the merchant dashboard. The URL must be reachable over HTTPS and must return a 2xx response when Ramp delivers a webhook.
Ramp delivers all app webhooks to this URL, including refund events.
Refund webhook events
Ramp sends two refund webhook event types:
| Event | When it is sent |
|---|---|
buy_transaction.refund.details_requested | Ramp needs refund account details for a buy transaction. |
buy_transaction.refund.completed | Ramp has marked the refund as completed. |
Webhook request format
Ramp sends a POST request to your webhook URL.
Headers:
Content-Type: application/json
User-Agent: Quidax-Ramp
X-Ramp-Signature: <hmac_sha256_signature>
Body:
{
"event": "buy_transaction.refund.details_requested",
"data": {
"refund_reference": "RFN9K4D2M8Q1",
"merchant_reference": "order_12345",
"amount": "1000.0",
"currency": "NGN",
"completed_at": null
}
}
For a completed refund, completed_at is an ISO 8601 timestamp:
{
"event": "buy_transaction.refund.completed",
"data": {
"refund_reference": "RFN9K4D2M8Q1",
"merchant_reference": "order_12345",
"amount": "1000.0",
"currency": "NGN",
"completed_at": "2026-07-17T10:30:00Z"
}
}
Verify webhook signatures
Ramp signs the exact JSON webhook body with HMAC-SHA256 using your app's latest active secret key. Compare the computed digest with X-Ramp-Signature.
Node.js example:
import crypto from "crypto";
function verifyRampSignature(rawBody, signature, secretKey) {
const expectedSignature = crypto
.createHmac("sha256", secretKey)
.update(rawBody)
.digest("hex");
if (expectedSignature.length !== signature.length) {
return false;
}
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(signature)
);
}
Use the raw request body for verification. Do not parse and re-serialize the JSON before verifying it.
Respond to webhooks
Return a 2xx response after you verify and store the event. Process business logic asynchronously where possible.
Ramp retries failed deliveries. A delivery is treated as failed when your endpoint times out, cannot be reached, or returns a non-2xx response. The default delivery timeout is 10 seconds.
Your webhook handler should be idempotent. Use the combination of event, refund_reference, and merchant_reference to avoid processing the same event twice.
Refund details flow
1. Receive details_requested
details_requestedWhen you receive buy_transaction.refund.details_requested, store the refund details and notify the customer or your operations team to provide the destination account.
Use merchant_reference to match the refund to the original buy transaction in your system. Use refund_reference as Ramp's refund identifier.
2. Optionally verify the bank account
Before submitting refund details, its advisable that you verify the account details so your customer doesnt supply the wrong details because a refund is final once completed, you can ask Ramp to resolve the bank account.
curl -X POST "https://ramp-be.quidax.io/api/v1/merchants/refunds/order_12345/verify_account" \
-H "x-private-key: sk_xxx" \
-H "Content-Type: application/json" \
-d '{
"account_number": "0123456789",
"bank_code": "058",
"currency_code": "NGN"
}'
Successful response:
{
"status": "ok",
"message": "Bank account successfully verified",
"data": {
"account_name": "Ada Lovelace",
"account_number": "0123456789",
"bank_name": "GTBank",
"bank_code": "058"
}
}
You can fetch available banks using this endpoint.
curl --request GET \
--url 'https://ramp-be.quidax.io/api/v1/merchants/custodial/banks?country=NG' \
--header 'accept: application/json' \
--header 'x-private-key: sk_xxx'
3. Submit refund account details
Submit details using the original transaction merchant_reference.
curl -X POST "https://ramp-be.quidax.io/api/v1/merchants/refunds/order_12345/details" \
-H "x-private-key: sk_xxx" \
-H "Content-Type: application/json" \
-d '{
"account_number": "0123456789",
"bank_code": "058",
"account_name": "Ada Lovelace"
}'
Successful response:
{
"status": "ok",
"message": "Refund details submitted successfully",
"data": {
"public_id": "mref_abc123",
"refund_reference": "RFN9K4D2M8Q1",
"status": "refund_details_received",
"amount": "1000.0",
"currency": "NGN",
"merchant_reference": "order_12345",
"created_at": "2026-07-17T10:00:00Z",
"updated_at": "2026-07-17T10:05:00Z"
}
}
Submitting the exact same details more than once in the same refund cycle is safe. Ramp stores one submission and returns the refund.
4. Track refund status
You can fetch the refund status at any time.
curl "https://ramp-be.quidax.io/api/v1/merchants/refunds/order_12345" \
-H "x-private-key: sk_xxx"
Response:
{
"status": "ok",
"message": "Refund found",
"data": {
"public_id": "mref_abc123",
"refund_reference": "RFN9K4D2M8Q1",
"status": "pending_refund_details",
"amount": "1000.0",
"currency": "NGN",
"merchant_reference": "order_12345",
"created_at": "2026-07-17T10:00:00Z",
"updated_at": "2026-07-17T10:00:00Z"
}
}
Refund statuses:
| Status | Meaning |
|---|---|
pending_refund_details | Ramp is waiting for refund account details. |
refund_details_received | Refund account details have been submitted and are being processed. |
completed | Ramp has completed the refund. |
5. Receive completed
completedWhen Ramp completes the refund, your webhook URL receives buy_transaction.refund.completed.
Update the order or transaction in your system as refunded. If you miss the webhook or need to reconcile, call GET /api/v1/merchants/refunds/:reference and check for status: "completed".
Updated about 5 hours ago

