# Verified visitors

> Make sure the person in the chat owns the e-mail before you change an order or an account, with a code by e-mail, signed identify or verified actions.

Updated 2026-10-27

Anyone can type an e-mail address into a chat. Before your team or the AI agent changes an order,
a delivery address or an account, the visitor should prove the address is theirs. Luxivo does this
in the chat itself, on every plan.

## A code by e-mail

1. Someone asks the visitor to verify: an agent with **Ask to verify** in the inbox (the
   **Verification** section next to the conversation), the AI agent by itself when the visitor asks
   about their own order or account, or an AI app with the MCP tool `request_verification`.
2. A card appears in the chat. The visitor sends a 6-digit code to the e-mail they left, or to
   another address.
3. They type the code in the card, or click **Confirm my e-mail** in the code e-mail. The inbox
   shows a green **Verified** badge, and the chat gets a line saying the e-mail was verified.

The rules:

- A code works for 10 minutes and for 5 tries. A new code retires the old one and its link.
- A new code can be sent a minute after the last one, and at most 5 times an hour to one visitor
  and to one address, so nobody can flood someone else's inbox from your chat.
- Only a hash of the code is stored. Neither the code nor its e-mail enters the conversation. If the
  visitor types the code into the message box instead of the card, it goes to the card.
- A verification lasts 24 hours for that visitor (that browser), or until their e-mail changes.
- The code e-mail uses the widget's e-mail look (**E-mail → Look**) and the
  visitor's language.
- Website chats only: conversations that came by e-mail or a contact form have no widget to type a
  code in.

## Orders need a verified e-mail

If your site reports the visitor's cart and orders (the WooCommerce plugin, or
`Luxivo("context", …)`), the AI agent can answer questions about them. Switch on **Orders need a
verified e-mail** under **Chat → your brand → Security** and it sees them only after the visitor
verified. Until then it asks for the code. What the visitor says in the chat ("I already verified")
changes nothing: the status comes from the server.

## Signed identify for logged-in users

When your website already knows who is logged in, there's no need for a code. Create an **identity
secret** under **Chat → your brand → Security**, sign the user's e-mail on your server and pass the
signature to `identify`:

```js
// Server (Node). Keep the secret on the server.
import { createHmac } from "node:crypto";
const signature = createHmac("sha256", process.env.LUXIVO_IDENTITY_SECRET)
  .update(user.email.trim().toLowerCase())
  .digest("hex");
```

```php
// Server (PHP)
$signature = hash_hmac('sha256', strtolower(trim($user->email)), getenv('LUXIVO_IDENTITY_SECRET'));
```

```html
<script>
  Luxivo("identify", { email: "jane@example.com", name: "Jane", signature: "<?= $signature ?>" });
</script>
```

The signature is the hex HMAC-SHA256 of the lowercased e-mail. With a valid one, the visitor counts
as verified (the inbox says "by your website") and stays so while they browse, because every page
view signs again. A missing or wrong signature is ignored: the e-mail is saved but not verified.
**Create a new secret** replaces the old one, and signatures made with it stop counting.

## Verified actions (Pro)

Verified actions let the AI agent and your team do things in your own systems: change a delivery
address, cancel an order that hasn't shipped, resend an invoice. Set them up in **Verified
actions** (`/app/actions`):

- **Name** and **When to use it**: what the AI reads to decide when the action fits.
- **Fields**: what the action needs, for example `order_number` and `address`.
- **Webhook URL**: your endpoint (https).
- **The AI agent may run it**: off means only your team can, from the inbox.

An action runs only for a visitor verified in the last 30 minutes. This is checked by Luxivo, not
left to the AI. The AI confirms the details with the visitor before it runs one, and every run is
an internal note in the conversation and an entry in the audit log.

Luxivo POSTs JSON to your URL:

```json
{
  "type": "action.run",
  "action": { "id": "…", "name": "Change delivery address" },
  "fields": { "order_number": "1042", "address": "Main St 1, 00-001 Warsaw" },
  "visitor": { "id": "…", "email": "jane@example.com", "name": "Jane", "verifiedAt": "2026-10-27T14:02:11.000Z", "method": "code" },
  "conversationId": "…",
  "requestedBy": { "type": "ai" },
  "sentAt": "2026-10-27T14:03:40.000Z"
}
```

`method` is `code`, `link` or `signed`. The request is signed like form webhooks:
`X-Luxivo-Signature: t=<unix seconds>,v1=<hex>`, where `v1` is the HMAC-SHA256 of `<t>.<raw body>`
with the action's signing secret (shown when you edit the action). Check it and reject old
timestamps (see [Contact forms](/docs/forms) for code).

Your endpoint must still check that the order belongs to `visitor.email`. Answer within 10 seconds
with a 2xx status when it's done, and optionally `{ "message": "Done. The new address is on order 1042." }`:
the AI or the agent passes it on. Any other status counts as failed, and the AI hands the
conversation to your team.

## AI apps (MCP)

`request_verification` shows the card in a conversation, `get_conversation` returns
`visitor.verifiedAt` and the latest `verification`, and `list_actions` and `run_action` run verified
actions as the connected user. See [Claude and ChatGPT](/docs/mcp).
