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

ParameterRequiredDescription
itemsYesThe items to refund, each with its id and the amount to refund of it, in minor currency units. The refund amount is their sum.
currencyYesISO 4217 code. Must be the Payment's currency.
reasonYesrequested_by_customer, fraudulent or other.
descriptionNoA short note on why the refund is made, for your own records.
messageNoA message shown to the Payer, where the provider supports it.
reserve_liquidity_account_idNoA 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: items replaces the amount and order_items parameters of API versions before 2026-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

FieldDescription
idThe refund's id. Webhook events refer to it.
amountThe refund amount, the sum of items.
currencyThe refund currency.
providerThe provider carrying out the refund, the same as the Payment's.
statusThe 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:

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.

StatusFinal?Meaning
INITIATEDNoPing has registered the refund.
PENDINGNoThe provider is carrying out the refund.
COMPLETEDYesThe Payer has been refunded.
DECLINEDYesThe refund was refused, by Ping's checks or by the provider. details says why.
FAILEDYesThe 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.


Did this page help you?