E-commerce / Shopify#
Send a genuine, counter-advanced tap to a destination you control — a Shopify product page, a claim page, or a rewards portal. The redirect target is owner-stored config, never taken from the tag or the URL, so a forgery can never reach attacker-controlled input: it falls back to the safe tap page and never redirects.
This is the same verification core as the Hosted Gateway — you are just adding a destination on top of it.
How it works#
- A customer taps a provisioned tag. The phone opens the tag's SUN URL at
/t(the human tap path). - The gateway verifies the tap: it re-derives the per-tag key, checks the MAC, and advances the monotonic counter.
- If the tap is authentic and you have set a destination, the gateway
302-redirects to it — with the tap context available to the target. - If the tap is forged, replayed, or unknown, there is no redirect; the visitor sees the "Not verified" tap page. Your Shopify store never receives the attacker's request.
Because step 3 only fires on an authentic, counter-advanced tap, a redirect is a proof of genuineness for everything downstream of it.
Set the system destination (Creator+)#
Point the whole system at one destination with POST /api/systems/{id}/routing. Custom routing is a Creator+ feature; the entitlement is checked here, at config time, never on the hot tap path.
curl -X POST https://edgenfc.com/api/systems/sys_01H…/routing \
-H "authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{ "url": "https://shop.example.com/products/limited-drop" }'Response:
{ "system_id": "sys_01H…", "route_url": "https://shop.example.com/products/limited-drop" }Now every authentic tap on that system 302s to the Shopify product. The url must be an absolute http(s) URL — anything else is rejected 400 and never stored, so a bad value can never become a redirect target. Send { "url": null } to clear the route.
Note
Routing is a bare redirect, not a Shopify app install. It works with any storefront (Shopify, a custom cart, a claim page) because it just hands the browser an owner-configured URL. Deeper Shopify-app integration is out of scope here.
Route the validated tap to a VIP claim portal#
The interesting pattern is not "tap goes to product page" — it is tap gates something. Because /t verifies before it redirects, arriving at your claim portal is itself the entitlement check: only a genuine, counter-advanced tap can put a browser there.
A claim flow looks like this:
- Set the system route to your claim portal (
https://shop.example.com/claim) instead of the product page. - Everyone who taps a genuine tag lands on the portal. Everyone who taps a forgery, replays a captured URL, or hits an unknown/revoked tag sees the "Not verified" tap page and never reaches the portal at all.
- The portal collects the email / issues the discount / registers the warranty, and hands off to Shopify checkout as normal.
The security property you are relying on is narrow but strong: the redirect target is owner-stored config, never taken from the tag or the URL. A forgery cannot be steered anywhere — not to your portal, and not to an attacker's.
Warning
The redirect proves a genuine tag was tapped. It does not by itself prove which tag, and the claim-portal URL, once a genuine visitor is on it, is an ordinary URL they can share. If your claim must be one-per-item, bind it to the tap — see the next section.
Pass tap context to the target#
EdgeNFC does not append uid or ctr to your redirect. The 302 sends the browser to the route_url you stored, byte for byte. That is deliberate — the target is owner config, so nothing tag-supplied can be smuggled into it. There are three honest ways to get tap context downstream:
- Per-tag routes (Enterprise). Store a different
route_urlper tag withPATCH /api/tags/{uid}—https://shop.example.com/claim/item-4821. The tag→URL mapping is server-side config, so the target does know which item it is, and the identifier was never guessable input. - Verify it yourself. Point the tag's SUN URL at your own handler and call
GET /verify?sys=&uid=&ctr=&mac=from your server. You get the full verdict —{ authentic, uid, read_ctr, replay, reason }— and can key a discount code, a claim row, or a CRM record touid+read_ctr. See Custom web app. - Static context in the stored URL. Campaign and UTM parameters you control can simply be part of the
route_urlyou store.
One-time use, enforced by the counter#
The monotonic read_ctr only ever increases, and the server rejects any tap whose counter does not advance (reason: non_monotonic). So the tap itself is single-use: a captured URL replayed later fails verification and never redirects.
Note what this does and does not give you. It makes the tap one-time, for free, with no server-side nonce of your own. To make the reward one-time, record (uid, read_ctr) from a server-side GET /verify and refuse a second claim for the same pair — a guessable claim link is still a guessable claim link, and the counter is what stops the guessing being useful.
Per-tag overrides (Enterprise)#
Point a single tag somewhere different from the rest of its system with PATCH /api/tags/{uid}. A per-tag route_url wins over the system route on an authentic tap, so you can route a VIP tag, a specific product batch, or a recalled unit independently — without re-writing hardware.
curl -X PATCH https://edgenfc.com/api/tags/04ee11ff2233aa \
-H "authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{ "route_url": "https://shop.example.com/tag/vip" }'PATCH /api/tags/{uid} is an Enterprise (mgmt_api) feature — non-Enterprise callers get 402 feature_required. The same endpoint can revoke a tag ({ "status": "revoked" }), after which it verifies as not authentic and stops redirecting immediately. Every change is audited in tag_events.
Note
Outbound scan webhooks to your store (fire a Shopify/CRM event on each tap) are a roadmap item, not yet built — see the Custom web app playbook. Today, drive experiences from the redirect plus a server-side
GET /verify.
Verify it worked#
- Tap a genuine tag and confirm the browser lands on your Shopify product page (a
302to yourroute_url). - Tap the same URL twice: the second tap fails with
non_monotonicand does not redirect — proof the counter stops replays. - Hand-edit the
mac(a forgery) and confirm it shows "Not verified" and never reaches your store.
Next steps#
- REST API reference — the full routing, tag, and verify contracts.
- Custom web app — verify taps server-side and read analytics.
- Key management & security — why a forged tap can't be minted.
EdgeNFC