Skip to the content
Menu: An avatar on your website

Put an avatar on your website

By the end of this guide a support avatar sits in the corner of every page of your website. Visitors talk or type to it; it answers from your help centre and, when it can't help, takes their details for a call-back, which reach your team by email and your systems by webhook.

You need: a plan with website assistants (dashboards.serve) and website embedding (embed.website), a key made with the Website assistant preset, and the Node SDK (every step is one API call, so any language works).

1. Check the plan#

website-widget.ts
const me = await zentiq.getMe();
const features = new Set(me.plan.features);
if (!features.has("portals.serve") || !features.has("embed.website")) {
  throw new Error("This plan doesn't include website assistants: an owner can upgrade under Plan & billing.");
}

2. Choose a face and a voice#

The library's people and voices, filtered:

website-widget.ts
const faces = await zentiq.listAvatars({ kind: "builtin", gender: "female" });
const voices = await zentiq.listVoices({ kind: "builtin", gender: "female" });
const avatarId = faces.data[0].id; // av_…
const voiceId = voices.data[0].id; // vo_…

3. Give it your help centre#

A knowledge pack with your help centre in it. Zentiq reads the page in the background; wait until it is ready.

website-widget.ts
const pack = await zentiq.createKnowledgeBase({ name: "Help centre" });
const page = await zentiq.createDocument(pack.id, { url: `${SITE}/help` });
// Reading a document takes a moment: wait until it is ready (a failed one throws, with its errorCode).
await zentiq.waitUntilReady("getDocument", page.id);

4. Make the persona#

languageMode: "follow" answers each visitor in the language they use.

website-widget.ts
const zoe = await zentiq.createPersona({
  name: "Zoe",
  avatarId,
  voiceId, // the voice decides how the avatar speaks, as in the console
  systemPrompt: "You are Zoe, the support assistant for Acme. Answer from the help centre, and offer a call-back from a person when you can't help.",
  greeting: "Hi, I'm Zoe. How can I help?",
  settings: { language: "en", languageMode: "follow" }, // answer visitors in their own language
  knowledge: { baseIds: [pack.id] },
});

5. Set up the widget#

Where it may appear (only those websites can show it), where visitors' requests are emailed, and how it looks:

website-widget.ts
await zentiq.updatePersonaWidget(zoe.id, {
  purpose: "support",
  allowedOrigins: [SITE], // the only websites that may show it
  position: "bottom-right",
  label: "Ask Zoe",
  start: "voice",
  requestEmail: "support@acme.example", // where visitors' requests are emailed
});

The widget is made, and still off. It lives on the persona: its small portal is kept out of GET /v1/portals.

6. Switch it on#

Switching it on needs portals:publish too. It goes live once it knows where requests go and has something to answer from; until then the answer is 422 go_live_refused, with what is missing:

website-widget.ts
try {
  const widget = await zentiq.updatePersonaWidget(zoe.id, { on: true });
  // One line, just before </body>, on every page of the site. It names the widget, never a key.
  console.log(widget.snippet);
} catch (e) {
  if (e instanceof GoLiveRefusedError) {
    for (const p of e.problems) console.log(`${p.field}: ${p.message}`); // what to finish first
  } else throw e;
}

Paste the snippet just before </body> on every page where the avatar should appear (for a floating widget, the default; an inline one's snippet also has the element it fills). It holds the widget's public name only, never a key or a token, and it works only on the websites you listed: elsewhere it shows nothing. Minimised (the default), nothing loads until a visitor clicks the button; the microphone is asked for only when they choose to talk. Install the widget shows where the snippet goes on Shopify, Wix, Squarespace, WordPress and Webflow, how to check that it works, and what a Content Security Policy needs.

7. Hear about requests#

website-widget.ts
const hook = await zentiq.createWebhook({
  url: "https://crm.acme.example/zentiq/webhooks",
  events: ["handoff.requested", "lead.captured", "knowledge.gap"],
  description: "Website requests",
});
console.log("Store this secret to verify deliveries:", hook.secret); // shown once

A hand-off (handoff.requested) arrives with the inbox item's id, the visitor's contact details and their consent, what they need, and when it is due. If they asked first and gave their details later in the conversation, handoff.updated follows with the same reference and the details. Look the item up, or move it on, in the inbox:

website-widget.ts
// In your webhook receiver: a hand-off names its inbox item; its contact details come from the inbox.
async function onHandoff(inboxItemId: string) {
  const item = await zentiq.getInboxItem(inboxItemId);
  console.log(item.contact?.name, item.contact?.phone, item.status);
}

The conversation's summary and the hand-off's reason go only to a webhook made with includeConversation (by a key holding transcripts:read). An under-18 visitor's details are never taken down, so their hand-off carries no contact and no words.

8. Switch it off#

website-widget.ts
await zentiq.updatePersonaWidget(zoe.id, { on: false }); // the widget disappears from every page at once

Show visitors around your website#

With Your website avatar may show visitors around your website switched on in Settings (under Features; Starter and up) and the widget's site guide set up on the avatar's page, the avatar can point at a part of the page, scroll to it, open another of your pages and, when a visitor asks, fill in a field or press a button, right on their screen. In the console you read your own pages, pick the pages it may open and the parts it may use, and say which fields it may fill in and which buttons it may press.

Your page has the last word on what it may touch:

<section data-zentiq-target="pricing-table">...</section>
<input name="company" data-zentiq-fillable>
<button type="button" data-zentiq-clickable>Compare plans</button>
  • data-zentiq-target names a part, so it is found even when the page changes.
  • It fills in only a field marked data-zentiq-fillable, after the visitor confirms what goes in it, and presses only a button or link marked data-zentiq-clickable, whatever the console says.
  • It never points at, fills in or presses a password, code or payment field, and never sends a form that holds one.
  • A button that sends a form shows the visitor a Send button on the page: only their own tap sends it.
  • It opens only the pages you listed, on the same website. A single-page app opens them its own way, and the conversation carries on:
ZentiqWidget.onNavigate(function (path) { return router.push(path); });

Without it, the page opens as a link would, and the panel opens again on the new page. Your page's own ZentiqWidget.onAction handlers never receive the site guide's commands.

Good to know#

  • Every visitor is told at once that they are talking to an AI.
  • Spoken conversations use your minutes, as any website assistant does; typed ones don't.
  • GET /v1/personas/{id}/widget shows the widget's state, the steps left before it can go live, and its snippet.
  • layout (floating or inline), initialState (minimized or expanded), position (one of four corners), label, accent and start change how it shows, at any time.