# Sessions and channels

## On the web

Render the token onto the widget's script tag. Signed-out visitors simply get
no token, and the agent answers with its public tools.

```erb
<script src="https://cdn.vatio.ai/v1/widget.js"
        data-workspace="acme"
        data-token="vatpub_..."
        <%= "data-visitor-token=\"#{visitor_jwt}\"".html_safe if current_user %>></script>
```

To sign someone in without reloading the page:

```js
window.VatioWidget.identify(newToken);   // null to sign out
```

## Signing in mid-conversation

A visitor asks something, the agent tells them to sign in, they do, and they
come back. **The conversation survives.** Whether they signed in through
`identify()` or by navigating away and reloading the page with a token, the
chat they already had is carried over and now has a name on it — so the agent
can answer the question that prompted the login without them retyping it.

| Before | After | What happens |
|---|---|---|
| anonymous | signed in | Same conversation, now identified |
| user A | user A, newer token | Same conversation, token refreshed |
| user A | user B | **New conversation** |
| signed in | signed out | New conversation |

The last two are the shared-laptop case: a conversation that belonged to one
`sub` is never handed to another, so signing in as someone else looks exactly
like arriving for the first time. Vatio enforces that server-side — the page
cannot opt out of it.

Refreshing is also how a web session stays alive: render a fresh token on each
page load and a long-running conversation keeps working past the first token's
`exp`.

## On WhatsApp and Instagram {#on-whatsapp}

A WhatsApp or Instagram chat has no page to render a token onto, so the
contact is sent to one. Add `sign_in_url` to `auth:`:

```yaml
auth:
  public_key: identity.pub
  sign_in_url: https://acme.com/vatio/sign-in
```

When a private tool runs for a contact who is not signed in, Vatio sends them a
message with a **Sign in** button instead. That message is the agent's whole
reply for the turn, and the inbox shows it with the button under it:

1. The button opens `sign_in_url?code=...`. The code works once, for 10 minutes.
   If they write again without having signed in, the next private tool sends
   a new button.
2. Your page signs them in the way your site already does. If they are already
   signed in on that phone, there is nothing to type.
3. It redirects to `https://vatio.ai/connect/<code>?token=<jwt>`, with the same
   JWT you put on the widget.
4. Vatio verifies it, and the conversation picks up where it left off: the
   agent runs the tool it was waiting on.

With [vatio-sdk](https://vatio.ai/docs/sdk/ruby) the page is one
redirect:

```ruby
redirect_to Vatio::Identity.sign_in_redirect_url(
  code: params[:code],
  subject: current_user.id,
  claims: { name: current_user.name, phone_number: current_user.phone }
), allow_other_host: true
```

Without it, sign the JWT as you do for the web and redirect to
`https://vatio.ai/connect/${code}?token=${jwt}`. Always to that host, never to
one taken from the request.

**That redirect has to be a full page load.** When the contact was not signed
in, your login sends them back to this page and this page on to vatio.ai. If
your login form submits with `fetch` (Turbo, the Rails default, does), the
browser cannot follow a redirect to another site from it: the login page just
sits there and the conversation never hears about it. In Rails, answer Turbo's
request with a reload ([vatio-sdk](https://vatio.ai/docs/sdk/ruby#sign-in-from-whatsapp-and-instagram)
shows how); in a single-page app, send the browser to this page with
`window.location.assign`, not a router push.

The token stays with the contact, so later conversations start signed in until
its `exp`, and then the button goes out again. Give it a longer `exp` than a web
token (the gem uses a week): the web gets a fresh one on every page load,
WhatsApp only when the contact signs in.

**Confirming the number.** A link can be forwarded: someone could ask for a
button on their own WhatsApp and send it to one of your users, whose account
would then answer on the wrong phone. So Vatio shows what is being connected
(`•••• 5678` on WhatsApp, `@handle` on Instagram) and asks the person to
confirm. On WhatsApp, put a `phone_number` claim in the token and, when it
matches the WhatsApp number, that step is skipped. Instagram has no number to
match, so there it is always asked.

Your API sees nothing new: a private tool sends the same `$auth.token`, and you
verify it the same way.
