EdgeNFC
Sign in Start free

Custom web app#

For teams building their own registration flow or experience on top of EdgeNFC: verify taps server-side, read analytics, and manage the tag registry — all over plain HTTPS/JSON. No SDK or webhook is required (and, honestly, neither exists yet — see below).

Verify a tap server-side (GET /verify)#

Point your tags' SUN URLs at your own handler, or read the tap params and call GET /verify yourself. It is public, unauthenticated, and rate-limited.

curl "https://edgenfc.com/verify?sys=sys_01H…&uid=04a1b2c3d4e580&ctr=000042&mac=…"

A genuine tap returns 200:

{ "authentic": true, "uid": "04a1b2c3d4e580", "read_ctr": 42,
  "replay": "ok", "first_seen": false, "verified_at": "2026-08-01T12:00:00Z" }

A forged, malformed, or replayed tap also returns 200, with authentic:false and a reason:

{ "authentic": false, "reason": "mac_mismatch" }

reason is one of mac_mismatch | malformed | non_monotonic | unknown_uid.

Important

A forgery is a valid request with a false result: 200 + authentic:false, never a 4xx. Branch on the boolean, not the HTTP status. Use read_ctr (monotonic) and replay to build one-time claims: a replayed URL comes back non_monotonic.

Encrypted-PICC mode (picc_data + cmac)#

GET /verify takes the tap parameters in either of the two URL modes the tag can be configured for — pass through whatever the tag emitted:

ModeQuery parametersUID on the wire
Mirror-plainsys, uid, ctr, macmirrored in plaintext
Encrypted-PICC (recommended default)sys, picc_data, cmacencrypted inside picc_data
GET /verify?sys=<system>&picc_data=<hex>&cmac=<hex>

In encrypted mode the UID and counter travel encrypted; the verifier decrypts them with the tag's diversified meta-read key before checking the MAC and the counter. The response shape is identical — you still get { authentic, uid, read_ctr, replay, reason }, with uid recovered from the decrypted payload. Your application code does not branch on the mode; only the tag's provisioning does.

Do not try to parse picc_data yourself, and do not log it alongside anything that would let you correlate it back to a person — hiding the UID is the entire point of the mode.

Rate limits and errors#

/verify and the /t tap page are public and rate-limited per client and system with a fixed window. Two things to know:

  • Being over budget is reported the same way a forgery is: 200 with authentic:false and reason: "rate_limited" on /verify (and the tap page for /t). Again — branch on the boolean.
  • Budgets are deployment configuration, not part of the contract. Do not build a client that hammers /verify in a loop; verify once per tap, cache your own result, and key it to (uid, read_ctr).

The verify path never returns secret material and compares MACs in constant time, whatever the verdict.

Read analytics (GET /api/analytics, Brand+)#

Aggregate scan data for one system — total scans, the authentic/failed split, a by-country breakdown, and a per-day series. Owner-scoped (404 on a system you don't own) and gated on the Brand plan or higher (402 feature_required).

curl "https://edgenfc.com/api/analytics?sys=sys_01H…" \
  -H "authorization: Bearer $TOKEN"
{ "system_id": "sys_01H…", "window_days": 30, "total": 128, "authentic": 120, "failed": 8,
  "by_country": [ { "country": "US", "count": 90 }, { "country": "GB", "count": 30 } ],
  "by_day": [ { "day": "2026-07-15", "count": 12 } ] }

The response is privacy-preserving by construction: only counts, ISO-2 country codes, and dates — never a UID, IP, or personal field.

Management auth#

Everything under /api/… is authenticated and owner-scoped, in contrast to the public verify surface:

  • Auth: a bearer session token — authorization: Bearer $TOKEN. There is no separate API key today, and the System Master Key is never an API credential: it derives tag keys, it does not authenticate calls. Never put it in a header, a query string, or a client bundle.
  • Owner scoping: a system, tag or org belonging to another account returns 404, not 403 — the resource's existence is hidden. Do not treat 404 as "deleted".
  • Feature gates: entitlement is checked at config time, never on the hot tap path. Below the required plan you get 402 feature_required (or 402 license_required for the perpetual Edge license). Handle these as "upgrade needed", not as a bug.
  • Roles: inside an org (Enterprise), a member without the owner/admin role on a management action gets 403 forbidden.
  • Provisioning sync: the provisioning app authenticates to POST /api/tags/import with a scoped provisioning token — again, not the master key.

Manage tags (management API)#

Drive your registry from your app with the authenticated management routes — all owner-scoped (a resource in another account returns 404):

  • GET /api/tags?sys=… — list/search the registry.
  • POST /api/tags/import — bulk import a provisioning-app registry export (idempotent by uid).
  • GET /api/tags/{uid} — tag detail (key version, last counter, last seen).
  • PATCH /api/tags/{uid} — re-point (route_url), revoke, or re-activate a tag without re-writing hardware (Enterprise mgmt_api; 402 feature_required otherwise).

Management errors use one envelope — { "error": { "code", "message" } } — with code in unauthorized | not_found | rate_limited | validation | feature_required | license_required | quota_exceeded. See the REST API reference for the full contract.

SDK — planned (not yet available)#

There is no published SDK yet. For in-app or edge verification today, embed the Wasm core directly (the same Rust core the hosted gateway and the Android app run), as shown in the DIY Edge Core guide — TypeScript types ship with the WASM package. A packaged JS/Wasm SDK with a stable surface is on the roadmap; until then, integrate over REST or against the Wasm module directly.

Webhooks — roadmap (not yet built)#

Note

Outbound scan webhooks are a roadmap item, not a shipping feature. The design — subscribe to tap events with signed, retried, idempotent delivery to drive dynamic product registration or CRM updates — is specified (spec/17, the G4 milestone) but not yet implemented. Do not build against a webhook endpoint today; none exists.

Until webhooks land, drive on-tap behavior with the GET /verify call above (poll or verify at your own handler) plus custom routing for redirects. This page will document the live webhook contract once G4 ships.

Verify it worked#

  • Call GET /verify with a genuine tap → authentic:true and an advanced read_ctr.
  • Replay the same params → authentic:false, reason: non_monotonic.
  • Call GET /api/analytics?sys=… and confirm the authentic/failed counts move as you test.

Next steps#