Skip to main content
A customer taps your product’s chip and lands on your page, or scans it in-page. From there you confirm the item is genuine, show them what they tapped, provision a wallet for them, and let them claim ownership - all from a page that carries only a publishable key. No backend is involved in the tap, the verify, or the claim. @endstate-sdk/web is the browser half of that flow. It captures the tap from a redirect or Web NFC, hands it to @endstate-sdk/core to open a session, and provisions the customer’s wallet. Key material never enters your page, and this version exposes no signing surface. The whole flow is one line: tap -> verify -> read the item -> provision a wallet -> claim. In React it is one provider and three hooks; the vanilla client runs the same steps by hand. This quickstart leads with React and shows the vanilla path alongside it.

Install

@endstate-sdk/core is a peer dependency - it is the API client the tap sources and hooks plug into. react is an optional peer (^19), needed only for the React entrypoint at @endstate-sdk/web/react; the vanilla client at @endstate-sdk/web needs neither React nor any runtime dependency. Both Endstate packages are ESM-only.

Prerequisites

  • A publishable key (end_pk_...). It is safe in browser source; a secret key is not, and is rejected here by type and at runtime. Credentials covers the difference.
  • Your page’s origin on your organization’s allowed-origins list, for both the API and the wallet frame. Until it is listed the browser blocks the request before it reaches Endstate.
  • A way to mint an identity token for your signed-in user - a fresh, single-use token your own backend signs. It is yours to build, not part of the SDK - see the identity credential and Bring your own auth.

The golden path

One provider, three hooks. <EndstateProvider> wraps your app - it builds the browser client, provisions the wallet, and verifies the landing tap on mount - and a child component reads the verified item, resolves the wallet address, and claims in a single call. The vanilla client in the second tab runs the same steps by hand.
Walking the React flow:
  1. Wrap once with <EndstateProvider>. It builds the browser client, provisions the wallet from your getIdentityToken callback, and - because autoVerify is on by default - captures the landing tap and verifies it on mount. wallet.enabled gates provisioning on whether a customer is signed in; while it is false the wallet stays unconfigured.
  2. Read the verified item with useSession(). Its status moves idle -> capturing -> verifying -> verified (or error), and item is the verified unit ({ id, external_id, name, attributes, collection }) once status is verified. It is read straight off the session with no extra request.
  3. Resolve the wallet with useWallet(). account is a WalletAccount ({ address }) once the wallet reports ready; its status runs unconfigured -> idle -> provisioning -> ready. Customers who bring no address of their own get one this way.
  4. Claim with useClaim(). claim() with no arguments hands the item to the wallet address, so to defaults to the address the provider provisioned. Its status drives the button: claiming then settling, landing on claimed, expired, or failed. claim() defaults to Endstate submitting the write for you; the alternate client_broadcast execution mode returns a payload for your own signer instead.
The vanilla tab runs the same steps by hand: createEndstateClient(...) builds the client, captureTap() reads the tap inside a user gesture, client.verify(tap) opens the session, session.item is the verified unit, wallet.ready() resolves the address, and session.claims.create({ to }) claims it. verify derives its idempotency key from the tap, so a page reload replays the original response instead of spending the single-use credential.
The provider provisions the wallet as it mounts, in parallel with capturing and verifying the tap, so the address is usually ready by the time a customer taps Claim. Only the claim waits on the wallet; capture, verify, and read do not touch it. In the vanilla flow this is why createWallet runs at page load and only wallet.ready() blocks the claim.

Capturing more than the landing tap

The golden path reads the tap that opened the page. To let a customer already on your page scan another item in-page with Web NFC, useTap() exposes scan(), its status, and the Web NFC permission state; the vanilla client does the same through captureTap(). Either way, see Tap capture for the source priority model, the user-gesture rule that gates in-page scanning, and how to present each Web NFC permission state.

Next steps

Tap capture

In-page scanning, the source priority model, and Web NFC permissions.

Wallet

The createWallet lifecycle, the identity credential, and error handling.

session.claims.create

The claim request body, execution modes, and error codes.

Bring your own auth

Issue an identity token for your signed-in user from your own login.