A refund returns money from a successful payment to the customer. You can refund the full amount in one go, or refund part of it and make more partial refunds later until the whole payment has been returned.

Refunds are often not final when the API responds. The payment provider can take seconds or days to confirm. Treat the status in the response as the current state, and rely on webhooks to learn the final outcome.

Refund status

StatusMeaning
pendingVNSwitch has sent the refund to the provider and is waiting for the result.
succeededThe provider has accepted the refund. The money is on its way to the customer.
failedThe refund did not go through. See error_code and error_message.
reviewThe refund needs a manual check before it can finish.

VNSwitch sends these webhook events for refunds:

Event (event_type)When
refund_succeededThe refund reached succeeded.
refund_failedThe refund reached failed.
refund_reviewThe refund was moved to review.

No webhook is sent while a refund is pending.

Update your order records from the webhook, not only from the create response. Verify the signature and handle each event once, as described in Webhooks.

Create a refund

Creates a refund against a payment with status succeeded or partially_captured. The example refunds 50,000 VND of a 150,000 VND payment. Send more requests later to refund more; the total of all refunds cannot exceed the amount captured.

POST /refunds

Authentication: secret API key (api-key header).

Body parameters

ParameterTypeRequiredDescription
payment_idstringYesThe payment to refund.
amountintegerNoAmount to refund, in the minor unit of the payment currency (for VND, whole dong: 50000 is 50,000 VND). Leave it out to refund the full captured amount. If you have already made a partial refund, send the amount you want instead, because the total of all refunds cannot exceed the amount captured.
refund_typestringNoinstant (default) or scheduled.
reasonstringNoWhy you are refunding, up to 255 characters. Shown in the dashboard and useful for support staff.
metadataobjectNoYour own key-value data, for example your order ID. Up to 50 keys; key names up to 40 characters, values up to 500 characters.
refund_idstringNoYour own ID for this refund. Sending the same refund_id again does not create a second refund, so you can retry safely. If you leave it out, VNSwitch creates an ID.

Example request

Response

{
  "refund_id": "ref_3b7e5a1c4d",
  "payment_id": "pay_8f2c1d9e0a",
  "amount": 50000,
  "currency": "VND",
  "status": "pending",
  "reason": "Customer returned one item",
  "metadata": { "order_id": "ORD-1001" },
  "error_code": null,
  "error_message": null,
  "connector": "<PROVIDER>",
  "created_at": "2026-11-01T09:30:00.000Z",
  "updated_at": "2026-11-01T09:30:00.000Z"
}

To refund the whole payment, send only payment_id (and reason if you want). VNSwitch then refunds the full captured amount.

Response

A refund object. Key fields:

FieldDescription
refund_idThe refund ID. Store it to look the refund up later.
payment_idThe payment that was refunded.
amount, currencyThe refunded amount and its currency.
statuspending, succeeded, failed or review. See Refund status.
error_code, error_messageSet when the refund failed. unified_code and unified_message give a provider-independent version when available.
connectorThe payment provider that handles the refund.
created_at, updated_atISO 8601 timestamps.

Retrieve a refund

Returns the current state of a refund. Use it to check a refund that is still pending, for example if you missed a webhook.

GET /refunds/{refund_id}

Authentication: secret API key (api-key header).

Path parameters

ParameterTypeRequiredDescription
refund_idstringYesThe ID returned when you created the refund.

Query parameters

ParameterTypeRequiredDescription
force_syncbooleanNotrue asks the payment provider for the latest status before responding. Default false, which returns the status VNSwitch already has.

Use force_sync=true sparingly, for example once when a refund has stayed pending longer than expected. For normal updates, wait for the webhook.

Example request

Response

{
  "refund_id": "ref_3b7e5a1c4d",
  "payment_id": "pay_8f2c1d9e0a",
  "amount": 50000,
  "currency": "VND",
  "status": "succeeded",
  "reason": "Customer returned one item",
  "metadata": { "order_id": "ORD-1001" },
  "error_code": null,
  "error_message": null,
  "connector": "<PROVIDER>",
  "created_at": "2026-11-01T09:30:00.000Z",
  "updated_at": "2026-11-01T09:31:12.000Z"
}

Response

The refund object, with the same fields as Create a refund.

Update a refund

Changes the reason or metadata of a refund. It does not change the amount or resend the refund to the provider.

POST /refunds/{refund_id}

Authentication: secret API key (api-key header).

Path parameters

ParameterTypeRequiredDescription
refund_idstringYesThe refund to update.

Body parameters

ParameterTypeRequiredDescription
reasonstringNoNew reason, up to 255 characters.
metadataobjectNoNew metadata. Same limits as when you create a refund.

Example request

Response

{
  "refund_id": "ref_3b7e5a1c4d",
  "payment_id": "pay_8f2c1d9e0a",
  "amount": 50000,
  "currency": "VND",
  "status": "succeeded",
  "reason": "Item damaged in delivery",
  "metadata": { "order_id": "ORD-1001", "ticket": "CS-2231" },
  "error_code": null,
  "error_message": null,
  "connector": "<PROVIDER>",
  "created_at": "2026-11-01T09:30:00.000Z",
  "updated_at": "2026-11-01T10:05:40.000Z"
}

Response

The updated refund object.

List refunds

Returns refunds that match the filters in the request body. This is a POST because the filters are sent as JSON. All filters are optional; send {} to list your most recent refunds.

POST /refunds/list

Authentication: secret API key (api-key header).

Body parameters

ParameterTypeRequiredDescription
payment_idstringNoOnly refunds of this payment.
refund_idstringNoOnly this refund.
start_timestringNoISO 8601 date-time. Only refunds created at or after this time.
end_timestringNoISO 8601 date-time. Only refunds created up to this time. Defaults to now. Use it together with start_time.
amount_filterobjectNo{ "start_amount": 10000, "end_amount": 100000 }. Either bound can be left out. Amounts are in the minor unit.
currencyarray of stringsNoCurrency codes, for example ["VND"].
refund_statusarray of stringsNoStatus filter. See the note below.
limitintegerNoNumber of refunds to return, 1 to 100. Default 10.
offsetintegerNoNumber of refunds to skip, for paging. Default 0.

The refund_status filter uses VNSwitch’s stored status names, which differ from the status values in responses: pending, success (shown as succeeded), failure and transaction_failure (shown as failed), and manual_review (shown as review).

Example request

Response

{
  "count": 1,
  "total_count": 1,
  "data": [
    {
      "refund_id": "ref_3b7e5a1c4d",
      "payment_id": "pay_8f2c1d9e0a",
      "amount": 50000,
      "currency": "VND",
      "status": "succeeded",
      "reason": "Item damaged in delivery",
      "metadata": { "order_id": "ORD-1001", "ticket": "CS-2231" },
      "connector": "<PROVIDER>",
      "created_at": "2026-11-01T09:30:00.000Z",
      "updated_at": "2026-11-01T10:05:40.000Z"
    }
  ]
}

Response

FieldDescription
countNumber of refunds in this response.
total_countNumber of refunds that match the filters. Use it with limit and offset to page through results.
dataArray of refund objects.