Create a refund
Create a refund
Full and partial refunds use the same endpoint, whatever the payment method and whether or not the Payment Order has settled: Refund Payment.
POST /payments/api/v1/payment_orders/{payment_order_id}/payments/{payment_id}/refund
Most refunds are carried out asynchronously by the payment provider. Ping tells you how it went through webhooks.
Request
| Parameter | Required | Description |
|---|---|---|
items | Yes | The items to refund, each with its id and the amount to refund of it, in minor currency units. The refund amount is their sum. |
currency | Yes | ISO 4217 code. Must be the Payment's currency. |
reason | Yes | requested_by_customer, fraudulent or other. |
description | No | A short note on why the refund is made, for your own records. |
message | No | A message shown to the Payer, where the provider supports it. |
reserve_liquidity_account_id | No | A Liquidity Account that covers the refund if the item recipient of a settled order cannot. See Refund reserves. |
You name what is being refunded, not what should be left. An item may appear once per refund.
The id of an item is the one on the Payment. You can choose it yourself when creating the Payment by giving each order_items entry an id (a unique UUID4). Ping assigns one to any item without.
Note:
itemsreplaces theamountandorder_itemsparameters of API versions before2026-08-24. The older request body still works on those versions but cannot refund a settled Payment Order. See Migration to 2026-08-24.
Response
| Field | Description |
|---|---|
id | The refund's id. Webhook events refer to it. |
amount | The refund amount, the sum of items. |
currency | The refund currency. |
provider | The provider carrying out the refund, the same as the Payment's. |
status | The refund's status right now. |
Example: full refund
The Payment has a single item of 299.00 SEK. Naming every item for its full amount refunds the whole Payment.
curl --location 'https://sandbox.pingpayments.com/payments/api/v1/payment_orders/<PAYMENT-ORDER-ID>/payments/<PAYMENT-ID>/refund' \
--header 'tenant_id: <TENANT-ID>' \
--header 'x-api-secret: <API-SECRET>' \
--header 'x-api-version: 2026-08-24' \
--header 'Content-Type: application/json' \
--data '{
"currency": "SEK",
"reason": "requested_by_customer",
"description": "Order cancelled",
"items": [
{ "id": "00328a14-fd85-4c08-a02b-7ecbeed110d1", "amount": 29900 }
]
}'{
"id": "e2fbcec8-4858-40f0-ba7f-7fcf82653f58",
"status": "PENDING",
"currency": "SEK",
"provider": "swish",
"amount": 29900
}Example: partial refund
Same Payment, but only 100.00 SEK is refunded. Name only the part of the item you are refunding. For a Payment with several items, name the ones being refunded and leave the others out.
--data '{
"currency": "SEK",
"reason": "requested_by_customer",
"description": "One item returned",
"items": [
{ "id": "00328a14-fd85-4c08-a02b-7ecbeed110d1", "amount": 10000 }
]
}'What happens next depends on the Payment Order:
- Not yet settled: the Payer gets 100.00 SEK back, and only the remaining 199.00 SEK is split and settled later. See Refunds before settlement.
- Already settled: the 100.00 SEK is taken back from the item recipient. See Refunds after settlement.
Following a refund with webhooks
A refund's status changes as it goes. To follow it, set up a webhook endpoint in the developer portal and subscribe it to refund.status.*. See Webhooks for setting up endpoints and verifying signatures.
| Status | Final? | Meaning |
|---|---|---|
INITIATED | No | Ping has registered the refund. |
PENDING | No | The provider is carrying out the refund. |
COMPLETED | Yes | The Payer has been refunded. |
DECLINED | Yes | The refund was refused, by Ping's checks or by the provider. details says why. |
FAILED | Yes | The provider could not carry out the refund. details says why. |
Each status is sent as its own event type, for example refund.status.completed for COMPLETED.
No more events are sent for a refund once it reaches a final status.
{
"id": "f0c1d6f8-3a1b-4e2c-9b7a-2d9f5e8c1a44",
"type": "refund.status.completed",
"version": "1",
"occurred_at": "2026-05-28T10:42:31.123456Z",
"data": {
"status": {
"status": "COMPLETED",
"details": {},
"occurred_at": "2026-05-28T10:42:31Z",
"refund_id": "e2fbcec8-4858-40f0-ba7f-7fcf82653f58"
},
"refund": {
"id": "e2fbcec8-4858-40f0-ba7f-7fcf82653f58",
"amount": 10000,
"currency": "SEK",
"provider": "swish",
"payment_id": "1a64fa5c-1f1f-4f2c-a8a5-b6ad0f33d8e9"
}
}
}On a settled order, a DECLINED or FAILED refund also gives the money taken back for it to the item recipient, and to your reserve if one was used. Nobody loses money on a refund that did not happen.
Note: Tenants set up before webhooks can still receive refund status through the callback URL on the Tenant (see Update Tenant). It keeps working, but new
integrations should use webhooks.
Updated about 11 hours ago