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:
| Situation | Use |
|---|---|
| Money goes back and nothing comes back: a duplicate charge, a service problem, a goodwill credit, an order canceled before it shipped | POST /v1/refunds |
| Merchandise comes back | A Return. It handles eligibility, receiving, restocking, and the refund. |
| The payment is still an uncaptured authorization | Cancel it. Nothing was captured, so there is nothing to refund. |
| A paid invoice was wrong | A 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 -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:
{
"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_moneyis the amount Flint worked out for the full refund.payment_refundshas one entry for each processor payment the refund draws from, with its own amount and status. Gift card outcomes appear intender_allocations; a gift card-only refund has nopayment_refundsentries.statusis 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 alreadysucceeded. It can also bepending, orfailedwhen the network rejects it straight away. A201means Flint recorded the refund, not that it succeeded. Readstatusand 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 -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:
{
"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:
{
"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 -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}
]
}'
Refund part of a line's value, such as a price adjustment. Flint takes tax back in proportion:
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-chip" \
-d '{
"order_id": "ord_1kmn0aExample",
"reason": "not_as_described",
"line_items": [
{
"order_line_item_id": "li_1kmn0aExample",
"amount_money": {"amount": 400, "currency": "USD"}
}
]
}'
Refund the unit and keep part of it with a refund_adjustments entry. The buyer gets the line's refund amount minus the fee:
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-restock" \
-d '{
"order_id": "ord_1kmn0aExample",
"reason": "customer_changed_mind",
"line_items": [
{
"order_line_item_id": "li_1kmn0aExample",
"quantity": 1,
"refund_adjustments": [
{
"adjustment_type": "restocking_fee",
"applies_to": "none",
"amount_money": {"amount": 200, "currency": "USD"},
"reason": {"code": "restocking_policy"}
}
]
}
]
}'
A restocking_fee must use applies_to: "none", and every adjustment needs a reason.code. The code other also needs a reason.description. Adjustments must add up to less than the line's refund. To return a fee you kept, refund it later with adjustment_refunds and the adjustment's refund_line_item_adjustment_id. For merchandise that physically comes back, a Return applies your return policy's restocking fee for you.
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:
{
"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 apayment_intent_idthat belongs to an order. - A refund that names both
line_itemsandchargesalso needs a top-levelamount_moneyequal to their total. Without it the request fails withAMOUNT_REQUIRED_FOR_MIXED_REFUND_TARGETS. - A top-level
amount_moneymay 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 -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 withPAYMENT_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_idandpayment_intent_id. If the payment doesn't belong to that order, the request fails withPAYMENT_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:
- 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 refundRefund events#
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.
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.
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
{
"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
{
"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_statusisrefundedonce everything paid has been refunded, andpartially_refundedbefore that.statusandpayment_statusdon't change. The order stayspaidbecause the money was collected before it went back.refund_idslists every refund on the order, including failed ones. Check each refund'sstatusbefore 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
refundrow when the refund is accepted, and arefund_failedrow 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_reason | What happened |
|---|---|
expired_or_canceled_card | The card can no longer receive credits. |
lost_or_stolen_card | The card was reported lost or stolen. |
declined | The card issuer declined the credit. |
insufficient_funds | The refund couldn't be funded. |
insufficient_available_balance | Your available balance couldn't cover the refund. See Balance and bank returns. |
payment_disputed | The payment is under dispute, or a bank return took the money back first. The buyer already has it through their bank. |
merchant_request | The refund was stopped at the merchant's request. |
payment_refund_failed | On partially_succeeded: one or more of the payment refunds failed. Read payment_refunds. |
payment_refund_not_attempted | On a canceled payment refund: Flint didn't try it because another payment in the same refund failed first. |
payment_refund_not_attempted | This payment refund was canceled before it was attempted because another payment refund failed. |
refund_failed | The 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.
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:
| Situation | Reasons |
|---|---|
| General | requested_by_customer, duplicate, fraudulent |
| A problem with the product or delivery | defective_product, wrong_item_shipped, never_received, not_as_described, arrived_too_late |
| The buyer changed their plans | customer_changed_mind, better_price_found, accidental_order |
| Anything else | other |
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 changemetadata, 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 reason | Refund reason |
|---|---|
defective | defective_product |
wrong_item | wrong_item_shipped |
too_small, too_large, damaged_on_arrival | not_as_described |
arrived_late | arrived_too_late |
changed_mind | customer_changed_mind |
no_longer_needed | requested_by_customer |
| A custom reason, or lines with different reasons | other |
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:
{
"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_idstatus(one value) andreason(repeat it, or separate values with commas)min_amountandmax_amount, which needcurrencycreated_after,created_before,updated_after,updated_beforequery, which matchesreason_message,external_reference_id, or the start of arefund_idsort_by(created_at,updated_at, oramount) andsort_direction
A daily check for failures:
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 "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.
