Refund an invoice

Give a client back money it paid on an invoice through Wingspan, in full or in part, follow the refund until it settles, and choose between a refund, a credit, and a void.

A refund sends money your client paid through Wingspan back to the card or bank account it came from. You can refund all of it or part of it, and you can refund the same invoice more than once until nothing refundable is left. For a payable you were paid, see Refunds and returns.

When you can refund

You can refund an invoice once your client has paid it in full through Wingspan and Wingspan has confirmed the deposit. Check events.depositedAt on the invoice, or wait for the Invoice.DepositConfirmed webhook.

How the client paidWhere the refund goes
Card, saved or through a payment linkBack to the same card
Bank debit, through a mandate or a payment linkBack to the same bank account
Check, cash, or a wire you recorded with pay-off-platformNot through Wingspan. Pay it back yourself, then record a credit with disposition: OffPlatform

To refund what was paid on a partly paid invoice, void the invoice first. Once it's voided, nothing more is due and you can refund the completed payments. Wait until no payment on it is Pending or Processing, because Wingspan refuses a void while one is.

Refund an invoice

Call Refund an invoice. Send amount for a partial refund, or leave it out to refund everything still refundable. reason is optional and only you see it.

curl -X POST "https://api.wingspan.app/v3/payments/invoices/Iv7Hn2Qx5Rt8Kw3Lp6Zb9D/refund" \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: Iv7Hn2Qx5Rt8Kw3Lp6Zb9D-refund-1" \
  -d '{
    "amount": 150.00,
    "reason": "Billed one extra shift"
  }'
// 200 OK (trimmed)
{
  "id": "Iv7Hn2Qx5Rt8Kw3Lp6Zb9D",
  "status": "Paid",
  "amount": 1200.00,
  "refunds": [
    {
      "id": "Rf4Kq9Wn2Lx7Tz5Bm8Hc3V",
      "amount": 150.00,
      "currency": "USD",
      "status": "Processing",
      "destination": { "type": "PaymentCard" },
      "reason": "Billed one extra shift",
      "events": { "initiatedAt": "2026-10-20T15:02:11Z" }
    }
  ]
}

The response is the invoice with the new refund in refunds[]. A 200 means Wingspan accepted the refund. It doesn't mean the money has reached your client yet.

Send an Idempotency-Key with every refund. If you lose the response, resend the same body with the same key and you get the same result back, with no second refund. A different body with the same key returns 409 IdempotencyKeyConflict.

Permissions and step-up

A refund moves money, so the call needs two things:

  • The payments.invoice-refund:write scope. Without it, the call returns 403 with code: Forbidden.
  • If you call with a person's login session, a recent MFA step-up. Without it, the call returns 403 with code: StepUpMfaRequired. Create a challenge with POST /v3/platform/mfa-challenges and { "requiredFor": "HighRiskWriteAction" }, verify it at POST /v3/platform/mfa-challenges/{challengeId}/verify with the code from your authenticator, then retry the refund. Step-up proof is short-lived, so verify right before you refund. A backend calling with an API key has no session to step up. See Environments and authentication.

How much you can refund

The most you can refund is what Wingspan collected on the invoice, minus refunds you've already made. A refund counts against that limit as soon as Wingspan accepts it. If a refund fails, its amount becomes refundable again. A request above the limit returns 422.

Wingspan funds the refund from your Wingspan balance or from the bank account your payouts go to. If neither can cover it, the call returns 422.

If you used a credit on the invoice, refunding all of the cash collected gives the credit back once, so it can apply to your client's other invoices. A partial refund doesn't give credit back. See Credits.

Follow the refund

Read the invoice with GET /v3/payments/invoices/{invoiceId} to follow each refund. Every refund is one entry in refunds[]:

FieldWhat it holds
idThe refund's ID
amount, currencyHow much is going back
statusPending, Processing, Completed, Failed, or Cancelled
destination.typeWhere the money goes, such as PaymentCard or ExternalBankAccount. The card or account itself isn't shown.
eventsinitiatedAt, completedAt, failedAt, cancelledAt, and returnedAt
reasonYour reason. Your client doesn't see it.
fundsMovementIdThe money movement that carries the refund, once there is one

When a refund is Completed, it also counts in the invoice's amountDetails.refunds, a negative total of settled refunds. amountDetails is left out while any refund or payment on the invoice is still settling.

The invoice keeps its status after a refund. A fully refunded invoice still reads Paid, so look at refunds[] and amountDetails.refunds to see what went back. Your client sees the refund on its side of the same record, without your reason.

If a refund fails, its entry stays in refunds[] with status: Failed, and the amount is refundable again. Send a new refund with a new Idempotency-Key.

Refund an overpayment

When a client sends more than an invoice needed, Wingspan applies what's due and keeps the excess as a credit linked to the payment. Refund the excess through that credit with POST /v3/payments/credits/{creditId}/complete and "remainderDisposition": "Refund". It needs the same scope and step-up as an invoice refund. See Partial and off-platform payments.

Wingspan can't refund money paid outside Wingspan, because it never held that money. Close that credit instead.

Refund, credit, void, or return

You want toUse
Give back money a client paid through WingspanA refund
Lower what a client owes without moving moneyA credit with disposition: Balance, which applies to the client's next invoices
Fix the line items before the client paysUpdate the invoice while it's Created or Opened
Cancel an invoice you sentVoid the invoice. Delete only drafts.
Record money you paid back by checkA credit with disposition: OffPlatform

A return is different from a refund. A return is the bank sending a payment back, for example for insufficient funds. You don't start it. The payment entry moves to Returned with a statusReason, and Invoice.Returned fires. See Collect payment on an invoice.

Errors

StatusCodeMeaning
403StepUpMfaRequiredWith a login session, verify a HighRiskWriteAction challenge, then retry.
403ForbiddenYour credential doesn't have payments.invoice-refund:write.
404NotFoundThe invoice doesn't exist, or you didn't issue it.
409ResourceConflictAnother refund on this invoice is still in progress with different details, or the invoice isn't paid and deposited yet. Wait for the refund or the deposit, then retry.
409IdempotencyKeyConflictYou reused an Idempotency-Key with a different body.
422ValidationErrorThe amount is zero or above what's refundable, the invoice isn't paid and deposited yet, it was paid off-platform, or your account can't fund the refund.

Related pages


Did this page help you?