> ## Documentation Index
> Fetch the complete documentation index at: https://docs.waffo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Refund Ticket

> Request a refund for a previously succeeded payment

`POST /api/actions/refund-ticket/create-ticket`

**Authentication:** Merchant API Key (server-to-server).

## Request body

| Field                            | Type   | Required | Description                                                                            |
| -------------------------------- | ------ | -------- | -------------------------------------------------------------------------------------- |
| `paymentId`                      | string | Yes      | Short ID of the payment to refund, e.g. `PAY_6eYCunG3IMmIgcQOnaXdoA`                   |
| `reason`                         | string | Yes      | Free-text reason shown to the customer / dashboard                                     |
| `requestedAmount.amount`         | string | Yes      | Refund amount in display format (e.g. `"10.50"`), must be `> 0` and `≤ payment.amount` |
| `requestedAmount.currency`       | string | Yes      | ISO 4217 code, must match the payment currency                                         |
| `refundTicketMerchantExternalId` | string | No       | Your business-side refund-ticket reference (max 128 chars).                            |

## Success response (200)

```json theme={"system"}
{
  "data": {
    "ticket": {
      "id": "TKT_3bVzrkD0FJjFdZNLk8Ualx",
      "status": "processing",
      "subjectId": "PAY_6eYCunG3IMmIgcQOnaXdoA",
      "refundTicketMerchantExternalId": "REF-2026-00891"
    }
  }
}
```

When the merchant submits via API Key, the ticket is auto-approved and goes straight to `processing`.

## Errors

> **Retry policy:** Never retry 4xx — fix the request and resubmit. Retry 5xx with exponential backoff (start 5s, max 3 attempts).

| Status | `errors[0].message`                                             | What it means                                                                                        | Recommended handling                                                     |
| ------ | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| 400    | `Missing required field: <name>`                                | Required field missing (`paymentId`, `reason`, `requestedAmount.amount`, `requestedAmount.currency`) | Fix the body, resubmit                                                   |
| 400    | `Expected format: PAY_xxx, got "..."`                           | `paymentId` Short ID could not be decoded                                                            | Fix `paymentId` format, resubmit                                         |
| 400    | `Invalid requestedAmount.amount format`                         | Amount string could not be parsed                                                                    | Use a display string like `"10.50"` (not minor units)                    |
| 400    | `refundTicketMerchantExternalId must be at most 128 characters` | Value exceeds 128 chars                                                                              | Shorten to 128 chars or less                                             |
| 401    | `Unauthorized`                                                  | Invalid API Key signature                                                                            | Verify auth headers                                                      |
| 404    | `Payment not found`                                             | Payment doesn't exist or doesn't belong to your store                                                | Verify `paymentId`                                                       |
| 409    | `Payment status is X, must be succeeded`                        | Original payment is not in `succeeded` state                                                         | Do not retry — the payment never succeeded                               |
| 409    | `Payment is not yet fully processed by PSP, please retry later` | PSP confirmation hasn't been written back yet                                                        | Retry after 5–30 seconds (transient)                                     |
| 409    | `Refund period expired (X days, max 14)`                        | Past the 14-day window                                                                               | Do not retry — refunds are no longer allowed                             |
| 409    | `Requested amount must be greater than 0`                       | Amount is ≤ 0                                                                                        | Fix the amount, resubmit                                                 |
| 409    | `Requested amount exceeds payment amount`                       | Amount > `payment.amount`                                                                            | Fix the amount, resubmit                                                 |
| 409    | `Currency mismatch: requested X, payment Y`                     | Currency must match the original payment                                                             | Use the payment's currency                                               |
| 409    | `A refund record already exists for this payment`               | A previous refund already succeeded against this payment                                             | Do not retry — query existing refunds via GraphQL                        |
| 409    | `A refund ticket already exists for this payment`               | Another in-flight ticket is blocking                                                                 | Wait for the existing ticket to terminate, then resubmit if still needed |
| 500    | `Internal server error`                                         | Unexpected server-side failure                                                                       | Retry with exponential backoff (start 5s, max 3 attempts)                |

<Tip>
  Use [GraphQL](/api-reference/endpoints/graphql/orders-and-payments) to query the created ticket and the executed refund record. Filter executed refunds by the ticket reference using `refunds(filter: { refundTicketMerchantExternalId: ... })`.
</Tip>
