How returns reconciliation works
If you've ever heard "our returns double-counted stock," this is the article that explains why it happens — and what SKU.io does to prevent it.
A marketplace return can reach you twice. Once as a record: a refund webhook from the channel (Shopify via Loop Returns, for example) that often fires the moment the first return-tracking scan happens — days before anything physically arrives. That refund can auto-create an RMA, and if your tenant auto-receives refunded returns, it can add stock. Then the parcel actually shows up at the dock and the warehouse receives it — adding stock again. One returned unit, two arrivals, two inventory bumps, two hits of COGS. Reconciliation is the machinery that recognizes those two arrivals as the same return and counts it once.
The core rule: one physical receipt, one stock event
The whole design rests on a single invariant:
The physical return receipt is the one and only inventory event for a return.
Every other path that links a return — a marketplace refund webhook, a manual link on the Unmatched Returns report, the scheduled sweep — records the received quantity against the RMA but creates no second stock movement. Paperwork gets attached to the receipt that already landed the goods; stock is never added a second time.
You can see the stock event happen at the Receive Station. When a warehouse scans a parcel that has no RMA yet, the confirm dialog says so plainly and still adds the unit to stock:

That receipt is the inventory event. Reconciliation's job from here on is bookkeeping, not restocking.
Blind receipts and the Unmatched Returns report
A blind receipt is a return receipt with no linked RMA — the warehouse received a parcel before any RMA existed for it. Blind receipts land real, sellable stock immediately, then wait to be matched to the return they belong to.
Every receipt carries a reconciliation status:
| Status | Shown as | Meaning |
|---|---|---|
matched | (linked) | The receipt is attached to an RMA. |
blind_unmatched | Unmatched | Received with no RMA; awaiting linkage. |
needs_review | Needs review | A candidate exists but the match is ambiguous — a human must decide. |
The rule that keeps this honest: any receipt whose RMA gets set is automatically forced to matched. Linkage and status can't drift apart.
Unmatched and needs-review receipts collect in the Unmatched Returns report — "blind receipts awaiting linkage to an RMA or sales order."

Automatic matching only looks back 30 days, so the report has an Age filter to surface older parcels — the ones you'll likely resolve by hand or write off.
How a marketplace return and its parcel get reconciled
The refund record and the physical parcel can arrive in either order. SKU.io handles both, and the trick in each case is to top up only the shortfall — never to restock the parcel twice.
Blind-receive, then webhook. The parcel arrives first and becomes a blind receipt (stock added). Later the refund webhook creates the RMA. Instead of receiving fresh stock, the webhook first reconciles the existing blind receipt, then adds only the per-product shortfall — expected quantity minus what's already been received. If the parcel already covered the return, the shortfall is zero and nothing more is stocked.
Refund-first, then receive (the typical Loop ordering). The refund fires early and creates the RMA before the parcel ships. When the goods physically arrive, the receipt links to that open, receipt-less RMA. And because a channel can re-sync and short-circuit, an hourly scheduled sweep (ReconcileOpenReturnsJob) re-checks open returns and catches anything that slipped through.
Either way, the returned unit is counted exactly once.
When SKU.io asks a human to decide
Reconciliation never guesses. It auto-links only when the match is unambiguous in both directions:
- If two blind receipts could match one RMA, or
- If one receipt could fit two open RMAs,
the receipt is flagged needs_review and left for a person. You resolve it from the Unmatched Returns report with the Link Blind Receipt dialog — search for the right RMA (or sales order) and confirm.

Two more guardrails keep automation conservative:
- Draft RMAs are excluded from auto-reconcile. Only Approved, In Transit, and Partially Received RMAs are considered "receivable," so a draft someone is still editing is never linked out from under them.
- Manual linking is guarded too. The dialog refuses the link if the receipt is already linked, the RMA isn't receivable, the warehouses don't match, the RMA already has receipts (linking would double-count), or the RMA has no lines to receive against.
The two reconciliation engines
Under the hood, two services do the matching. Which one runs depends on a single question: does the RMA already have lines?
ReturnReconciliationService— used when the marketplace refund created the RMA with expected lines. It attaches the blind receipt to that RMA, maps each receipt line onto an existing RMA line, and records the received quantity — adding no new movement. Its outcomes are Matched, NeedsReview, or NoCandidate.BlindReceiptMatchService— used when the marketplace RMA is header-only (created with no lines yet). It heals the RMA by mirroring the receipt's lines into new RMA lines, so the paperwork finally describes what actually came back.
Both engines claim a receipt atomically — a conditional "set the RMA only where it's still unset," taken under a row lock — so two processes (a webhook and the sweep, say) can never both attach the same receipt.
Related
- Receive returns at the Receive Station — where the one physical stock event is created.
- Link unmatched (blind) returns — resolve the Unmatched Returns report by hand.
- Match marketplace returns — the Returns Inbox side of reconciliation.
- Returns reconciliation reference — statuses, services, and guard conditions at a glance.
- Process a customer return (RMA) — the end-to-end RMA lifecycle.
Video transcript
A marketplace return can arrive twice — once as a refund record, once as a physical parcel. SKU.io treats the physical receipt as the one and only stock event. Every other path just records the received quantity, adding no second movement. Receipts that arrive without an RMA wait in the Unmatched Returns report as blind receipts. When a refund and its parcel are clearly the same return, SKU.io links them and tops up only the shortfall. When the match is ambiguous, it asks a person to decide — so a return is counted once, never twice.