The record-only flow below was exercised on staging on 2026-10-08 with a test key:
a fresh MXN 1,160 payment received an MXN 116 refund record, and a readback confirmed
11,600 cumulative minor units. The Stripe path remains source-reviewed and was not
executed with a processor. The handler multiplies the submitted amount by 100; the
worked example uses MXN. Confirm processor
currency support before adapting it to another currency. A credit note or cancellation
is a separate fiscal operation, not proof that money was returned.
Before testing: check refund automations
Recording a refund can trigger published refund Journeys and the team’s automatic refund handling, including actions on associated fiscal documents. A payment’sautomation_type: "none" does not disable these separate settings. Test payments
match published test Journeys; check those with the account owner before running a test.
Stripe test events can also reach configured webhook listeners and send team refund
notifications. ignore_emails on the original payment does not suppress every refund
notification. Use an approved test account with known webhook destinations and
recipients, and a fresh synthetic payment without invoice or receipt associations.
1. Read the original payment and choose the amount
Use Bash withcurl and jq; use the quickstart commands
to set the base URL and key, and check their environment.
PAYMENT_ID must refer to the intended succeeded payment in that team
and mode. A payment merely requested or canceled is not eligible.
data.total - (data.total_refunded // 0) / 100. The maximum considers all previous refunds.
Keep these units separate
Do not send
11600 to mean a refund of MXN 116. That would request MXN 11,600.
Use decimal-safe arithmetic in your application and serialize refund attempts for the
same payment; a client-side remaining-balance check is not a concurrency lock.
2. Record the authorized refund
This example records money you returned through another process. It does not initiate a bank transfer or a Stripe refund. Confirm the payment, amount and reason before running it.200. Save data.refund.id. The handler sets its
refund record status to succeeded; this is not independent confirmation of a bank or
processor settlement. The response’s data.payment.refund_status is fully_refunded
when the cumulative amount equals the payment amount; otherwise it is requires_action.
That label alone does not mean this partial refund failed.
3. Read back the refund record
If you intend to return money through Stripe
Only after confirming the payment has the correct Stripe PaymentIntent and account, use the same endpoint withexternal_processor_refund: true. This is a separate choice;
do not first run the record-only example and then repeat it to send money.
The public payment response does not prove PaymentIntent eligibility. Check the original
processor record or ask the account owner. A payment without a PaymentIntent is rejected
with 400 and External processor refund is not allowed for this payment.
After an authorized processor refund, verify both the gigstack refund and the corresponding
refund in the connected Stripe account. The refund POST does not expose the Stripe refund ID
or use its final settlement status as a completion check. Do not report money received
by the customer solely because gigstack’s record says succeeded.
A later Stripe webhook can change the payment status to partial_refunded or
refunded and append a processor refund entry alongside the API-created entry. Do not
count list entries as separate money movements or sum them to infer the refunded
balance. Reconcile the cumulative amount against the corresponding Stripe refund and
preserve both identifiers when asking support to investigate a mismatch.
Duplicate attempts, failures, and timeouts
This endpoint acceptsreason, amount and external_processor_refund; it does not
document an idempotency key. Never apply the payment-registration replay recipe here.
A processor request can succeed before the local update fails. After a timeout or 500,
compare GET /payments/{id} and the processor history with your pre-request snapshot.
Preserve the payment ID, amount, reason, time and any refund ID. Ask the account owner or
support to reconcile an ambiguous result before another POST.
For 400, inspect the body: the payment must be succeeded, the amount must be at least
0.01, and cumulative refunds cannot exceed the original payment. Correct the identified
problem; do not invent a new reference to bypass a duplicate or over-refund concern.
See the refund API contract for all response shapes.