Refunds

A refund returns funds to the original processor payment or gift card, or to replacement gift cards when you choose that destination. On Flint you refund against the order: the whole order, an amount, specific line items or charges, or one payment on an order paid in several. Flint works out which payments to refund, how much tax comes back, and what the order's refunded totals become.

Creating and updating refunds needs the commerce.refunds.write scope. Reading them needs commerce.refunds.read. Test keys run the same refund lifecycle with no real money. Testing shows how to stage a paid order to refund.

Refund, return, cancel, or credit note#

Choose the operation that matches what is happening:

SituationUse
Money goes back and nothing comes back: a duplicate charge, a service problem, a goodwill credit, an order canceled before it shippedPOST /v1/refunds
Merchandise comes backA Return. It handles eligibility, receiving, restocking, and the refund.
The payment is still an uncaptured authorizationCancel it. Nothing was captured, so there is nothing to refund.
A paid invoice was wrongA credit note with a refund

A Return or credit note that pays the buyer back creates an ordinary refund on the same order, drawn from the same refundable balance. Refunds from a Return carry return_id and return_resolution_id. Refunds from a credit note carry credit_note_id and invoice_id. A refund you create directly has none of these fields.

POST /v1/refunds only moves money. It does not check return eligibility, apply your return policy, or put stock back.

Refund an order in full#

Send the order_id and leave out amount_money. Flint refunds everything that is still refundable on the order.

cURL
curl -X POST https://api.withflintpay.com/v1/refunds \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Flint-Version: 2026-09-07" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: refund-ord-1042-full" \
  -d '{
    "order_id": "ord_1kmn0aExample",
    "reason": "requested_by_customer"
  }'

The response is 201 with the refund:

Response
{
  "data": {
    "refund_id": "ref_1kmn0aExample",
    "order_id": "ord_1kmn0aExample",
    "payment_intent_id": "pi_1kmn0aExample",
    "status": "succeeded",
    "reason": "requested_by_customer",
    "amount_money": {"amount": 3098, "currency": "USD"},
    "refunded_tip_money": {"amount": 0, "currency": "USD"},
    "refund_method": "original_payment",
    "payment_refunds": [
      {
        "payment_intent_id": "pi_1kmn0aExample",
        "amount_money": {"amount": 3098, "currency": "USD"},
        "refunded_tip_money": {"amount": 0, "currency": "USD"},
        "status": "succeeded"
      }
    ],
    "created_at": "2026-09-22T17:03:00Z",
    "updated_at": "2026-09-22T17:03:01Z"
  }
}
  • amount_money is the amount Flint worked out for the full refund.
  • payment_refunds has one entry for each processor payment the refund draws from, with its own amount and status. Gift card outcomes appear in tender_allocations; a gift card-only refund has no payment_refunds entries.
  • status is how far the refund got before the response was sent. For a refund on an order, Flint submits it to the card network during the request, so the response is often already succeeded. It can also be pending, or failed when the network rejects it straight away. A 201 means Flint recorded the refund, not that it succeeded. Read status and follow the refund to its outcome, as Track the refund describes.

reason is optional. Choose a reason lists the values.

Send an Idempotency-Key with every refund. It is required when you send tender_allocations or gift_card_load_id, and when you refund an order funded by gift cards by order_id alone; without it, those requests return IDEMPOTENCY_KEY_REQUIRED. For other refunds it is optional, but a retry without it after a timeout can create a second refund. With it, the retry returns the original refund. Flint keeps a refund's key for as long as the refund exists, not just 24 hours. See Idempotency.

Refund part of an order#

Send amount_money to refund less than the whole order. Amounts are integers in the currency's minor unit, so 500 is $5.00. The currency must match the payment's currency.

cURL
curl -X POST https://api.withflintpay.com/v1/refunds \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Flint-Version: 2026-09-07" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: refund-ord-1042-goodwill" \
  -d '{
    "order_id": "ord_1kmn0aExample",
    "amount_money": {"amount": 500, "currency": "USD"},
    "reason": "other",
    "reason_message": "Goodwill credit for the late delivery"
  }'

You can refund an order in as many parts as you like. What stays refundable is what the order's succeeded payments collected, minus refunds that are pending or succeeded, minus any bank returns. Failed and canceled refunds don't count, so their amount can be refunded again. A refund over the remaining amount fails with AMOUNT_EXCEEDS_REFUNDABLE. Once nothing is left, the next one fails with NOTHING_TO_REFUND. On an order funded by gift cards, gift card payments count toward what was collected, and both cases return REFUND_TENDER_CAPACITY_CONFLICT instead.

Flint also records which parts of the order an amount-only refund covers. It is applied to line items first, then to order charges such as delivery fees, and to the tip last. The tip portion shows up in refunded_tip_money on the refund and on each payment_refunds or tender_allocations entry, so your reports can keep sales and tips apart.

Refund the purchase of a gift card#

For a standalone load funded by a Flint payment, send its original gift_card_load_id to POST /v1/refunds with an Idempotency-Key:

Response
{
  "gift_card_load_id": "gcl_01J00000000000000000000000",
  "amount_money": {"amount": 9000, "currency": "USD"},
  "reason": "requested_by_customer"
}

amount_money is the paid consideration. For a $100 card sold for $90, a full $90 purchase refund removes $100 of eligible unspent value. Omit the amount to refund the load's remaining consideration. If you include payment_intent_id, it must match the original funding payment. Omit order_id, tender_allocations, line_items, charges, and tax_breakdown_refunds.

The eligible value stays reserved while the processor outcome is pending or unknown. Confirmed success removes it; confirmed failure releases the hold. Spent or otherwise reserved value returns GIFT_CARD_PURCHASE_REFUND_CONFLICT before the cash refund starts. A later reload cannot cover that mismatch. Read the load's purchase_refunds to follow each allocation and outcome. For a gift card purchased on a Flint order, refund the original order line item instead.

Refund gift card and mixed payments#

For a $100 order funded by a $60 gift card and a $40 processor payment, a $30 amount refund defaults to the original gift card. To return that $30 to the processor payment, provide an explicit allocation:

JSON
{
  "order_id": "ord_01J00000000000000000000000",
  "amount_money": {"amount": 3000, "currency": "USD"},
  "tender_allocations": [
    {
      "tender_type": "payment_intent",
      "payment_intent_id": "pi_01J00000000000000000000000",
      "amount_money": {"amount": 3000, "currency": "USD"}
    }
  ]
}

Send this body to POST /v1/refunds with an Idempotency-Key. Explicit allocations must cover the entire refund amount. An omitted tender receives nothing, and an allocation cannot exceed the value originally collected by that tender minus its pending and successful refunds. Failed processor allocations release their capacity; successful gift card credits remain refunded. A capacity conflict returns REFUND_TENDER_CAPACITY_CONFLICT.

For a gift card allocation, use tender_type: "gift_card_redemption", its original gift_card_redemption_id, and positive USD amount_money. The default destination, original, credits the card that paid. Choose destination: "replacement" to issue new cards under the same merchant and environment. If the original card is closed or cannot accept the entire amount within its balance cap, Flint returns GIFT_CARD_REFUND_DESTINATION_REQUIRED before executing refund allocations. Select a replacement destination and submit the revised request with a new key.

A captured standalone redemption can be refunded with the same gift card allocation without an order_id. A redemption that paid a Flint order retains that order link. For line-item, charge, or flat-tax targets, Flint also checks the remaining value of each selected component and attributes it to its original tender. Additional untargeted value follows the gift card-first order.

Read tender_allocations for each outcome. A gift card credit succeeds when the refund is created; a processor refund may stay pending or fail afterward. The refund's status can therefore be partially_succeeded. Retry an uncertain request with the original key to recover that refund without repeating a successful credit. For a confirmed failed processor allocation, read the current refundable capacity before creating another refund for its remaining amount.

Replacement card codes appear in the create response's top-level gift_card_codes, outside the ordinary refund in data. The original request can recover them for 24 hours. After that, replay still recovers the refund and omits codes. Refund reads, lists, and events contain safe card identities and amounts. Treat returned codes as bearer credentials.

Refund specific line items or charges#

When a buyer is refunded for one item, name the line item instead of calculating the amount yourself. Each entry in line_items needs the order's order_line_item_id. An entry with only that ID refunds the line's remaining quantity. Set quantity or amount_money to choose a quantity or amount. Sending both fails with LINE_ITEM_REFUND_TARGET_AMBIGUOUS.

Refund whole units at the price the buyer paid, including their share of any discount and tax:

cURL
curl -X POST https://api.withflintpay.com/v1/refunds \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Flint-Version: 2026-09-07" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: refund-ord-1042-mug" \
  -d '{
    "order_id": "ord_1kmn0aExample",
    "reason": "defective_product",
    "line_items": [
      {"order_line_item_id": "li_1kmn0aExample", "quantity": 1}
    ]
  }'

With line items and no amount_money, the refund amount is the total of the lines you named. The response adds line_item_allocations, one entry for each line the refund covered:

Response
{
  "line_item_allocations": [
    {
      "order_line_item_id": "li_1kmn0aExample",
      "quantity": 1,
      "refunded_money": {"amount": 1299, "currency": "USD"},
      "tax_refund_mode": "automatic",
      "automatic_refund": {
        "subtotal_money": {"amount": 1200, "currency": "USD"},
        "discount_money": {"amount": 0, "currency": "USD"},
        "tax_money": {"amount": 99, "currency": "USD"},
        "total_money": {"amount": 1299, "currency": "USD"}
      }
    }
  ]
}

automatic_refund breaks down the price paid for the refunded quantity. refunded_money is what the buyer gets back after any adjustments. The order's line item also records it, in refunded_quantity and refunded_money.

A line can't be refunded past what is left on it. The limits return LINE_ITEM_REFUND_QUANTITY_EXCEEDS_REFUNDABLE, LINE_ITEM_REFUND_AMOUNT_EXCEEDS_REFUNDABLE, or NOTHING_TO_REFUND_FOR_LINE_ITEM.

Order charges (delivery fees, service fees, and surcharges; see Tips and charges) are refunded through charges. Each entry takes the order_charge_id and an optional amount_money. Without an amount, the charge's full remaining amount is refunded.

Rules for combining targets:

  • Line item and charge targets need an order. Send order_id, or a payment_intent_id that belongs to an order.
  • A refund that names both line_items and charges also needs a top-level amount_money equal to their total. Without it the request fails with AMOUNT_REQUIRED_FOR_MIXED_REFUND_TARGETS.
  • A top-level amount_money may be larger than the lines you name. Flint applies the rest across the order as it does for an amount-only refund. It can't be smaller: LINE_ITEM_TARGETS_EXCEED_REFUND_AMOUNT.

Tax comes back automatically in proportion to what you refund. To send back less tax than that, or to refund an order-level flat tax, see Sales tax.

Refund one payment on a multi-payment order#

An order can be paid in several payments. When you refund by order_id, Flint draws from the payment with the most left to refund first and lists each payment in payment_refunds. When a line item you name was paid by a particular payment, that payment is used first.

To refund one payment, send its payment_intent_id:

cURL
curl -X POST https://api.withflintpay.com/v1/refunds \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Flint-Version: 2026-09-07" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: refund-pi-1042-duplicate" \
  -d '{
    "payment_intent_id": "pi_1kmn0aExample",
    "amount_money": {"amount": 500, "currency": "USD"},
    "reason": "duplicate"
  }'
  • The payment must be succeeded. Otherwise the request fails with PAYMENT_INTENT_NOT_REFUNDABLE, including for an ACH debit that is still processing.
  • If the payment belongs to an order, the refund is recorded on that order like any other, and the response includes order_id.
  • You can send both order_id and payment_intent_id. If the payment doesn't belong to that order, the request fails with PAYMENT_INTENT_NOT_PART_OF_ORDER.
  • A payment with no order is refunded in the background, so the create response is always pending.

Track the refund to completion#

A refund's status moves like this:

Refund statusStartFinal
  • pending moves to succeeded on every payment refunded
  • pending moves to failed on every payment failed
  • pending moves to partially_succeeded on some payments failed

A refund's status summarizes its payment_refunds, which each carry their own status. requires_action, in_transit, and canceled appear only on those entries. A succeeded refund can still move to failed if the buyer's bank rejects the credit later (see When a refund fails).

status on refund
Final values do not change again
  • pending
    Recorded and on its way to the card network or bank. At least one payment refund is still in progress.
  • requires_action
    Only on a payment_refunds entry: some payment methods need more information before the refund can finish. The refund stays pending meanwhile.
  • in_transit
    Only on a payment_refunds entry: the money is on its way back. The refund stays pending meanwhile.
  • succeeded
    Every payment refund succeeded. This is almost always final, but a bank can still reject the credit afterward, which moves the refund to failed.
  • partially_succeededFinal
    Some payment refunds succeeded and others failed or were canceled. Read payment_refunds for the split.
  • failedFinal
    No money went back to the buyer. failure_reason says why.
  • canceledFinal
    Only on a payment_refunds entry: Flint didn't try this payment because another payment in the same refund failed first. Its failure_reason is payment_refund_not_attempted.

Refund events#

  • refund.created
    The refund was created with status: "pending". Sent for API, Return, and credit-note refunds.
  • refund.updated
    The refund or one of its payment refunds changed. Sent when the card network accepts the refund and again at its outcome.
  • refund.failed
    The refund reached failed. Sent alongside that refund.updated. Not sent for partially_succeeded.
  • order.refunded
    The order's refunded total went up. Sent once for each refund, when it is accepted.
  1. What happens: You call POST /v1/refunds on an order
  2. Flint sends: refund.created
    Created. Status is pending.
  3. Flint sends: refund.updated
    Accepted. Status is pending, or already succeeded.
  4. Flint sends: order.refunded
    The order's refunded total now includes this refund.
  5. What happens: The refund settles
  6. Flint sends: refund.updated
    Status is succeeded or partially_succeeded.
  7. What happens: Or the refund fails
  8. Flint sends: refund.updated
    Status is failed.
  9. Flint sends: refund.failed
    failure_reason says why.

refund.created is also sent when a Return or credit note creates a refund. There is no refund.succeeded event; success is refund.updated with status: "succeeded".

A refund event's data carries refund_id, status, reason (null when unset), amount_money, order_id, payment_intent_id, payment_refunds, and line_item_allocations, plus failure_reason when the refund failed. Older event retries and resends can lack reason; fetch the refund if it is missing. Flint sends one refund.updated for each change in the refund's status or its payment refunds' statuses, and one refund.failed per refund. Delivery is at least once and in no guaranteed order, so treat an event as a signal: fetch the refund, act on its current status, and record which statuses you have already handled for each refund_id.

TypeScript
import { Client } from "@flintpay/node";

const flint = new Client({
  baseUrl: "https://api.withflintpay.com",
  apiKey: process.env.FLINT_API_KEY!,
});

export async function handleFlintWebhook(rawBody: string, headers: Headers) {
  const { event } = flint.verifyWebhook(rawBody, headers, [
    process.env.FLINT_WEBHOOK_SECRET!,
  ]);
  if (event.event_type !== "refund.updated") return;

  const refund = await flint.refunds.get(event.data.refund_id);
  if (await alreadyHandled(refund.refund_id, refund.status)) return;

  switch (refund.status) {
    case "succeeded":
      await markRefunded(refund.order_id, refund.amount_money);
      break;
    case "partially_succeeded":
    case "failed":
      // payment_refunds shows which payments went back and which did not.
      await flagForSupport(refund.refund_id, refund.payment_refunds);
      break;
    // pending: still in progress. The next refund.updated brings the outcome.
  }
  await recordHandled(refund.refund_id, refund.status);
}

Handling refund.updated alone covers failures too, because a failure is also sent as refund.updated with status: "failed". Subscribe to refund.failed as well if an alerting system only needs failures. Webhooks covers signature checks and retries.

If you can't receive webhooks, poll GET /v1/refunds/{refund_id} until the status is succeeded, failed, or partially_succeeded. Refunds on an order often finish during the create call. A refund on a payment with no order, or one the card network is slow to accept, can stay pending for longer.

What the buyer sees#

When a refund reaches succeeded or partially_succeeded, Flint emails the buyer a refund receipt. Flint sends it to the payment's receipt_email, or else the customer's email, or else the order's buyer_contact.email. It lists the amount, the order number, the refunded line items, and your support contact details, plus the credit note and invoice for a credit-note refund. It is not sent when the refund is created or when it fails, and a request can't turn it off.

The receipt includes the refund reason only when the reason describes the purchase: requested_by_customer, duplicate, defective_product, wrong_item_shipped, never_received, not_as_described, arrived_too_late, and accidental_order. It leaves out fraudulent, customer_changed_mind, better_price_found, and other, and says nothing when there is no reason.

Note:

succeeded means the card network accepted the refund. The credit usually appears on the buyer's statement within 5 to 10 business days, and their bank controls that timing. Saying so in your own confirmation saves a support ticket.

What the refund changes on the order#

The order's refunded totals move when the card network accepts the refund, before it reaches succeeded. If the refund then fails, they move back.

Before

Paid order

Response
{
  "order_id": "ord_1kmn0aExample",
  "status": "closed",
  "payment_status": "paid",
  "refund_status": "none",
  "refund_ids": [],
  "settlement_amounts": {
    "paid_money": {"amount": 3098, "currency": "USD"},
    "refunded_money": {"amount": 0, "currency": "USD"}
  }
}

After a $5.00 refund

Partially refunded

JSON
{
  "order_id": "ord_1kmn0aExample",
  "status": "closed",
  "payment_status": "paid",
  "refund_status": "partially_refunded",
  "refund_ids": ["ref_1kmn0aExample"],
  "settlement_amounts": {
    "paid_money": {"amount": 3098, "currency": "USD"},
    "refunded_money": {"amount": 500, "currency": "USD"}
  }
}
  • refund_status is refunded once everything paid has been refunded, and partially_refunded before that.
  • status and payment_status don't change. The order stays paid because the money was collected before it went back.
  • refund_ids lists every refund on the order, including failed ones. Check each refund's status before you count it.
  • Refunded line items, charges, and tips each record their own refunded_money.
  • Once an order has a refund, edits that change its money, such as adding a line item, fail with ORDER_FINANCIAL_MUTATION_NOT_ALLOWED. Make further changes as refunds.
  • The order's activity log gets a refund row when the refund is accepted, and a refund_failed row when it fails or partly fails.

A payment reports the same totals for itself: refunded_money, and a refund_status of none, partially_refunded, or refunded.

When a refund fails#

A failed refund means the money didn't move: it is still in your balance, and the buyer hasn't been paid back. failure_reason on the refund, on failed or canceled payment_refunds entries, and in the refund.failed event says why:

failure_reasonWhat happened
expired_or_canceled_cardThe card can no longer receive credits.
lost_or_stolen_cardThe card was reported lost or stolen.
declinedThe card issuer declined the credit.
insufficient_fundsThe refund couldn't be funded.
insufficient_available_balanceYour available balance couldn't cover the refund. See Balance and bank returns.
payment_disputedThe payment is under dispute, or a bank return took the money back first. The buyer already has it through their bank.
merchant_requestThe refund was stopped at the merchant's request.
payment_refund_failedOn partially_succeeded: one or more of the payment refunds failed. Read payment_refunds.
payment_refund_not_attemptedOn a canceled payment refund: Flint didn't try it because another payment in the same refund failed first.
payment_refund_not_attemptedThis payment refund was canceled before it was attempted because another payment refund failed.
refund_failedThe refund failed with no more specific reason.

A failed or canceled amount becomes refundable again. Contact the buyer to confirm where they can receive the money, fix the cause if you can, and create a new refund with a new Idempotency-Key. Retrying with the failed request's key returns the original result again.

Warning:

On a multi-payment refund, one failed payment doesn't undo the others. Payments already refunded stay refunded, payments not yet tried become canceled, and the refund ends partially_succeeded or failed. Read payment_refunds before you tell the buyer anything: "we returned $30.00 of your $50.00 and are fixing the rest" is a different message from "your refund failed".

A refund can also fail after it succeeded, when the buyer's bank rejects the credit. The refund moves to failed, refund.updated and refund.failed fire, the order's refunded_money goes back down and its activity log gets a refund_failed row, and a recovery balance transaction returns the amount to your balance. The buyer has already had a refund receipt, so tell them yourself.

Refunds on Affirm payments have two extra limits. They must be submitted within 120 days of the payment settling (REFUND_SUBMISSION_DEADLINE_EXPIRED), and after one fails, that payment can't be refunded again through Flint (AFFIRM_REFUND_RETRY_NOT_ALLOWED).

ACH returns and refunds#

Refunds are paid from your balance#

A refund is paid from your available balance. When no payment refund has been accepted and the balance can't cover a refund on an order, POST /v1/refunds returns 409 REFUND_INSUFFICIENT_AVAILABLE_BALANCE. Flint still records the refund as failed with failure_reason insufficient_available_balance, so it appears in your refund list. A refund on a payment with no order returns pending and fails the same way afterward.

If some payment refunds were accepted before another failed, the create call returns the refund and its payment_refunds breakdown. Its status is partially_succeeded if the accepted payments have succeeded, or pending if any are still processing. Wait for the pending payments to finish, then refund only the unpaid remainder. Do not retry the original full amount with a new key.

For a failed refund with no accepted payments, restore your available balance, then create a new refund with a new Idempotency-Key. Sending the same key again returns the same 409. Payouts explains how the available balance fills up.

Bank returns#

A bank return is the buyer's bank pulling an ACH debit payment back days after it succeeded. It is not a refund, and it is not a Return of merchandise. It arrives as a dispute with case_type bank_return (see Disputes).

A bank return reduces what is left to refund on the payment, just as a refund would. After a full bank return there is nothing left, so POST /v1/refunds fails with NOTHING_TO_REFUND.

Flint handles a refund and a bank return one at a time, so the same money can't go back twice. If the bank return comes first, the refund fails with NOTHING_TO_REFUND. If a refund is already on its way when the return arrives, it fails with failure_reason payment_disputed. Either way, the buyer already has the money back through their bank.

An ACH payment that is still processing can't be refunded yet. Wait for it to succeed.

Choose a reason#

reason is optional. It doesn't change how the money moves. It feeds your reports, the Dashboard's refund views, and the buyer's receipt. Choose the most specific value that is true:

SituationReasons
Generalrequested_by_customer, duplicate, fraudulent
A problem with the product or deliverydefective_product, wrong_item_shipped, never_received, not_as_described, arrived_too_late
The buyer changed their planscustomer_changed_mind, better_price_found, accidental_order
Anything elseother

Any other value fails with INVALID_REFUND_REASON. Without a reason, the refund's reason is null.

Three more fields help you find a refund later:

  • reason_message: a note of up to 1,024 characters for the people who handle the refund, such as "Buyer sent photos of the cracked mug".
  • external_reference_id: your ID from another system, such as a support ticket or an RMA number. You can filter the refund list by it.
  • metadata: up to 50 key-value pairs of your own. PATCH /v1/refunds/{refund_id} can change metadata, and nothing else on a refund can be changed.

A refund created by a Return takes its reason from the buyer's return reason:

Return reasonRefund reason
defectivedefective_product
wrong_itemwrong_item_shipped
too_small, too_large, damaged_on_arrivalnot_as_described
arrived_latearrived_too_late
changed_mindcustomer_changed_mind
no_longer_neededrequested_by_customer
A custom reason, or lines with different reasonsother

Read the Return for the buyer's own wording.

Errors to handle#

Flint checks refunds before any money moves, so most mistakes come back as a 400 from the create call rather than a refund that fails later.

Errors use the standard error envelope:

JSON
{
  "error": {
    "type": "validation_error",
    "code": "AMOUNT_EXCEEDS_REFUNDABLE",
    "message": "Refund amount exceeds remaining refundable amount",
    "request_id": "9d2b6e4f-3a8c-4d1e-8c47-5b9a2e0d4f36"
  }
}

Refunds get retried often: a support agent clicks twice, or a job restarts partway through. An Idempotency-Key makes each retry return the first result. After an error, a corrected request is a new action and needs a new key. See Idempotency.

Expect races too. If a support agent refunds in the Dashboard while your code refunds through the API, one of them gets AMOUNT_EXCEEDS_REFUNDABLE or NOTHING_TO_REFUND (REFUND_TENDER_CAPACITY_CONFLICT on an order funded by gift cards). That means the order is already refunded. Handle it as resolved, not as an alert.

Find and reconcile refunds#

GET /v1/refunds lists refunds, newest first, with cursor pagination. Filters:

  • order_id, payment_intent_id, customer_id, return_id, return_resolution_id, external_reference_id
  • status (one value) and reason (repeat it, or separate values with commas)
  • min_amount and max_amount, which need currency
  • created_after, created_before, updated_after, updated_before
  • query, which matches reason_message, external_reference_id, or the start of a refund_id
  • sort_by (created_at, updated_at, or amount) and sort_direction

A daily check for failures:

cURL
curl "https://api.withflintpay.com/v1/refunds?status=failed&created_after=2026-09-01T00:00:00Z" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Flint-Version: 2026-09-07"

GET /v1/refunds/{refund_id} accepts expand with order, customer, payment_intent, and payment_refunds.payment_intent, so you can read a refund with its context in one call:

cURL
curl "https://api.withflintpay.com/v1/refunds/ref_1kmn0aExample?expand=order" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Flint-Version: 2026-09-07"

For your books, each refund posts a negative refund balance transaction and is subtracted from your next payout. The transaction's related_resource has type: "refund" and the refund_id, so you can look the refund up from the ledger. Reconciliation matches them to bank deposits. A refund doesn't give back the processing fee on the original payment, and Flint charges no fee for refunding. See Processing fees.

Test refunds#

Sandbox refunds run the same lifecycle as live ones, without real money. Pay a test order with the 4242 4242 4242 4242 card (Testing walks through it), refund it with the requests above, and watch refund.created, refund.updated, and order.refunded arrive at your webhook endpoint. Then try the failures: refund an unpaid order to get NO_PAYMENTS_FOR_ORDER, and refund one cent more than what is left to get AMOUNT_EXCEEDS_REFUNDABLE.

Next steps#

  • Returns: refunds tied to merchandise coming back, with eligibility and restocking.
  • Credit notes: correct a paid invoice and refund the difference.
  • Manual capture: cancel an uncaptured hold instead of refunding it.
  • Sales tax: override the tax a line item refund sends back.
  • Webhooks: verify signatures and handle retries for refund.* events.
  • Disputes: when the buyer goes to their bank instead of asking you.
  • Reconciliation: match refunds to payouts and bank deposits.
  • Refunds API reference: every field, including explicit tax and adjustment refunds.

Was this helpful?