> ## Documentation Index
> Fetch the complete documentation index at: https://docs.endstate.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Host your own verify page

> Run the entire post-tap experience on your own domain: point taps at a page you control, receive the chip_id and e, verify server-side, and render a branded authenticity result.

By default, tapping a chip lands the user on Endstate's hosted verify page. This guide shows how to host that experience yourself — your domain, your design — by combining [tap redirects](/concepts/tap-redirects) with the verify endpoint.

The shape: you configure a redirect to a page you own; Endstate sends each tap there with the `chip_id` and one-time `e`; your page forwards them to your server, which verifies the tap and returns the item. You render the result.

## Prerequisites

* Registered items — a [collection](/concepts/collections), a [unit](/concepts/units) per item, and a paired chip. See [Set up your catalog](/guides/set-up-catalog).
* A secret API key (`end_sk_...`), used **server-side only**.
* A page on your domain to receive taps (the redirect destination).

<Steps>
  <Step title="Point taps at your page">
    Set a `redirect_url` to the page you'll host. Choose the level that matches how broadly it applies — a single unit, a whole collection, or your organization default:

    ```bash theme={null}
    curl -X PATCH https://api2.endstate.io/v1/collections/8e1a7f50-90ab-4cde-b012-3456789abcde \
      -H "Authorization: Bearer end_sk_test_..." \
      -H "Content-Type: application/json" \
      -d '{ "redirect_url": "https://brand.example/verify" }'
    ```

    From now on, a tap on any item covered by that redirect sends the user to your URL with the tap's `chip_id` and `e` appended:

    ```
    https://brand.example/verify?endstate_chip_id=ABCDEF0123&endstate_e=C78566198547116F3A715DC1C62AF96F
    ```

    The most specific redirect wins (unit → collection → org default). See [Tap redirects](/concepts/tap-redirects) for the full resolution order.
  </Step>

  <Step title="Receive the tap on your page">
    Read `endstate_chip_id` and `endstate_e` from the query string and forward them to **your** server. Your browser code must never call Endstate with your secret key.

    ```ts theme={null}
    // On https://brand.example/verify
    const params = new URLSearchParams(window.location.search);
    const chipId = params.get("endstate_chip_id");
    const e = params.get("endstate_e");

    const result = await fetch("/api/verify", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ chipId, e }),
    }).then((r) => r.json());
    ```

    <Warning>
      The `e` value is single-use and short-lived. Forward it to your server and
      verify promptly; do not log it or store it at rest.
    </Warning>
  </Step>

  <Step title="Verify the tap server-side">
    Your server calls `POST /v1/chips/{chip_id}` with the `e`. A successful response **is** the proof of authenticity — it can only be produced by a genuine chip that was just tapped.

    ```ts theme={null}
    // Your /api/verify handler — secret key stays here.
    const res = await fetch(`https://api2.endstate.io/v1/chips/${chipId}`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.ENDSTATE_SECRET_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ e }),
    });

    if (!res.ok) {
      const { error } = await res.json();
      // Branch on error.code: chip.invalid_e_value / chip.already_scanned → not
      // authentic (ask the user to tap again); chip.not_found → bad chip_id.
      throw new Error(error.code);
    }

    const { unit, session_token } = await res.json();
    ```

    See [Verify a unit](/guides/verify-a-unit) for the full response, the session-token model, and error handling.
  </Step>

  <Step title="Render the result">
    Return the `unit` to your page and render a branded authenticity view — its `name`, `attributes`, and serial (`unit.collection.token.serial`). If a follow-on action needs it, pass the `session_token.token` (`end_sess_...`) to the client; it's safe there, unlike your API key.

    A successful verify means authentic. A failure (`chip.invalid_e_value`, `chip.already_scanned`) means the tap couldn't be confirmed — show a "tap again" state rather than asserting the item is fake on a single failed tap.
  </Step>
</Steps>

<Note>
  The session token also authorizes **claiming** the unit — transferring
  ownership to a recipient. You can add that to your page as a next step. See
  [Claim a unit](/guides/claim-a-unit).
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Verify a unit" icon="scan-line" href="/guides/verify-a-unit">
    The full server-side verification flow and its response in depth.
  </Card>

  <Card title="Tap redirects" icon="corner-up-right" href="/concepts/tap-redirects">
    Resolution order and how to set redirects per unit, collection, or org.
  </Card>
</CardGroup>
