Persona

Embed the chat on your website or app

The widget puts your persona on any web page, as a chat bubble or inside an element you choose. It loads the chat in an iframe from Persona (or your custom domain), so your page never handles chat data.

Before you start

In the dashboard, open Embed and API:

  1. Under Allowed sites, add every origin that will show the chat, for example https://yourbrand.com and https://app.yourbrand.com. The chat refuses to load or start sessions anywhere else.
  2. Create a key:
    • Server key (sk_live_…) if your users sign in to your site or app.
    • Website key (pk_live_…) if you want anonymous visitors to chat. Also switch on Let anonymous visitors chat.

Your client ID (cli_… or biz_…) is shown on the same page.

Option A: signed-in users (recommended)

Your users already have accounts with you. Your server tells Persona who they are and gets back a short-lived chat session; the browser only ever sees that session token.

Browser (your page)         Your server                    Persona
      |  getSession() ----->  |                              |
      |                       |  POST /api/v1/sessions ----> |  verifies sk_live_ key
      |                       |  <---- { session.token } ----|  creates cs_ token (1h)
      |  <---- { token } -----|                              |
      |  postMessage(token) to the chat frame ------------> |  chat calls use the token

1. Add an endpoint on your server that requires your normal login and returns a session token. Node/Express:

app.post("/api/persona-session", requireLogin, async (req, res) => {
  const r = await fetch("https://persona.ignited.tools/api/v1/sessions", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.PERSONA_SECRET_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      subclient: { id: req.user.id, name: req.user.name, email: req.user.email },
      expires_in: 3600,
    }),
  });
  if (!r.ok) return res.status(502).json({ error: "chat unavailable" });
  const { session } = await r.json();
  res.json({ token: session.token });
});

Next.js route handler:

// app/api/persona-session/route.ts
import { auth } from "@/auth"; // your auth

export async function POST() {
  const user = await auth();
  if (!user) return new Response("Unauthorized", { status: 401 });
  const r = await fetch("https://persona.ignited.tools/api/v1/sessions", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.PERSONA_SECRET_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ subclient: { id: user.id, name: user.name, email: user.email } }),
    cache: "no-store",
  });
  const { session } = await r.json();
  return Response.json({ token: session.token });
}

Python (FastAPI):

@app.post("/api/persona-session")
async def persona_session(user = Depends(current_user)):
    async with httpx.AsyncClient() as http:
        r = await http.post(
            "https://persona.ignited.tools/api/v1/sessions",
            headers={"Authorization": f"Bearer {os.environ['PERSONA_SECRET_KEY']}"},
            json={"subclient": {"id": str(user.id), "name": user.name, "email": user.email}},
        )
    r.raise_for_status()
    return {"token": r.json()["session"]["token"]}

2. Add the widget to your page:

<script>window.Persona=window.Persona||{q:[]};["init","open","close","destroy"].forEach(function(m){window.Persona[m]=window.Persona[m]||function(){window.Persona.q.push([m].concat([].slice.call(arguments)))}});</script>
<script src="https://persona.ignited.tools/widget.js" async></script>
<script>
  Persona.init({
    clientId: "cli_YOUR_ID",
    getSession: async () => {
      const res = await fetch("/api/persona-session", { method: "POST", credentials: "include" });
      return (await res.json()).token;
    },
  });
</script>

The widget calls getSession when the chat opens and again whenever a session expires, so users never see an expired chat.

Option B: anonymous visitors

For a public site where visitors don't sign in. Use your website key; it's safe in page source because it only works from your allowed sites and can only start anonymous chats.

<script>window.Persona=window.Persona||{q:[]};["init","open","close","destroy"].forEach(function(m){window.Persona[m]=window.Persona[m]||function(){window.Persona.q.push([m].concat([].slice.call(arguments)))}});</script>
<script src="https://persona.ignited.tools/widget.js" async></script>
<script>
  Persona.init({ clientId: "cli_YOUR_ID", publishableKey: "pk_live_YOUR_KEY" });
</script>

Each browser gets a random visitor id (kept in that browser's storage), so a returning visitor continues as the same subclient. Anonymous sessions last 2 hours and are rate limited (see Errors and limits).

Widget options

OptionTypeDefault
clientIdstringrequiredYour client ID
getSession() => Promise<string | {token}>Signed-in mode. Return a cs_… token from your server
publishableKeystringAnonymous mode. Your pk_live_… key
mode"bubble" | "inline""bubble"Floating button, or fill an element
targetselector or elementInline mode: where to render. Give it a height
position"right" | "left""right"Bubble position
colorhex#111111Bubble button color
labelstring"Open chat"Bubble accessible label
openbooleanfalseOpen the bubble on load
onError(error) => voidconsole.errorSession errors

Methods: Persona.open(), Persona.close(), Persona.destroy(), and Persona.init(options) again to switch users (for example after your user signs in or out).

Sign-out

When a user signs out of your app, call Persona.destroy() in the page and, from your server, end their chat sessions:

curl -X DELETE https://persona.ignited.tools/api/v1/sessions \
  -H "Authorization: Bearer $PERSONA_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"subclient_id":"user_123"}'

Mobile apps

Use a hosted link in a web view: your server creates a session and opens session.url (see Hosted links). The token rides in the URL fragment, which is never sent to a server.

Custom domain

If you've connected a custom domain, load https://chat.yourbrand.com/widget.js and call https://chat.yourbrand.com/api/v1/... instead. The widget always talks to the domain it was loaded from.