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:

EventWhen it is sent
buy_transaction.refund.details_requestedRamp needs refund account details for a buy transaction.
buy_transaction.refund.completedRamp 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

When 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:

StatusMeaning
pending_refund_detailsRamp is waiting for refund account details.
refund_details_receivedRefund account details have been submitted and are being processed.
completedRamp has completed the refund.

5. Receive completed

When 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".


Did this page help you?