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

# Verified visitors

> Sign a user id on your server so the agent knows who it is talking to.

For a widget on pages people sign in to. Anything the browser says about a visitor can be edited by the visitor, so on its own it proves nothing. Instead, your server signs the user's id with a secret that only it and WireDesk hold, and the page passes the signature along. WireDesk recomputes it; a forged id does not match.

A verified visitor's conversation is tied to their email address when you send one, so it joins up with any threads they started by email. Without an email it is tied to the id you signed.

## Set it up

<Steps>
  <Step title="Generate a signing secret">
    On the agent's **Install** page, under **Verified visitors**, choose **Generate a signing secret**. Store it on your server as `WIREDESK_SECRET`. Never put it in the page.
  </Step>

  <Step title="Sign the user id on your server">
    The signature is HMAC-SHA256 of the user id, hex-encoded, under the secret. The id you sign must be exactly the id you send.

    <CodeGroup>
      ```js Node.js theme={null}
      import { createHmac } from "node:crypto";

      const hash = createHmac("sha256", process.env.WIREDESK_SECRET)
        .update(String(user.id))
        .digest("hex");
      ```

      ```python Python theme={null}
      import hmac, hashlib, os

      digest = hmac.new(
          os.environ["WIREDESK_SECRET"].encode(),
          str(user.id).encode(),
          hashlib.sha256,
      ).hexdigest()
      ```

      ```ruby Ruby theme={null}
      require "openssl"

      hash = OpenSSL::HMAC.hexdigest(
        "SHA256", ENV.fetch("WIREDESK_SECRET"), user.id.to_s
      )
      ```

      ```php PHP theme={null}
      <?php
      $hash = hash_hmac('sha256', (string) $user->id, getenv('WIREDESK_SECRET'));
      ```
    </CodeGroup>
  </Step>

  <Step title="Pass it to the page">
    Set `window.wiredeskSettings` before the widget script loads:

    ```html theme={null}
    <script>
      window.wiredeskSettings = {
        user: {
          id: "4812",       // the value you signed
          hash: "…",        // the digest your server produced
          name: "Jane Doe",
          email: "jane@acme.com"
        }
      };
    </script>
    <script src="https://wiredesk.ai/widget.js" data-widget="wk_..." async></script>
    ```

    In a single-page app where the visitor signs in after the page loads, call [`identify`](/docs/widget/javascript-api) instead:

    ```js theme={null}
    wiredesk("identify", { id: "4812", hash: "…", name: "Jane Doe", email: "jane@acme.com" });
    ```

    When they sign out, call `wiredesk("reset")` so the next person at that browser does not see their conversation. It drops the identity and clears the conversation stored in the browser, even if you redirect straight afterwards. See [signing out](/docs/widget/javascript-api#signing-out).
  </Step>
</Steps>

## Require it

**Require verification** refuses anonymous visitors outright. Only turn it on for a widget that appears exclusively on signed-in pages — on a public page it stops the chat working.

## Rotating the secret

Rotating takes effect immediately. Deploy the new secret to your server first, then rotate, or signed-in visitors fail verification in between.

## Where the signature travels

The widget hands the identity to the conversation frame in the URL fragment, never the query string, and the frame sends it in a request header. It never appears in an access log or a `Referer`. See the [security model](/docs/developers/security-model) for how the server checks it and who owns a verified conversation.

## Related

* [JavaScript API](/docs/widget/javascript-api)
* [Widget settings](/docs/developers/widget-settings)
* [Security model](/docs/developers/security-model)
* [Allowed domains](/docs/widget/allowed-domains)
