Connect OneTwoAgent to your app

Add the OneTwoAgent Widget, recognize signed-in customers securely and give the agent live account data from your backend. Your coding agent integrates it.

Signed-in customer identityOneTwoAgent CLI setupLive account data and custom support viewsServer-side Widget identity integrationConversation continuity across logins

Recommended setup: your coding agent does the integration

Account-aware support is production. The fastest way to add the Widget, signed-in customers and live account data to your app is the OneTwoAgent CLI plus the coding agent you already use, such as Claude Code, Cursor or Codex.

  1. 1Run the OneTwoAgent CLI in your app and click Connect in the browser.
  2. 2Tell your coding agent: Integrate OneTwoAgent fully into this app and deploy it. Use the installed OneTwoAgent skill and finish by verifying production.
  3. 3Approve the account data your agent proposes: one click in the browser.
  4. 4Your agent verifies production with onetwoagent doctor; you sign in once when it asks.
Notes
  • Your coding agent reuses your existing login, backend and deployment. OneTwoAgent never gets database access: your backend answers one small, read-only request at a time, only for the signed-in customer who is asking.

1) Run the OneTwoAgent CLI in your app

Open a terminal in the folder of the web app your customers sign in to (in a monorepo, that app’s folder) and run the command below. You can also ask your coding agent to run it. It opens OneTwoAgent in your browser for the business currently selected in your dashboard (Channels → Widget → Install & Connect shows the same command).

What the CLI does

  • Connects the project to your selected OneTwoAgent business.
  • Creates or rotates the Widget Identity Secret and stores it in your server env file (.env.local for Next.js, otherwise .env).
  • Writes non-secret project metadata to .onetwoagent.json.
  • Installs the OneTwoAgent coding-agent skill for Claude Code (.claude/skills) and other agents (.agents/skills).

Node.js requirement

  • The CLI needs Node.js 20 or newer to run. Your application can use any stack: Django, Rails, Laravel, Go, Java, .NET and more.
Run in your app’s folder
npx -y @onetwoagent/cli@latest init --browser

2) Approve the connection in OneTwoAgent

After the CLI starts, your browser opens a OneTwoAgent connection page titled Connect OneTwoAgent CLI. Click Connect CLI. If this business already has a Widget Identity Secret, you may instead see Rotate secret & connect.

When should I rotate the secret?

  • You intentionally want to reconnect the application.
  • The existing credential may have been exposed.
  • The application should stop using the previous server credential.
Notes
  • Rotating the secret invalidates the previous Widget Identity Secret for new identity-token exchanges. After rotation, your application must use the new secret.
  • When the connection succeeds, the browser shows: Connected - you can return to your terminal.

3) Tell your coding agent to integrate and deploy

Open your coding agent in the same project and send this one sentence. You do not need to explain your auth library, database, plans or hosting: the installed skill tells the agent to inspect the repository and adapt everything to what your app already has.

What the coding agent does

  • Runs onetwoagent inspect: it finds the app folder (also in a monorepo), framework, auth library, deployment and what is already integrated, so the agent follows one fixed path instead of exploring.
  • Installs the Widget once, on your public site and inside your signed-in app.
  • Adds one server endpoint that exchanges the signed-in customer for a short-lived OneTwoAgent identity token, with a small signed account snapshot.
  • Calls identify(token) after sign-in, reset() on sign-out, and refreshes when the Widget asks.
  • Adds one read-only account endpoint with the built-in sections your app has (plan, access, usage and credits, jobs, integrations) and 2 to 5 support views such as projects, orders or conversations.
  • Sets the server environment variables and deploys through your normal pipeline when you ask it to.
  • Runs onetwoagent doctor, changes only the parts doctor reports as failing, and reruns it until production verifies.
Caution
  • OneTwoAgent does not replace your authentication system. The integration reuses the login, session and user model your application already has.
Say to your coding agent
Integrate OneTwoAgent fully into this app and deploy it. Use the installed OneTwoAgent skill and finish by verifying production.

4) Approve account data access

When the account endpoint is ready, your coding agent runs the configure command. Your browser shows exactly what OneTwoAgent may read: the endpoint, the account sections and each support view with its description. An owner or admin clicks Approve.

  • The CLI generates a separate read credential in your server env file. It is never printed and never sent to the browser in a URL.
  • Approving again with a changed list replaces the configuration. Nothing is read until a signed-in customer asks a question that needs it.
Run by your coding agent
npx -y @onetwoagent/cli@latest account-lookup configure onetwoagent.account-lookup.json

5) Verify production with doctor

Your coding agent finishes by running doctor, and you can run it any time. It checks the project connection, the installed skill, the server environment (by name only), the Widget and identity wiring, your OneTwoAgent configuration, the Content-Security-Policy of your production pages, and what OneTwoAgent actually observed in production: the Widget loading, signed-in identity, a signed-in conversation and live account reads.

  • Every problem comes with a code and a concrete fix, for example IDENTITY_EXCHANGE_FAILED at accountContext.facts.usage.0.used, ALLOWED_DOMAIN_MISSING or CSP_WIDGET_BLOCKED.
  • Checks that need a person in production, such as signing in once and asking one question, are reported as manual checks instead of a false pass.
  • --json gives coding agents a stable result. Secrets, account data and conversations are never printed.
Run in your app’s folder
npx -y @onetwoagent/cli@latest doctor --url https://app.example.com

Custom support views

Besides the built-in sections, your app can offer up to 8 support views: short, read-only lists named after your own product, so the AI agent can answer questions like “Which of my projects are stuck?” or “Do I have conversations that need a reply?”. Custom support views are available in production.

  • You choose the names and descriptions (for example projects, orders, shipments, deployments, campaigns or conversations). OneTwoAgent only owns the transport, the limits and the authority.
  • Each answer is at most 6 records with a title, a status, an updated time, a short summary and up to 4 simple fields. No HTML, links, secrets or whole database rows.
  • Your server rechecks the customer’s session and permissions on every read. The AI agent can only pick a view you approved; it never chooses the user, workspace or address.
  • For conversation-like data, expose the subject, status and a short safe excerpt, never full message bodies or whole mailboxes.

Choose an account context mode

Signed account snapshots and fresh account lookup are available in production. Pick the smallest mode that answers your customers' questions; each mode adds to the one before it.

  • Identity only: name/email from your authenticated server; no lookup setup required.
  • Identity + signed snapshot: explicitly selected customer-visible facts at observedAt, with a maximum five-minute proof lifetime.
  • Snapshot + fresh lookup: one optional read-only endpoint in your existing backend; the agent requests one allowed section or support view only when a fresh check is needed.

Fresh account lookup: one read-only endpoint

Build it with @onetwoagent/integration (npm) on a JavaScript or TypeScript server, or with the native adapter for Python, PHP, Ruby, Go, Java or .NET that ships in the CLI skill. You connect isSessionLive to your existing live session and workspace membership checks, and readSection/readView to explicit customer-visible projections; the adapter handles the read credential, request validation, the support grant, result limits and fixed errors.

  • Built-in sections: subscription, access, usage, jobs and integrations, plus up to 8 approved support views. The model controls only which section or view to read; user/workspace/business, grant, URL and credentials come from trusted server context.
  • Add optional accountReadGrant { reference, expiresAt } to the existing identity exchange. It is a short scoped reference, not a cookie or session token, and expires no later than the signed snapshot.
  • Configure the exact HTTPS endpoint, sections and views with onetwoagent account-lookup configure, approved by an owner or admin in the browser (or the OWNER/ADMIN account-lookup API). A separate encrypted read credential authorizes the endpoint; the Widget identity secret remains hash-only and cannot administer it.
  • The endpoint rechecks live session/delegation and membership on every call. Logout/removal causes the next lookup to fail even if the signed snapshot is still valid.
  • No unrestricted SQL, write actions, full ORM records, hidden flags, stack traces, DOM access or session replay. Never expose secrets to the model, browser, .onetwoagent.json or logs.
Notes
  • @onetwoagent/integration has no runtime dependencies and offers fetch-API handlers (Next.js App Router, SvelteKit, Nuxt, Remix, Hono) and Node (req, res) handlers (Express, Fastify, Next.js Pages Router), plus supportView/supportViewRecord and the browser lifecycle. Integrations built from the earlier example at /.well-known/agent-skills/onetwoagent-saas/account-lookup-example.mjs keep working unchanged; moving to the package is a one-line import change.
  • Server setup: ONETWOAGENT_ACCOUNT_READ_CREDENTIAL is the separate endpoint secret the CLI generates; set it and ONETWOAGENT_WIDGET_IDENTITY_SECRET on the server that serves the endpoint. Scripts that configure without a browser can use an OWNER/ADMIN session token in ONETWOAGENT_ADMIN_TOKEN. Never print these values or paste them into the descriptor.
Approve in the browser (generates the read credential if missing)
npx -y @onetwoagent/cli@latest account-lookup configure onetwoagent.account-lookup.json
Non-secret descriptor; use the current revision
{
  "expectedRevision": 0,
  "enabled": true,
  "schemaVersion": 1,
  "endpoint": "https://your-app.example/api/onetwoagent/account",
  "sections": [
    "usage",
    "jobs"
  ],
  "views": [
    {
      "id": "projects",
      "description": "Current projects with their status"
    }
  ]
}

Freshness and dialogue limits

  • A lookup never changes the original snapshot or conversation. Fresh successful fields override that snapshot section for this turn. Other snapshot fields remain observations at their original observedAt.
  • Fresh results last at most 30 seconds and never exceed proof/grant expiry. The 3-second total network deadline includes DNS/connect/body; JSON is limited to 8 KiB and 10 objects total, with exact field schemas. HTTPS, pinned global IPs and valid TLS are required; redirects and compressed responses are rejected.
  • Timeout, 5xx or invalid data means current state could not be checked; old snapshot facts cannot masquerade as a successful live check. A 401/403 requires reauthentication and prevents private snapshot/history fallback.
  • Same-snapshot bounded dialogue remains. A clarification consuming a lookup-derived reply inherits its shorter expiry and grant/config provenance. Old dialogue is context, never authority for current account facts. Changed snapshots still start a new AI history boundary.
  • One lookup per customer turn, no automatic HTTP retry or cross-turn result cache. A lost result is uncertain; recovery requires a new customer turn. Existing billing and Pause/Schedule apply; lookup has no separate credit charge.
  • AI receives only permitted projected fields, provenance, the question and bounded dialogue. Apply your application's disclosure/consent policy. Reset clears local presentation but does not instantly revoke historical conversation tokens or cancel a request already in flight.

How signed-in customer identity works

The identity flow is intentionally split between your server and the browser.

Notes
  • Your long-lived Widget Identity Secret stays on your server.
  • The browser receives only a short-lived identity token issued by OneTwoAgent.
  • This means the browser never needs access to your permanent OneTwoAgent server credential.
Identity flow
Your application
      |
      | signed-in user
      | stable ID + email
      v
Your server
      |
      | ONETWOAGENT_WIDGET_IDENTITY_SECRET
      v
OneTwoAgent API
      |
      | short-lived identity token
      v
Browser
      |
      v
window.OneTwoAgent.identify(token)
      |
      v
Customer's OneTwoAgent conversation

Anonymous visitors vs signed-in customers

The Widget can work with both anonymous website visitors and authenticated application users.

Anonymous visitor

  • Can use the Widget normally.
  • Receives a Widget conversation.
  • Is not connected to a known account inside your application.
  • Relies on the Widget's browser session for anonymous continuity.

Signed-in customer

  • Is identified using your application's own stable user ID.
  • Can provide trusted email and optional name from your authenticated server session.
  • Receives a conversation associated with that application identity.
  • Can return later and recover their own conversation.
  • Is isolated from conversations belonging to other signed-in users.
Notes
  • You can install the Widget without enabling logged-in customer identity.
Caution
  • User B must never receive User A's conversation history.
Example
User A logs in
-> conversation A

User A logs out

User B logs in
-> fresh conversation B

User B logs out

User A logs in again
-> conversation A returns

Supported frameworks and authentication systems

The OneTwoAgent signed-in customer integration is framework-agnostic. Your application needs two capabilities: your server can determine which user is currently authenticated, and that user has a stable internal identifier and an email address.

Common application stacks

This is not an exhaustive list.

  • Next.js / React
  • Remix
  • Express
  • Fastify
  • Django
  • Flask
  • FastAPI
  • Ruby on Rails
  • Laravel / PHP
  • Go
  • Java / Spring
  • ASP.NET
  • other applications with a web frontend and server-side authentication

Common authentication systems

The coding agent should detect which authentication system your project already uses. It should not install a second authentication system for OneTwoAgent.

  • Auth.js / NextAuth
  • Clerk
  • Supabase Auth
  • Firebase Auth
  • custom JWT sessions
  • HTTP-only cookie sessions
  • server-side session stores
  • another existing application authentication system

Can I use signed-in identity with a static website?

A purely static frontend cannot safely hold the long-lived Widget Identity Secret. The secret must remain server-side.

  • A static website can still use the normal anonymous OneTwoAgent Widget.
  • To recognize signed-in customers, your application needs a server-side or trusted backend function capable of authenticating the current user, reading their stable ID and email, and calling the OneTwoAgent identity-token API securely.

Files created during setup

The CLI writes two different kinds of project configuration. They have very different security properties.

.onetwoagent.json

This file contains no long-lived secret. It tells the coding agent and integration which OneTwoAgent business, Widget and API endpoint this project belongs to.

businessId

The OneTwoAgent business connected to this project.

publicId

The public Widget identifier used when loading the Widget. This value is safe to use in browser-visible Widget installation code.

apiBaseUrl

The OneTwoAgent API origin used by your server integration.

.onetwoagent.json
{
  "version": 1,
  "businessId": "your-business-id",
  "publicId": "your-widget-public-id",
  "apiBaseUrl": "https://api.onetwoagent.com"
}

Widget Identity Secret

The CLI also stores the value below. This value is secret. It must remain on the server. Treat it like another production API credential.

Never expose it through public environment variables

Do not expose it through your framework's equivalent public/client environment system.

  • NEXT_PUBLIC_*
  • VITE_*
  • PUBLIC_*
Caution
  • Never expose it in browser JavaScript, HTML, localStorage, sessionStorage, browser cookies, URLs, query parameters, logs, analytics events, or client-side error messages.
  • Never commit the Widget Identity Secret to Git.
.env (server only)
ONETWOAGENT_WIDGET_IDENTITY_SECRET=...

Manual integration

The CLI + coding-agent flow is recommended. The manual architecture is the same regardless of framework.

  • Use the manual integration when your development environment cannot run a coding agent.
  • Use it when your team prefers to implement the integration directly.
  • Use it when you need full manual control over the integration.

1) Install the Widget

Add the OneTwoAgent Widget once in your global application shell or shared layout.

Notes
  • YOUR_WIDGET_PUBLIC_ID comes from .onetwoagent.json or Widget Settings.
Caution
  • Do not create a different Widget instance for every user.
  • Do not manually manage OneTwoAgent conversation IDs. The Widget owns its own conversation state.
html
<script
  src="https://widget.onetwoagent.com/widget/v1.js"
  data-id="YOUR_WIDGET_PUBLIC_ID"
  defer>
</script>

Content Security Policy (if your app sends one)

Extend your existing policy; never disable it. With a nonce-based policy, give the Widget's script tag your page nonce: every style, stylesheet link and script the Widget injects reuses it, so no 'unsafe-inline' is needed for the Widget.

Example additions
Content-Security-Policy: script-src 'self' 'nonce-{NONCE}' https://widget.onetwoagent.com;
  style-src 'self' 'nonce-{NONCE}'; connect-src 'self' https://api.onetwoagent.com wss://api.onetwoagent.com https://widget.onetwoagent.com;
  img-src 'self' https://widget.onetwoagent.com https://*.supabase.co data: blob:

2) Create a server identity endpoint

Your application needs a server-only endpoint that reads the currently authenticated user using your existing auth system, returns 401 when no user is authenticated, extracts a stable user ID and email, and exchanges them with OneTwoAgent for a short-lived identity token.

Notes
  • The exact server API syntax depends on your framework. The security model does not.
  • On a JavaScript or TypeScript server, createIdentityWebHandler (or createIdentityHandler) from @onetwoagent/integration/server is this endpoint, including the optional account snapshot and support grant.
Framework-neutral example
const user = await getCurrentAuthenticatedUser()

if (!user) {
  return response(401)
}

const upstream = await fetch(
  'https://api.onetwoagent.com/api/widget/identity/token',
  {
    method: 'POST',
    headers: {
      Authorization:
        `Bearer ${process.env.ONETWOAGENT_WIDGET_IDENTITY_SECRET}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      externalUserId: String(user.id),
      email: user.email,
      name: user.name,
    }),
  }
)

return upstream.json()

externalUserId

externalUserId must be your application's stable internal customer/user identifier.

Good examples

  • database user UUID
  • database user numeric ID
  • stable account ID
  • stable customer ID

Avoid

  • temporary session ID
  • browser-generated random ID
  • access-token ID
  • temporary request ID
Notes
  • If your application already has a stable database user ID, use it.
  • Do not replace an existing stable ID with the email address.

email

Email is required for signed-in customer identity. Read it from the authenticated server-side session or user record.

Caution
  • Do not accept an arbitrary email supplied directly by the browser without authenticating it first.

name

Name is optional. If your authenticated user already has a name, you may send it to OneTwoAgent.

3) Identify the customer in the browser

After your own application confirms that the browser belongs to an authenticated user, request a short-lived identity token from your server endpoint.

Notes
  • Keep identity/account proof in memory only; do not store it in browser storage or logs.
  • The Widget manages its own conversation state.
js
// Use the guarded helper in step 5 for initial login as well.
await refreshWidgetIdentity()

4) Reset identity on logout

When your application logs the customer out, reset the Widget identity.

  • Call reset() synchronously as part of the logout lifecycle.
  • This ensures that the next visitor cannot inherit the previous signed-in customer's Widget identity.
  • The application's own logout should continue normally.
js
invalidateWidgetIdentity() // step 5: invalidates pending fetches and calls reset()

5) Refresh identity when requested

Identity tokens are short-lived. The Widget emits otw:identity:required when it needs a fresh identity token.

Caution
  • Do not create an aggressive retry loop. If refreshing fails, allow the next natural authentication or Widget event to try again.
  • Do not log the application user out simply because OneTwoAgent identity refresh failed.
Listen for the event
let identityEpoch = 0
let pendingIdentity = null

// Call from existing logout/user/workspace-change hooks BEFORE fetching again.
function invalidateWidgetIdentity() {
  identityEpoch += 1
  pendingIdentity = null
  window.OneTwoAgent.reset()
}

function refreshWidgetIdentity() {
  if (!currentUserIsAuthenticated()) return Promise.resolve('signed_out')
  if (pendingIdentity) return pendingIdentity
  const epoch = identityEpoch
  const scope = currentAuthenticatedScopeKey() // from your existing auth state
  const request = (async () => {
    const response = await fetch('/api/onetwoagent/identity', {
      credentials: 'include', cache: 'no-store',
    })
    if (response.status === 401) return 'signed_out'
    if (!response.ok) return
    const { token } = await response.json()
    if (epoch !== identityEpoch || !currentUserIsAuthenticated()
      || scope !== currentAuthenticatedScopeKey()) return
    await window.OneTwoAgent.identify(token)
  })().catch(() => { /* retry only on the next natural trigger */ })
  pendingIdentity = request
  void request.finally(() => {
    if (pendingIdentity === request) pendingIdentity = null
  })
  return request
}
// The Widget asks when it holds a customer's identity and needs a fresh token. If that session ended
// (in another tab, or it expired), put the Widget back to anonymous instead of leaving it waiting.
window.addEventListener('otw:identity:required', () => {
  void refreshWidgetIdentity().then((outcome) => {
    if (outcome === 'signed_out') window.OneTwoAgent.reset()
  })
})

6) Switching accounts without a page reload

Some applications can switch from one user or account to another without fully reloading the page. After the new application session becomes active, identify the new account.

Notes
  • Do not merge the old customer's Widget history into the new customer. OneTwoAgent resolves the appropriate customer conversation from the new identity.
js
invalidateWidgetIdentity() // before the user/workspace switch
// After the new authenticated scope is active:
await refreshWidgetIdentity()

Avoid stale identity after logout

An asynchronous identity request may still be running when a customer logs out. Your integration should make sure an old identity request cannot complete after logout and identify the previous customer again.

Notes
  • Your coding agent should handle this lifecycle when adapting the integration to your framework.
text
identify User A starts
-> User A logs out
-> Widget identity is invalidated/reset
-> old User A request finishes
-> old result must be ignored

Optional signed account snapshot: saas_support_v1

Signed account snapshots are available in production. Extend the existing server identity exchange with a small customer-visible projection. The browser still calls identify(token). This snapshot describes observed state; it is not a live database lookup.

  • Resolve externalUserId and the authorized active workspace from your existing server session. Never accept them, or account facts, directly from the browser request body.
  • Add accountContext with schemaId saas_support_v1, version 1, scope.workspaceId, observedAt, expiresAt and facts. Unknown fields are rejected, including nested fields.
  • Snapshot lifetime is at most 5 minutes from observedAt; observedAt cannot be in the future. The normal identity token lifetime remains 15 minutes. accountContextExpiresAt in the exchange response reports the earlier account deadline.
  • The complete signed snapshot is limited to 2,048 UTF-8 bytes, the identity token to 6,144 bytes and combined identity/conversation proof headers to 8,192 bytes.
  • Optional facts: name, email, plan, accountStatus, subscriptionStatus, role, capabilities, usage, jobs, integrations and reasonCode. Missing facts mean unknown, not zero or false.
  • Labels name/plan/role: 80 characters. Email: 180. capabilities: at most 12 identifiers of 64 characters. usage: at most 4 rows with metric, nonnegative used and nonnegative or null limit. jobs: at most 3 rows with id, type, state and optional reasonCode. integrations: at most 3 rows with kind, state and optional reasonCode.
  • accountStatus: active/trial/restricted/suspended/closed. subscriptionStatus: trialing/active/past_due/cancelled/expired/none. Job state: pending/running/completed/failed/cancelled. Integration state: connected/disconnected/pending/error. reasonCode: at most 64 uppercase letters, digits or underscores, starting with a letter.
Caution
  • A signed token is browser-readable, not encrypted. Include only facts this exact user is already allowed to see. Never include credentials, cookies, access/refresh tokens, stack traces, hidden feature flags or another user's objects.
  • Roles and capabilities describe the account for support answers. They cannot grant execution permissions or change agent instructions.
Inside your EXISTING server identity endpoint
// These two helpers represent your application's existing authorization code.
const user = await getCurrentAuthenticatedUser(request)
if (!user) return new Response(null, { status: 401 })
const workspace = await getAuthorizedActiveWorkspace(request, user.id)
if (!workspace) return new Response(null, { status: 403 })
const observedAt = new Date()
const response = await fetch(apiBaseUrl + '/api/widget/identity/token', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.ONETWOAGENT_WIDGET_IDENTITY_SECRET,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    externalUserId: user.id, name: user.name, email: user.email,
    accountContext: {
      schemaId: 'saas_support_v1', version: 1,
      scope: { workspaceId: workspace.id },
      observedAt: observedAt.toISOString(),
      expiresAt: new Date(observedAt.getTime() + 240_000).toISOString(),
      facts: {
        plan: workspace.customerVisiblePlanLabel,
        accountStatus: 'active', // map explicitly from your own status enum
        usage: [{ metric: 'imports', used: workspace.importsUsed, limit: workspace.importLimit }],
      },
    },
  }),
})
// Return token/expiresIn/expiresAt/accountContextExpiresAt only; Cache-Control: no-store.
// Handle upstream errors without reflecting secrets or raw account data.

Account refresh, workspace switches and history

Request a new snapshot after account changes and when otw:identity:required fires. Coalesce concurrent refreshes, and discard responses for an old user or workspace using the lifecycle example above.

  • Call getWidgetIdentity().invalidate() (from @onetwoagent/integration/browser) in your existing logout or user/workspace-change hook, then getWidgetIdentity().refresh() after the new authenticated session is active. It coalesces refreshes, drops responses for an old user and answers otw:identity:required once per page. Without the package, the lifecycle example above does the same.
  • A fresh proof for the same user and external workspace resumes that account conversation. A different user or workspace resolves a separate conversation; do not select conversation IDs yourself.
  • Account proof stays only in Widget memory and accompanies every account Text/Talk request. It is not written to localStorage, URLs or analytics. Expired or missing proof requires refresh; an older stored snapshot is never substituted.
  • reset() clears local account presentation and signals other tabs for the same Widget to reset. Your application still owns logout/session synchronization and must invalidate its own pending identity fetches in each tab.
  • The conversation history token has a separate, longer lifetime. reset() does not revoke previously issued history tokens on the server. Account proof controls new account AI work and Talk playback; do not describe this as instant history-token revocation.
  • Queued turns retain their original snapshot only until its expiry. Refresh does not retroactively authorize an old queued turn; after a refresh the user may send a new question.
  • Within the same unexpired signed snapshot, the server can include up to 8 recent messages within its existing token budget (default 1,200 estimated tokens), with verified source and publication records. This helps the agent follow previously tried steps; current account facts still come only from the signed snapshot.
  • Re-signing an identical snapshot preserves this bounded dialogue context. Changing any snapshot content or timestamp starts a new AI history boundary; dialogue continuity is not carried across that refresh. Inbox history remains durable. Long-term AI summaries, shared caches and raw replay captures stay disabled for account turns.
  • The snapshot itself needs no database credentials or public tool registration. For current state, use the optional fresh lookup endpoint described above.

Control where the Widget appears

For most applications, install the Widget globally and control visibility from OneTwoAgent. This keeps page-placement decisions in Widget Settings instead of scattering Widget conditions throughout your application code.

  • Open Channels>Widget>Advanced -> Where the Widget appears.
  • The underlying settings are Allowed Pages and Excluded Pages.

Show the Widget everywhere

Turn Page Restrictions off.

Notes
  • The Widget appears on every allowed domain.
text
Page Restrictions: Off

Hide the Widget from login, registration and checkout

Leave Allowed Pages empty. Add the paths below to Excluded Pages.

Notes
  • Result: Widget appears on all pages except these excluded paths.
  • This is a common configuration for authenticated SaaS applications.
Excluded Pages
/login
/register
/checkout
/checkout/*

Show the Widget only inside the authenticated application

Add the paths below to Allowed Pages and leave Excluded Pages empty.

Notes
  • Result: Widget appears only on /dashboard and its child pages.
Allowed Pages
/dashboard
/dashboard/*

Allowed Pages

Allowed Pages define where the Widget may appear.

Notes
  • When Allowed Pages contains values, paths that do not match the list are hidden unless the configuration has another applicable rule.
  • If Allowed Pages is empty, the Widget is allowed on all pages except pages explicitly excluded.
text
/dashboard
/dashboard/*
/products/*

Excluded Pages

Excluded Pages define where the Widget must never appear.

Caution
  • Excluded Pages always win. If a page matches both Allowed Pages and Excluded Pages, the Widget is hidden.
text
/login
/register
/checkout
/admin/*

Wildcard examples

* can match multiple characters.

/products/*

Matches product pages below /products.

/dashboard/*

Matches pages below /dashboard.

/blog/*/comments

Matches nested blog-comment URLs matching that structure.

Allowed Domains

Allowed Domains and Page Restrictions solve different problems.

Allowed Domains

Answers: which websites are trusted to load this Widget? Use Allowed Domains to prevent your Widget installation from being reused on unrelated websites.

  • example.com
  • www.example.com
  • app.example.com

Allowed Pages / Excluded Pages

Answers: where inside an allowed website should the Widget appear?

Notes
  • Example: allowed domain app.example.com, excluded page /login -> the Widget is trusted on app.example.com, but hidden when the visitor opens /login.

Logout and conversation continuity

Signed-in identity connects Widget conversations to the authenticated customer.

Notes
  • This lets the same application account continue its OneTwoAgent conversation across visits without exposing another customer's conversation.
text
User A logs in
-> OneTwoAgent identifies User A
-> User A conversation appears

User A refreshes
-> User A conversation remains

User A logs out
-> Widget identity resets

User B logs in
-> User B gets their own conversation

User B logs out

User A logs in again
-> User A's previous conversation returns

Security model

The most important security boundary is simple.

Server-only

This is a long-lived server credential. Keep it private.

  • ONETWOAGENT_WIDGET_IDENTITY_SECRET

Browser-safe

The public Widget ID is intentionally public. The short-lived identity token is issued by OneTwoAgent for the authenticated user.

  • Widget publicId
  • short-lived OneTwoAgent identity token
Caution
  • Never place ONETWOAGENT_WIDGET_IDENTITY_SECRET in NEXT_PUBLIC_*, VITE_*, PUBLIC_*, browser JavaScript, HTML, localStorage, sessionStorage, browser cookies, URLs, query strings, logs, or client analytics.
  • Your server identity endpoint is the only part of the customer application that needs this secret.

Rotating the Widget Identity Secret

Widget Settings allows you to rotate the identity secret.

  • Rotate when you suspect the secret was exposed.
  • Rotate when you intentionally reconnect the integration.
  • Rotate when you want the previous credential to stop working.
Notes
  • After rotation: the previous server credential is no longer valid for new identity-token exchanges; your application needs the new credential; reconnect the project through the OneTwoAgent CLI or update the server environment securely.
Caution
  • Do not rotate casually on every deployment. Treat rotation as a credential-management action.

Test your integration

Before considering signed-in identity complete, test with at least two real application accounts.

User A

  • Log in as User A
  • Open the Widget
  • Send a message
  • Wait for the reply
  • Refresh the page
  • User A's conversation is still present

Logout and User B

  • Log out User A
  • Log in as User B
  • Open the Widget
  • No User A messages are visible
  • Send a message as User B
  • Refresh the page
  • User B's conversation remains

Return to User A

  • Log out User B
  • Log in again as User A
  • Open the Widget
  • User A's previous conversation returns
  • User B's messages are not present
Notes
  • If all checks pass, signed-in identity and conversation continuity are working correctly.

Troubleshooting

Troubleshooting by symptom.

Widget does not appear

  • The Widget script is installed.
  • The Widget is enabled.
  • The current hostname is allowed by Allowed Domains.
  • Page Restrictions are configured correctly.
  • The current path is not in Excluded Pages.

Widget appears on the login page

  • Add the login path to Excluded Pages, for example /login.
  • For applications with multiple auth pages: /login, /register, /forgot-password, /reset-password/*

window.OneTwoAgent.identify is unavailable

  • Verify widget/v1.js loaded successfully.
  • Verify the Widget script is installed once.
  • Verify your identify code runs after the Widget has initialized.
  • If your framework loads third-party scripts asynchronously, wait until the Widget API is available before calling identify().

Identity endpoint returns 401

  • A 401 normally means your own application does not consider the request authenticated.
  • Check your application's session cookie, authentication middleware, server session helper, login state.
  • Fix the application authentication first.
  • Do not bypass your application's authentication just to make OneTwoAgent identity work.

Identity endpoint cannot obtain a OneTwoAgent token

  • Check ONETWOAGENT_WIDGET_IDENTITY_SECRET exists in the server environment.
  • Check the value belongs to the correct OneTwoAgent business.
  • Check the secret was not rotated without updating the application.
  • Check .onetwoagent.json points to the intended OneTwoAgent business/API.
  • Do not print the secret into logs while debugging.

User B sees User A's conversation

  • Stop testing until logout/account-switch handling is corrected.
  • Verify that window.OneTwoAgent.reset() runs when User A logs out.
  • Make sure an older asynchronous identify() operation cannot finish after logout and identify User A again.
  • Expected behavior is strict separation: User A -> conversation A, User B -> conversation B.

Conversation does not return after signing in again

  • Check that the same application account uses the same stable externalUserId.
  • Do not generate a new external ID for every login session.
  • The stable ID should normally be your application's own database user/account ID.

CLI cannot connect to the intended business

  • Sign in to OneTwoAgent.
  • Switch to the business you want to connect.
  • Run the CLI command again.
  • Approve the connection in that business.
  • The project metadata written by the CLI should represent the intended business and Widget.

Final checklist

Before going live:

  • Widget script installed once
  • Correct OneTwoAgent business connected
  • Allowed Domains configured if needed
  • Page visibility configured
  • ONETWOAGENT_WIDGET_IDENTITY_SECRET is server-only
  • Stable application user ID is used
  • Email comes from authenticated server session
  • identify(token) runs after authentication
  • reset() runs on logout
  • Identity refresh event is handled
  • User A survives refresh
  • User B cannot see User A
  • User A history returns when User A signs in again
Notes
  • Once these checks pass, your application is connected to OneTwoAgent with signed-in customer identity and conversation continuity.

Related guides

Continue with the next setup steps around channels, booking, scheduling, or automation.

Common questions

Does my SaaS need to use Node.js?

No. Your application can use any web stack that provides a server-side authentication boundary. Node.js 20+ is currently required only to run the OneTwoAgent CLI.

Does OneTwoAgent replace my authentication system?

No. OneTwoAgent uses your existing authenticated user. Your login system remains the authority for deciding who the user is.

Can I use the Widget without logged-in identity?

Yes. The normal Widget works for anonymous visitors without signed-in identity. Logged-in identity is an additional integration for applications that want OneTwoAgent conversations connected to their own user accounts.

Is .onetwoagent.json secret?

No. It contains non-secret project metadata such as the OneTwoAgent business ID, Widget public ID and API base URL. The secret is stored separately, server-only, as ONETWOAGENT_WIDGET_IDENTITY_SECRET.

Can the same customer restore their conversation after returning later?

Yes. When your application identifies the same stable customer again, OneTwoAgent resolves that customer's conversation.

Can two application users see the same Widget history?

They should not. Different stable application user IDs are isolated from each other. If User B sees User A's history, review logout/account-switch integration immediately.

Should I install the Widget only on authenticated pages?

Usually, no. A common setup is to install the Widget globally, then use Page Restrictions in OneTwoAgent to control visibility. This supports anonymous visitors on public pages while still recognizing signed-in customers inside the application. If you want the Widget only inside authenticated application pages, configure Allowed Pages accordingly.

What happens when I rotate the identity secret?

The previous long-lived server credential can no longer be used for new identity-token exchanges. Your application's server environment must be updated with the new secret.

Do I need to store OneTwoAgent identity tokens?

Normally, no. Request a short-lived token when the signed-in customer needs to be identified and pass it to window.OneTwoAgent.identify(token). Let the Widget manage its conversation session.

Need help

support@onetwoagent.com