Skip to main content
Refund endpoints allow merchants to issue refunds for previously succeeded payments. Refunds are modelled as refund tickets (the request) and refund records (the executed result, written after PSP confirms).

Business Rules

  • Refunds must be requested within 14 days of the original payment success
  • Refund amount can be partial (any value > 0 and ≤ payment.amount), but must use the same currency as the payment
  • The original payment must already be fully confirmed by the PSP — calling refund immediately after a payment webhook arrives may transiently fail; retry after a few seconds
  • A payment that already has a pending or succeeded refund cannot have another refund ticket created at the same time
  • When the merchant submits the refund (server-to-server via API Key), the ticket is auto-approved and goes straight to processing — no manual review step

Refund Ticket Status Values

StatusMeaning for merchant
pendingAwaiting Waffo review (only seen when buyer self-initiated)
approvedApproved, will be dispatched to PSP shortly
rejectedRejected — see rejectReason in GraphQL
processingDispatched to PSP, awaiting upstream completion
succeededFunds returned to the consumer
failedPSP returned failure — can be reviewed and resubmitted
Merchant-initiated refunds skip pending and approved entirely — they appear as processing immediately after creation.

You can attach an optional business-side identifier (max 128 characters) at two creation points; on the wire each one carries its own flat key so a single payload can hold both without ambiguity:
Created atRequest body fieldStored as
checkout/create-sessionorderMerchantExternalIdorders.merchant_external_id (inherited by first and renewal payments)
refund-ticket/create-ticketrefundTicketMerchantExternalIdrefund_tickets.merchant_external_id (inherited by the executed refund record)
End-to-end propagation (the same flat dual-key names are used everywhere — request bodies, webhook payloads, and GraphQL types):
checkout/create-session         (request:  orderMerchantExternalId)

                                    ├──► OnetimeOrder / SubscriptionOrder . orderMerchantExternalId
                                    ├──► Payment                          . orderMerchantExternalId   (first + subscription renewals)

refund-ticket/create-ticket     (request:  refundTicketMerchantExternalId)

                                    ├──► RefundTicket                     . refundTicketMerchantExternalId

                                    └──► Refund carries BOTH:
                                         • orderMerchantExternalId           (from the originating order)
                                         • refundTicketMerchantExternalId    (from the originating refund ticket)
These identifiers are not idempotency keys — Waffo does not enforce uniqueness; the same reference can be attached to multiple records and you are responsible for any uniqueness guarantees on your side. The same field names appear in three places — request body, webhook payload, GraphQL — so the value you write at checkout/refund-ticket creation can be read back without renaming. Use these identifiers to query Payments, Refunds, and Refund Tickets via GraphQL — see the GraphQL orders & payments guide.

Endpoints

Create Refund Ticket

Request a refund for a succeeded payment. Supports full and partial refunds.

Resubmit Refund Ticket

Revise and resubmit a rejected or failed refund ticket.
Use GraphQL to query refund tickets and the executed refund records. On Refund, the two business numbers are exposed as flat fields refund.orderMerchantExternalId and refund.refundTicketMerchantExternalId — same names as the webhook payload. Filter executed refunds by the refund ticket reference using refunds(filter: { refundTicketMerchantExternalId: ... }).