Skip to main content
Returns Verification answers a narrower, sharper question than a general product lookup: did this organization sell this exact unit, and has it already come back? It’s built for a return-desk scanner or POS plugin — one call, one decision.
Don’t confuse this with Serial Verification (gs1:verificationService). That’s a public, unauthenticated anti-counterfeit check anyone can run from a consumer scan. This is an authenticated, retailer-scoped returns check — a different question, a different API, a different audience.

How a unit becomes verifiable

Verification reads from your organization’s own EPCIS 2.0 event history — there’s no separate “sale record” table, and no state to keep in sync by hand. Get the events in, and the verify call reads them back out.

Recording a sale

Two ways to get sale events in, both landing on the same event log: Real-time, per serial — capture it like any other EPCIS event:
Nightly batch — upload your POS export as a CSV instead of one call per sale:
Each row is translated into the same retail_selling / retail_sold event server-side. Re-uploading a file is safe — rows already captured are reported as skipped, not duplicated, so a replayed nightly export is a no-op. Maximum upload size 10 MiB, maximum 10,000 rows per upload.

Recording a return

Closing the loop uses the same capture endpoint with a different business step:
Once that lands, the next verify call on the same serial reports already_returned — which is what stops the same unit being refunded twice.

The verify call

Requires an X-API-Key for an organization membership with OWNER or MANAGER role — this is a privileged, retailer-side call, not a public scan.

Decision states

recall is populated only when decision is recalled_lot, and carries recall_id, title, severity, and the matched lot.

Why the states catch what they catch

Serialization at the return desk is a targeted defense, not a blanket one — it’s worth being specific about what each pattern trips:
  • Receipt fraud (a valid receipt paired with a different, unpurchased unit) — the receipt matches a transaction, but the physical serial being returned was never the one sold under it, so it reports never_sold regardless of how convincing the paperwork looks.
  • Double returns — the second attempt on an already-processed unit reports already_returned instead of quietly refunding twice.
  • Cross-retailer arbitrage (bought at one retailer, returned at another for a better refund) — because verification only ever reads your organization’s own events, a unit this store never sold reports never_sold or unknown_serial here even though another retailer’s system would show it as legitimately sold.
  • Counterfeit-swap returns (genuine unit purchased, a counterfeit substituted back into the box before return) — this only works if the serial is scanned off the unit itself at the return desk, not read off the outer packaging. A code that lives only on the box comes back unchanged no matter what’s inside it.

Tenant isolation

Verification answers are computed only from the querying organization’s own events. Two retailers selling the same GTIN + serial (unlikely, but the model doesn’t assume otherwise) hold entirely separate event histories and neither can see the other’s. The one deliberate exception is recall status: a recall is public product-safety information, not one retailer’s private event, so an open recall on a unit is reported regardless of which organization published it or which organization is asking.

Performance

The read is a single indexed lookup against denormalized columns cached on the serial’s row — never a walk through the event log. This is what makes it safe to call inline at a return desk rather than as a background check.

Errors