RefundsInitiate Refunds

Initiate a Refund

Use this endpoint to request a full or partial refund for a successful USD card transaction. Refunds are processed manually and asynchronously.

An accepted request reserves funds and returns HTTP 202. It does not mean that the refund has been completed. Verify the final status or wait for a refund webhook before treating the refund as successful.

Read the Refund Overview first for supported transactions, fees, wallet funding, statuses, and the complete integration flow.

The refund fee is based on your merchant agreement. The USD 1.00 fee in the examples is illustrative; use the fee returned by the API as the applicable fee for each refund.

Endpoint

POST https://backendapi.sayswitchgroup.com/api/v1/refund

Headers

Content-Type: application/json
Authorization: Bearer YOUR_SECRET_KEY
Idempotency-Key: UNIQUE_REFUND_REQUEST_KEY

Use the test or live secret key that matches the original transaction. Idempotency-Key is strongly recommended for every refund request.

Request Body

{
  "transaction_reference": "SSW_TRANSACTION_REFERENCE",
  "amount": "25.00",
  "merchant_note": "Customer requested a partial refund"
}
FieldTypeRequiredDescription
transaction_referencestringConditionalOriginal successful transaction reference. Required unless reference is supplied.
referencestringConditionalAlternative name for transaction_reference.
amountnumeric string or numberNoAmount to return. When omitted, the remaining refundable transaction amount is used. Minimum 0.01; it cannot exceed the remaining refundable amount.
merchant_notestringNoMerchant description, up to 2,000 characters.
idempotency_keystringNoBody alternative to the Idempotency-Key header, up to 190 characters.

Do not include callbackUrl in a refund request. Refund status notifications are delivered to your configured webhook URL.

Full Refund

Omit amount to refund the transaction’s entire remaining refundable amount.

curl --request POST \
  --url https://backendapi.sayswitchgroup.com/api/v1/refund \
  --header "Authorization: Bearer YOUR_SECRET_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: refund-full-unique-key" \
  --data '{
    "transaction_reference": "SSW_TRANSACTION_REFERENCE",
    "merchant_note": "Customer requested a full refund"
  }'

Keep your secret key on your server. Do not run this request in frontend JavaScript.

Partial Refund

Include amount to refund part of the remaining refundable amount.

curl --request POST \
  --url https://backendapi.sayswitchgroup.com/api/v1/refund \
  --header "Authorization: Bearer YOUR_SECRET_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: refund-partial-unique-key" \
  --data '{
    "transaction_reference": "SSW_TRANSACTION_REFERENCE",
    "amount": "25.00",
    "merchant_note": "Customer requested a partial refund"
  }'

Accepted Responses

Available and Pending Funded

When pending funds are needed, the API returns HTTP 202:

{
  "success": true,
  "message": "Refund submitted and awaiting settlement funding.",
  "data": {
    "refund_reference": "REF_2026_001",
    "transaction_reference": "USD_TXN_2026_001",
    "amount": "100.00",
    "fee": "1.00",
    "total_reserved": "101.00",
    "funding": {
      "status": "awaiting_settlement",
      "available_reserved": "40.00",
      "pending_reserved": "61.00",
      "pending_funded_amount": "0.00",
      "pending_remaining": "61.00",
      "funded_at": null
    },
    "currency": "USD",
    "status": "awaiting_settlement",
    "external_reference": null,
    "requested_at": "2026-09-28T10:00:00+01:00",
    "completed_at": null
  }
}

An awaiting_settlement refund is accepted, but it is not ready for processing. Do not resubmit it. SaySwitch automatically moves it to pending when settlement has completely funded the reservation.

Fully Available Funded

When available balance covers the full amount and fee, the API returns HTTP 202 with a refund status of pending:

{
  "success": true,
  "message": "Refund submitted and awaiting processing.",
  "data": {
    "refund_reference": "REF_2026_002",
    "transaction_reference": "USD_TXN_2026_002",
    "amount": "100.00",
    "fee": "1.00",
    "total_reserved": "101.00",
    "funding": {
      "status": "funded",
      "available_reserved": "101.00",
      "pending_reserved": "0.00",
      "pending_funded_amount": "0.00",
      "pending_remaining": "0.00",
      "funded_at": "2026-09-28T10:00:00+01:00"
    },
    "currency": "USD",
    "status": "pending"
  }
}

See Funding Status for descriptions of every field in the funding object.

Idempotent Retry

Send the same Idempotency-Key when retrying a request after a timeout or uncertain response. The API returns the original refund and does not reserve the wallet twice.

curl --request POST \
  --url https://backendapi.sayswitchgroup.com/api/v1/refund \
  --header "Authorization: Bearer YOUR_SECRET_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: refund-partial-unique-key" \
  --data '{
    "transaction_reference": "SSW_TRANSACTION_REFERENCE",
    "amount": "25.00",
    "merchant_note": "Customer requested a partial refund"
  }'

An idempotent retry returns HTTP 200:

{
  "success": true,
  "message": "Refund request already received.",
  "data": {
    "refund_reference": "REFUND_REFERENCE",
    "transaction_reference": "SSW_TRANSACTION_REFERENCE",
    "amount": "25.00",
    "fee": "1.00",
    "total_reserved": "26.00",
    "funding": {
      "status": "funded",
      "available_reserved": "26.00",
      "pending_reserved": "0.00",
      "pending_funded_amount": "0.00",
      "pending_remaining": "0.00",
      "funded_at": "2026-09-28T10:00:00+01:00"
    },
    "currency": "USD",
    "status": "pending",
    "external_reference": null,
    "requested_at": "2026-09-27T12:00:00+01:00",
    "completed_at": null
  }
}

Track a Refund

Use GET /api/v1/refund to retrieve your refunds. Use GET /api/v1/refund/status/{refund_reference} to verify one refund, and listen for refund webhooks. Both endpoints return refunds for the authenticated test or live environment only.

Do not assume that HTTP 202 means success. The Refund Overview explains the complete status and settlement lifecycle.

Validation and Eligibility Errors

Transaction Not Found

HTTP 404:

{
  "success": false,
  "message": "Transaction not found."
}

Transaction Is Not Successful

HTTP 422:

{
  "success": false,
  "message": "Only successful transactions can be refunded."
}

Non-Card Transaction

HTTP 422:

{
  "success": false,
  "message": "Only card transactions can currently be refunded."
}

Unsupported Currency

HTTP 422:

{
  "success": false,
  "message": "Refunds are not currently supported for NGN transactions."
}

Amount Exceeds the Remaining Refundable Amount

HTTP 422:

{
  "success": false,
  "message": "Refund amount exceeds the remaining refundable amount."
}

Insufficient Eligible Funds

HTTP 422:

{
  "success": false,
  "message": "Insufficient funds to cover the refund amount and fee.",
  "required": "101.00",
  "available_balance": "20.00",
  "pending_balance": "50.00",
  "shortfall": "31.00",
  "currency": "USD"
}

pending_balance is the eligible pending balance after existing refund holds, not necessarily the gross pending balance. If available balance plus eligible pending balance cannot cover the refund and fee, the request is rejected and no funds are deducted or reserved.

Transaction Already Fully Refunded

HTTP 409:

{
  "success": false,
  "message": "This transaction has already been fully refunded."
}

Invalid Request Fields

HTTP 422:

{
  "success": false,
  "message": "Invalid refund request.",
  "errors": {
    "transaction_reference": [
      "The transaction reference field is required when reference is not present."
    ]
  }
}

Encrypted S2S Merchants

Merchants without full payload encryption send the normal JSON request shown above. If S2S payload encryption is enabled for your account, encrypt the complete inner refund payload using your existing SaySwitch encryption flow.

The decrypted inner payload can contain:

{
  "transaction_reference": "SSW_TRANSACTION_REFERENCE",
  "amount": "25.00",
  "merchant_note": "Customer requested a partial refund",
  "idempotency_key": "refund-encrypted-unique-key"
}

Send the encrypted value in the outer request body:

{
  "data": "ENCRYPTED_PAYLOAD"
}

Encrypted merchants receive an encrypted response and must decrypt it using their existing response-decryption process. The refund GET endpoints do not require an encrypted request body, but their responses remain encrypted when encryption is enabled.

Encrypted Request Examples

curl --request POST \
  --url https://backendapi.sayswitchgroup.com/api/v1/refund \
  --header "Authorization: Bearer YOUR_SECRET_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: refund-encrypted-unique-key" \
  --data '{
    "data": "ENCRYPTED_PAYLOAD"
  }'

The encryption helper names above represent your existing implementation. Follow API Payload Encryption for the configured encryption and response-decryption process.

Never include raw card numbers, CVVs, PINs, or other card information in a refund request.