Skip to main content
A tap proves someone is holding a specific physical item. verify() records that tap and hands back a session scoped to it: every call the session makes carries the session credential, so the session token never has to be threaded through your own code.

Verify a tap

verify() takes the chipId and e from the tap and returns the session:
It never takes a URL. To get a tap out of one, parse it first with tryParseTapUrl, which returns null rather than throwing:
That handles both shapes a tap arrives in: an Endstate tap URL (/verify/{chip_id}?e= or /u/{chip_id}?e=), and a tap redirect destination on your own domain, where the values arrive as endstate_chip_id and endstate_e query parameters instead.
Keeping the parse separate is deliberate. “Is there a tap on this page?” is an offline question whose answer is often no - a direct visit, or a reload after the query was stripped - so it belongs in a branch rather than a try around a network call. See Host your own verify page for the full flow.
The e value is single-use. Never cache one and never replay one yourself. verify() derives its idempotency key from the tap, so a page refresh replays the original response instead of spending the credential and reporting a false “already scanned”.

Act on the session

Reading the unit is the start; the tap’s real payoff is acting on it. A verified session authorizes claiming the item for a customer and transferring it between owners. The unit comes from the session that the tap opened, so you supply only the recipient’s wallet address, and the session token authorizes the write.
See Claim ownership and Transfer ownership for the worked browser walkthrough, including claim’s two execution modes. session.claims.create and session.transfers.create are the reference for the request body and error codes. The session exposes exactly what a tap authorizes:

Sessions cannot be refreshed

A new session needs a new tap. There is no refresh, by design - the session’s authority comes from someone having physically held the item.
isExpired() is a local clock check, for interface copy only. The SDK never gates a request on it: a device clock is not authoritative. The definitive answer is a 401 session_token.invalid_or_expired from the API.

Adopting a session you already hold

If your server issued the session, or you kept the token across a page reload, adopt it rather than re-verifying:

Where the tap comes from

In a browser, core can capture the tap for you through a registered tap source, or take the output of @endstate-sdk/reader directly - see Browser. On a server, you already have chipId and e from whatever forwarded them to you.