All articles
ProductJul 17, 2026 · 7 min

Returns in a Headless Commerce Stack

DA
Defne Aksoy
Head of Product

Going headless solves a rendering problem and creates an ownership problem. Once the storefront is a custom frontend talking to commerce APIs, every feature that used to arrive bundled with the platform's theme, including cart, checkout, account, and returns, becomes something you either build, integrate, or explicitly delegate. Returns are the piece teams forget until launch is close, because in a monolith the returns page just existed. In a composable stack it does not exist until you decide where it lives, what renders it, and which service owns the state machine behind it. Get that decision right and returns feel native across every storefront; get it wrong and you end up with return logic smeared across your frontend, your OMS, and three webhooks nobody documented.

Why headless changes the returns problem

A traditional platform hosts the returns UI, the returns logic, and the order data in one place, so a return is a local operation. A headless architecture pulls those apart on purpose. The frontend might be a single storefront, or several storefronts for different regions and brands, each needing the same return capability with its own look. The order data lives in a commerce backend. Fulfillment and inventory live elsewhere again. A return now has to coordinate across all of them, initiated from any storefront, without a single monolith to anchor it. The requirement that falls out of this is non-negotiable: returns must be API-first, with the logic decoupled from any one frontend, or you will reimplement it per storefront and watch the implementations drift apart.

Where the returns logic should live

The load-bearing rule is that return logic, meaning policy enforcement, eligibility, resolution decisions, and state transitions, belongs in a dedicated returns service behind an API, never in the storefront. The frontend's job is to render a flow and collect input; it should not decide whether an item is in-window, whether an exchange is allowed, or when a refund fires. Push those decisions into the frontend and you have to duplicate them across every storefront and redeploy all of them each time a policy changes. Keep them in a service and every storefront calls the same endpoints, gets the same answers, and stays consistent for free. This is the same separation-of-concerns discipline that makes any returns API integration maintainable, and headless just makes it mandatory rather than merely advisable.

PatternHow it worksBest fitMain pitfall
Full APIFrontend calls returns endpoints directly; you build the UITeams wanting full control of the return UX across storefrontsYou own retries, errors, and reconciliation
Webhook-drivenReturns service emits events; your backend reacts and syncsKeeping OMS, inventory, and BI in sync with return stateRequires idempotent handlers for at-least-once delivery
Embedded widgetA prebuilt returns component drops into the storefrontFast launch with limited engineering capacityLess control over styling and flow specifics
Hosted handoffStorefront links out to a hosted returns portalMinimal build, consistent flow across many storefrontsA context switch away from your domain and design

The event-driven backend and webhooks

In a composable stack the returns service should be event-driven, emitting a webhook on every state change, including return_created, label_generated, package_received, inspection_completed, and refund_issued, so the rest of your architecture reacts rather than polls. Your OMS updates the order, inventory restocks on receipt, your BI warehouse records the reason code, and your notification service messages the customer, all off the same event stream. The detail that separates a working integration from a 2 a.m. incident is idempotency: webhook delivery is at-least-once, so every handler must be safe to run twice on the same event or a redelivered package_received will restock an item twice. Commerce platforms including Shopify and the composable backends that have followed them expose exactly this kind of event surface, and the returns service should mirror it rather than invent a parallel one your team has to learn separately.

In a headless stack, the return is not a page you host. It is a state machine you expose, and every storefront is just one of its clients.

Pitfalls specific to composable stacks

The failure modes in headless returns are predictable, and all of them trace back to logic leaking out of the returns service. The first is duplicating policy in the frontend: a 30-day window hardcoded in a component breaks the moment marketing runs a holiday extension, and it breaks in only the storefronts someone remembered to update. The second is treating webhooks as fire-and-forget instead of idempotent, which double-processes refunds and restocks. The third is scattering routing decisions across services instead of centralizing them, so no single system can answer where a given return should go; keeping that in one place is the point of automated routing rules. The fourth is forgetting that multiple storefronts need one coherent return experience, so a customer who bought on your EU site and your US site should not meet two visibly different return flows built by two teams.

ResReturn is built API-first for exactly this shape of stack. The returns logic, meaning policy, eligibility, exchange-first resolution, instant credit, and routing, lives behind the API and emits webhooks on every state change, so a composable frontend can render its own return UI, drop in an embedded widget, or hand off to the hosted self-service portal, all backed by the same state machine. Multiple storefronts across regions and brands call one service and get one consistent set of rules, which is the whole reason to keep return logic decoupled from the frontend in the first place. Nothing you build on top has to reverse-engineer behavior that only ever existed inside a UI, because the UI was never where the behavior lived.

  • Keep all return logic, meaning policy, eligibility, and resolution, in an API-first service, never in the storefront frontend.
  • Make the backend event-driven: emit a webhook on every state change and let the OMS, inventory, and notifications react.
  • Make every webhook handler idempotent, because at-least-once delivery will replay events and double-process refunds otherwise.
  • Fetch policy from the API rather than hardcoding windows in the frontend, so a change deploys once instead of per storefront.
  • Give every storefront the same return service so multiple brands and regions share one coherent flow instead of drifting apart.
How are returns different in a headless commerce setup?

In a traditional platform the returns UI, logic, and order data live together, so a return is a local operation. Headless pulls them apart: the frontend is custom, order data lives in a commerce backend, and fulfillment lives elsewhere. Returns therefore have to be API-first, with the logic in a decoupled service that any storefront can call, rather than a page the platform hosts for you.

Where should return logic live in a composable architecture?

In a dedicated returns service behind an API, not in the storefront. The frontend should render the flow and collect input while the service decides eligibility, resolution, and state transitions. Putting that logic in the frontend forces you to duplicate it across every storefront and redeploy them all whenever a policy changes.

Why do returns webhooks need to be idempotent?

Because webhook delivery is at-least-once by design, and the sender retries on timeout or error without knowing if your handler finished. If a handler is not safe to run twice, a redelivered event like package_received will restock an item twice or issue a second refund. An idempotency key that records processed events prevents the replay from doing damage.

Can one returns service support multiple storefronts?

Yes, and that is the main reason to keep return logic API-first. Multiple storefronts across regions or brands call the same service, get the same policy answers, and present a consistent flow, whether each renders its own UI, embeds a widget, or hands off to a hosted portal. The alternative, separate return logic per storefront, guarantees the implementations drift apart over time.

See it on your own returns.

Start free