Refunds¶
A refund returns money against the payment that funded an order: a card payment through Hosted Checkout, a subscription renewal charge, or your invoiced billing arrangement.
Refunds are issued by DailyStak, not through the API. To request a refund for an order, contact DailyStak support. The API lets you read the refunds on your orders and follow them to completion.
POST /v2/orders/{orderId}/refundswas removed on 2026-09-29. Requests to it now fail; they never refund anything.
Endpoints:
GET /v2/orders/{orderId}/refunds— list an order's refundsGET /v2/orders/{orderId}/refunds/{refundId}— fetch one refund (poll this for completion)
Authentication¶
Both endpoints require the order/v2.read scope. As everywhere in v2, you need a v2 API key — v1 keys cannot call these endpoints.
Reading refunds¶
GET /v2/orders/{orderId}/refundsreturns a plain JSON array of every refund for the order — all statuses includingFAILED— oldest first, unpaginated. An unknown order ID yields200 [], not404.GET /v2/orders/{orderId}/refunds/{refundId}returns one refund,404if it doesn't exist under that order in your account. This is the endpoint to poll forPROCESSING→COMPLETED/FAILED.
A refund looks like this:
{
"refundId": "2vT4kQx0mA9rLc3PbYs61WqZkDe",
"orderId": "2vSGym0bH8qVEwCIGlyFoRgJq1A",
"partnerId": "partner_abc",
"status": "PROCESSING",
"refundType": "PARTIAL",
"amount": 3750,
"currency": "usd",
"stripeRefundId": "re_3XYZ789",
"paymentIntentId": "pi_3ABC123def456",
"lineItems": [
{"lineItemId": "2vSGyowEEPYh6T4C4NylMGXBpX2", "quantity": 1, "amount": 1250},
{"lineItemId": "2vSGypLtGKQr8V5D5OzmNHYCqY3", "quantity": 2, "amount": 2500}
],
"reason": "Two capsules damaged in transit",
"initiatedBy": "client_abc123",
"errorMessage": null,
"createdAt": "2026-07-25T14:30:00+00:00",
"updatedAt": "2026-07-25T14:30:02+00:00"
}
refundType is FULL when the refund exhausted the order's remaining refundable balance — so a second refund that drains what's left is typed FULL even though it isn't the whole order total. Anything less is PARTIAL. lineItems is present when the refund was allocated to specific order lines (each lineItemId is an id from the order's line items).
The asynchronous lifecycle¶
Refunds are asynchronous: the money moves when the payment processor settles, which flips the refund to COMPLETED (or FAILED).
PENDING → PROCESSING → COMPLETED
↘ FAILED (COMPLETED can also flip to FAILED later)
PROCESSING— the refund has been submitted to the payment processor.PENDING— a transient internal state you may occasionally observe; it resolves on its own and is not an error.COMPLETED— the processor settled the refund. The order's refund accounting updates, therefund.completedwebhook event fires, and (if the refund-email toggle in your partner settings is on) the customer gets a confirmation email that includes the refund'sreason.FAILED— the refund did not go through;errorMessagesays why.
Two things to build for:
- Late failures. Card refunds can fail at the bank days after appearing to succeed. When that happens, a refund that was already
COMPLETEDflips toFAILEDand the order's refund accounting (cumulative totals and per-line ledgers) is reversed. Do not treatCOMPLETEDas immutable in your books. - Failures send
refund.failed. Whether the processor rejects the refund right away or reports it failed days later, the refund goesFAILEDand onerefund.failedevent fires with the processor'sfailureReason. Subscribe to it rather than polling. A refund that fails after it was created also emails your account's admin users once.
Cents, not decimal strings¶
Everything on the refund surface is integer cents: amount on refunds and their line items. This is different from order pricing, which uses two-decimal strings ("33.39"). When you compare refunds with order totals, convert deliberately — "33.39" → 3339.
What refunds do to the order¶
As refunds complete, the platform updates the order's refund accounting: a cumulative refunded total, an order-level refund state (unrefunded, partially, or fully refunded), and a per-line ledger of refunded quantities and amounts. A refund can never exceed what was actually charged for the order (its frozen settlement snapshot) minus what has already been refunded.
This accounting is not exposed as fields on the order object — to see where an order stands, list its refunds with GET /v2/orders/{orderId}/refunds and sum the COMPLETED ones. Refunding does not change the order's fulfillment status.
Webhook events¶
| Event | Fires when | Data |
|---|---|---|
refund.created |
A refund is created against one of your orders and submitted | refundId, orderId, amount, refundType, status, reason |
refund.completed |
The payment processor settled the refund | same shape |
refund.failed |
The refund failed, when created or later (see The asynchronous lifecycle) | same shape, plus failureReason |
Delivery, signatures, and retry behavior are covered in Webhooks.
Errors¶
Bodies are {"message", "code", "details"?} — branch on code.
| Status | code |
When |
|---|---|---|
401 / 403 |
— | Missing/invalid token, or token lacks a required scope |
404 |
REFUND_NOT_FOUND |
Single-refund GET only: no such refund for that order in your account |
Good to know¶
paymentIntentIdtells you which payment was refunded; the platform always resolves it from the order's payment history.stripeRefundIdis the processor's refund identifier, useful when reconciling against your billing statements.currencyis the currency the processor refunded in ("usd"today).initiatedByrecords who created the refund.- camelCase everywhere on this surface; null fields are present as
null.