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

# Identity Verification

> Pass a server-side HMAC hash to identify authenticated users and skip redundant OTP prompts.

By default, the chat widget treats every session as anonymous. If a user is already logged into your app, you can tell the widget who they are. When a verified identity is passed, the AI agent:

* Skips asking the user to confirm their email address
* Uses the authenticated email automatically when escalating to a human agent
* Has access to the user's identity for the duration of the session

<Warning>
  Never compute the HMAC on the client side. The secret key must stay on your server. Anyone who can read your client-side code or network requests can replay a user identity — the server-side signature is the only thing that prevents this.
</Warning>

***

## How it works

1. Your server computes `HMAC-SHA256(secret, userEmail)` using your widget secret.
2. You pass the email and the hash to the embed script as `data-*` attributes.
3. The widget's iframe URL carries those values as query params.
4. Layerup's server verifies the hash using the same secret before trusting the email.

If the hash doesn't match, the email is silently discarded and the session proceeds as anonymous.

***

## Setup

### 1. Get your widget secret

Open your **Chat Widget** integration in the Layerup dashboard. In the **Identity Verification** section, click **Generate** to create a secret. Copy it and store it as an environment variable on your server.

<Warning>
  Rotate the secret immediately in the dashboard if it is ever exposed. All existing authenticated sessions will become anonymous until users reload.
</Warning>

### 2. Compute the hash on your server

<CodeGroup>
  ```js Node.js theme={null}
  const crypto = require('crypto');

  const hash = crypto
    .createHmac('sha256', process.env.LAYERUP_WIDGET_SECRET)
    .update(userEmail)
    .digest('hex');
  ```

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

  hash = hmac.new(
      os.environ['LAYERUP_WIDGET_SECRET'].encode(),
      userEmail.encode(),
      hashlib.sha256
  ).hexdigest()
  ```

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

  hash = OpenSSL::HMAC.hexdigest(
    'SHA256',
    ENV['LAYERUP_WIDGET_SECRET'],
    user_email
  )
  ```

  ```php PHP theme={null}
  $hash = hash_hmac(
    'sha256',
    $userEmail,
    $_ENV['LAYERUP_WIDGET_SECRET']
  );
  ```
</CodeGroup>

### 3. Pass the values to the embed snippet

Add `data-user-email` and `data-user-hash` attributes to the script tag. These are rendered server-side into your HTML — they must never be hard-coded or sourced from client storage.

```html theme={null}
<!-- Rendered server-side with values from your backend -->
<script
  src="https://us.uselayerup.com/widget-loader.js"
  data-widget-key="YOUR_WIDGET_ID"
  data-user-email="<%= current_user.email %>"
  data-user-hash="<%= layerup_identity_hash(current_user.email) %>">
</script>
```

### 4. Mobile webview

For native apps, append the parameters to the webview URL. Compute the hash server-side and inject it into the URL before opening the webview — do not compute it in the app.

```
https://us.uselayerup.com/widget/YOUR_WIDGET_ID?ue=user%40example.com&uh=COMPUTED_HASH
```

<Tabs>
  <Tab title="iOS (Swift)">
    ```swift theme={null}
    // hash and email come from your API response, not client-side computation
    let urlString = "https://us.uselayerup.com/widget/\(widgetId)?ue=\(encodedEmail)&uh=\(hash)"
    let url = URL(string: urlString)!
    webView.load(URLRequest(url: url))
    ```
  </Tab>

  <Tab title="Android (Kotlin)">
    ```kotlin theme={null}
    // hash and email come from your API response, not client-side computation
    val url = "https://us.uselayerup.com/widget/$widgetId?ue=${encodedEmail}&uh=$hash"
    webView.loadUrl(url)
    ```
  </Tab>
</Tabs>

***

## Verification flow

```mermaid theme={null}
flowchart TB
  SRV["Your server\ncomputes HMAC-SHA256(secret, email)\nrenders HTML with email + hash"] --> LDR["Widget loader\nappends ?ue=email&uh=hash\nto iframe URL"]
  LDR --> REQ["Layerup server\nreceives iframe GET request"]
  REQ --> VFY{"timingSafeEqual\nexpected vs hash"}
  VFY -- match --> AUTH["authenticatedEmail set\nfor session lifetime"]
  VFY -- mismatch --> ANON["Session proceeds\nas anonymous"]
  AUTH --> CHAT["Chat POST\nincludes authenticatedEmail"]
  CHAT --> AGENT["AI agent skips\nemail confirmation\nand OTP prompt"]
  classDef server fill:#fafafa,stroke:#111,stroke-width:1.5px,color:#111;
  classDef check fill:#fff,stroke:#111,color:#111;
  classDef outcome fill:#fff,stroke:#111,color:#111;
  class SRV,REQ server;
  class VFY check;
  class LDR,AUTH,ANON,CHAT,AGENT outcome;
```

*Layerup uses a constant-time comparison (`timingSafeEqual`) to prevent timing attacks. An invalid hash produces no error — the session continues as anonymous.*

***

## What changes for verified users

<CardGroup cols={2}>
  <Card title="Email confirmation skipped" icon="envelope-open">
    The AI agent won't ask the user to spell out or confirm their email address before escalating to a human agent.
  </Card>

  <Card title="Escalation pre-filled" icon="headset">
    When the AI creates a support ticket, it uses the authenticated email as the reply-to address automatically.
  </Card>

  <Card title="OTP not required" icon="shield-check">
    Users who are already authenticated in your app don't need to re-verify their identity through an OTP inside the widget.
  </Card>

  <Card title="Session continuity" icon="arrow-rotate-right">
    The authenticated email is attached to the session for its entire duration, across multiple messages.
  </Card>
</CardGroup>

***

## Security checklist

<Steps>
  <Step title="Keep the secret server-side">
    Store it in an environment variable. Never include it in client JavaScript, mobile app binaries, or version control.
  </Step>

  <Step title="Compute the hash per-request">
    Generate the hash fresh for each page render or API response. Don't cache hashes in the browser or in cookies.
  </Step>

  <Step title="Use HTTPS">
    The widget URL and all chat API calls require HTTPS. Don't serve the embed script over HTTP.
  </Step>

  <Step title="Rotate on exposure">
    If you suspect the secret has been leaked, rotate it immediately in the dashboard. Generate a new secret and redeploy.
  </Step>
</Steps>
