Skip to Content
DocumentationWidgetContext & Metadata

The widget can carry four things from your page into the call:

WhatWhere it lands
Context — free textThe agent’s system prompt, as a caller-context block it speaks from
Greeting — one sentenceThe agent’s opening line, replacing its stored greeting for this call
Metadata — key/value factsThe same prompt block, plus the session record, plus cross-session memory
MCP headersEvery MCP and HTTP tool call the agent makes during the session

All four are captured when a call starts. Set them any time before the visitor hits Start Call; changing them mid-call affects the next call, not the one in progress.

Quickest version

<script src="https://api.oshara.ai/widget.js" data-agent="support-bot" data-context="The visitor is on the Pro plan and has an open billing ticket (#4821)." data-greeting="Welcome back! Still about ticket #4821?" data-metadata='{"user_id":"u_123","plan":"pro","renewal":"2026-09-01"}'> </script>

The agent now opens the call already knowing this. Ask it “what’s my plan?” and it answers without a lookup.

Setting context from JavaScript

Most apps only learn who the visitor is after login or a route change. Use the runtime handle for that:

window.__OsharaVoiceWidget.setContext( "Visitor is viewing the Enterprise pricing page. Signed in, on a trial that ends in 3 days." ); window.__OsharaVoiceWidget.setMetadata({ user_id: user.id, email: user.email, plan: user.plan, }); window.__OsharaVoiceWidget.setGreeting( `Welcome back, ${user.firstName}. Want to finish setting up billing?` );

The handle is installed the moment widget.js is parsed — before the widget finishes booting — so you can call it straight after injecting the script without waiting for DOMContentLoaded.

MethodBehaviour
setContext(value)Replaces the context. A string is used as-is; an object is flattened into key: value lines. null clears it.
setGreeting(value)Replaces the agent’s opening line for the next call. null — or anything that isn’t text — restores the character’s stored greeting.
setMetadata(obj, opts?)Merges into existing metadata. Pass { merge: false } to replace, null to clear.
setMcpHeaders(obj, opts?)Merges, same options.
setForwardMetadataToMcp(bool)Turn X-Oshara-Meta-* header forwarding on or off (see below).
destroy()Tear the widget down. The context setters keep working afterwards.

Metadata merges by design: a page can set user_id at boot and add plan after the entitlements call resolves, without restating the first.

Boot attributes

AttributeWindow globalTypeDescription
data-contextVOICE_CONTEXTstring | JSONFree-text context. A JSON object is flattened into key: value lines.
data-greetingVOICE_GREETINGstringThe agent’s opening line for this page. Plain text, not JSON.
data-metadataVOICE_METADATAJSON objectFacts about the visitor.
data-mcp-headersVOICE_MCP_HEADERSJSON objectHeaders for MCP/HTTP tool calls.
data-mcp-forward-metadataVOICE_MCP_FORWARD_METADATAbooleanAlso send each metadata entry as an X-Oshara-Meta-<key> header.

Attributes are applied first, so a later setContext() call always wins.

The opening line

The stored greeting has to work for every visitor. Your page usually knows better:

const w = window.__OsharaVoiceWidget; // Mid-onboarding w.setGreeting("Hi! You're two steps from going live — want to finish now?"); // Third visit to the pricing page w.setGreeting("Back on pricing? I can walk you through the tiers.");
  • One spoken sentence. It is read out loud, so it is collapsed to a single line and capped at 400 characters. Visitor state belongs in setContext(), which the agent draws on for the whole call — not in the greeting, which it says once.
  • It replaces. The character’s stored greeting is not also spoken.
  • It falls back cleanly. Send nothing, an empty string, or null and the visitor hears the character’s own greeting. Same for a value that isn’t text.
  • It is a line, not an instruction. The agent speaks it and then continues under its own system prompt, so a greeting cannot widen what the agent will do. Overriding the prompt itself needs an API key and a backend call — see Starting Sessions.

A greeting that changes per visitor costs a little latency. Greeting audio is cached per exact phrase (per voice and language), and a cache hit lets the agent speak before its TTS model has finished waking up. "Welcome back, Ana" is a fresh phrase every time, so it always misses — after a quiet period the visitor waits out the model’s cold start before hearing anything. A handful of reused variants (“Back on pricing?”) stay warm; a name in every greeting never will.

What the agent does with it

Context and metadata become one block in the agent’s system prompt:

=== What the page already knows about this caller === Visitor is on the Pro plan and has an open billing ticket (#4821). - user_id: u_123 - plan: pro - renewal: 2026-09-01 === End of caller context ===

The agent is instructed to treat it as already established: personalise from it, fill tool arguments from it, never ask for something it already answers, and never read it back as a list. If the visitor contradicts it out loud, the visitor wins.

It also survives a transfer — the context describes the caller, so a handoff target receives it too.

Reserved keys

  • context — set by setContext(); don’t also send it via setMetadata().
  • visitor_id — added automatically (the widget’s persistent per-browser id). It keys cross-session memory when the character has it enabled, and is deliberately kept out of the prompt.

Limits

Values are capped so the bundle stays inside the join token: context 2,000 characters; greeting 400 characters (single line); 20 metadata keys; 256 characters per value; 8 KB of metadata in total. Functions, null, and empty values are dropped; nested objects are JSON-encoded into their value. Anything over a limit is truncated, not rejected — the call still starts.

Injecting metadata into MCP tools

Two ways, and they compose.

Spell the headers out when a tool needs a specific header — an auth token, a tenant selector:

window.__OsharaVoiceWidget.setMcpHeaders({ "Authorization": `Bearer ${shortLivedUserToken}`, "X-Tenant": tenant.id, });

Scope them to one server by nesting under its name:

window.__OsharaVoiceWidget.setMcpHeaders({ "crm-mcp": { "X-Account-Id": account.id }, });

Or forward the metadata you already set, so you don’t author the same values twice:

window.__OsharaVoiceWidget.setMetadata({ user_id: "u_123", plan: "pro" }); window.__OsharaVoiceWidget.setForwardMetadataToMcp(true); // every MCP call now carries: // X-Oshara-Meta-user_id: u_123 // X-Oshara-Meta-plan: pro

The free-text context is never forwarded as a header. Headers you set explicitly always beat a forwarded one of the same name.

Your character’s stored MCP credentials (Notion, Slack, and other connected integrations) are applied after these, so a page cannot swap out the integration token your workspace configured.

These values come from the browser. Anything your page can read, a visitor can read and edit in devtools — including calling setGreeting() from the console. Treat widget-supplied context, greetings and headers as claims, not proof:

  • Send short-lived, user-scoped tokens your MCP server validates itself — never a shared API key.
  • Don’t let a tool authorise an action on X-Oshara-Meta-user_id alone.
  • A tampered greeting changes the agent’s first sentence, not its instructions or its tools. If even the opening line must be yours alone, mint the session from your backend and leave data-greeting unset.
  • When the agent must be certain who it’s talking to, mint the session from your backend instead: the same metadata and mcp_headers fields on POST /api/agents/agent-session/ are then set by code the visitor can’t touch. See Starting Sessions.
Last updated on