Skip to content

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}/refunds was removed on 2026-09-29. Requests to it now fail; they never refund anything.

Endpoints:

  • GET /v2/orders/{orderId}/refunds — list an order's refunds
  • GET /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}/refunds returns a plain JSON array of every refund for the order — all statuses including FAILED — oldest first, unpaginated. An unknown order ID yields 200 [], not 404.
  • GET /v2/orders/{orderId}/refunds/{refundId} returns one refund, 404 if it doesn't exist under that order in your account. This is the endpoint to poll for PROCESSING → 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, the refund.completed webhook event fires, and (if the refund-email toggle in your partner settings is on) the customer gets a confirmation email that includes the refund's reason.
  • FAILED — the refund did not go through; errorMessage says why.

Two things to build for:

  1. Late failures. Card refunds can fail at the bank days after appearing to succeed. When that happens, a refund that was already COMPLETED flips to FAILED and the order's refund accounting (cumulative totals and per-line ledgers) is reversed. Do not treat COMPLETED as immutable in your books.
  2. Failures send refund.failed. Whether the processor rejects the refund right away or reports it failed days later, the refund goes FAILED and one refund.failed event fires with the processor's failureReason. 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

  • paymentIntentId tells you which payment was refunded; the platform always resolves it from the order's payment history. stripeRefundId is the processor's refund identifier, useful when reconciling against your billing statements.
  • currency is the currency the processor refunded in ("usd" today).
  • initiatedBy records who created the refund.
  • camelCase everywhere on this surface; null fields are present as null.