Make It Yours — Settings, Voice & Web Chat
Out of the box your assistant is capable but generic. This chapter is where it becomes yours: its name, its greeting, how it sounds on the phone, how the chat widget looks on your website, which languages it opens in, and what it shows visitors. Everything here is configuration you set once and refine as you learn what your callers need.
All of these are app attributes set via
PATCH /api/v1/apps/{appId} unless a different endpoint is
shown. Remember: attribute changes are drafts until you take the
assistant live (web-chat settings written via the dedicated endpoint
below are the exception — they take effect immediately).
Identity & behaviour:
assistantName— display name in the chat UIwelcomeMessage— initial greetingvoiceChatPrompt— system instructions for conversations. Either a string (single prompt) or an object{"default": "...", "demo": "..."}for multi-mode prompts (selected via?v=modeon the voice link)voiceChatTools— JSON array of tool definitions the assistant can callvoice— voice option (case-insensitive). Valid:alloy(default),ash,ballad,coral,echo,fable,nova,onyx,sage,shimmer,verse
Appearance:
primaryColor,secondaryColor,tertiaryColor,textColor— hex codes. Legacy: for the web-chat widget these are fallbacks only — style the widget viawebchat.branding(below), where one brand colour derives the whole palette.font— font family. Legacy fallback for the widget; preferwebchat.branding.fontFamily. Supported webchat fonts:Manrope,Inter,Roboto,Open Sans,Lato,Montserrat,Poppins,Nunito,Source Sans 3,Work Sans,DM Sans,Karla,Merriweather,Playfair Display. Setwebchat.branding.fontUrltohttps://<widget-host>/widget/fonts/<slug>.woff2(slug = the name lower-cased, spaces → hyphens) so the widget loads the face on any host page.assistantAvatar,thumbnailImage— image objects{s3Key, url}embedAppTitle— title in iframe-embed mode
Billing:
accountId— if set, the app draws from an account credit pool. (Also returned/accepted as the legacy aliasmerchantId— see the naming-transition note under For Partners.)billingMode—"app"(default) or"merchant"(pool-funded; see Billing & Credits)pricePerMinute,allowanceMinutes,overageRatePence— per-app billing overrideslowBalanceThreshold— alert threshold in seconds (default 600)
Voice tuning (see the voice attribute table below):
voiceEngine,turnDetectionType,vadEagernessPhase1,vadEagernessPhase2,vadThreshold,vadPrefixPaddingMs,vadSilenceDurationMs,noiseReduction,transcriptionModel,transcriptionLanguage,maxOutputTokens,pipelineConfig
These control how the assistant listens, transcribes, thinks and speaks on voice calls. The defaults suit most businesses; tune them when you have a specific problem (an assistant that interrupts, a noisy call environment, a non-English caller base).
| Attribute | Type | Notes |
|---|---|---|
voiceEngine |
string | "openai" (default), "livekit" |
turnDetectionType |
string | "semantic_vad" (default), "server_vad",
"disabled" |
vadEagernessPhase1 |
string | low / medium (default) / high
/ auto — speech-end detection during greeting |
vadEagernessPhase2 |
string | low / medium / high (default)
/ auto — during conversation |
vadThreshold |
number | 0.0-1.0, server_vad only (default 0.5) |
vadPrefixPaddingMs |
number | 0-2000, server_vad only (default 300) |
vadSilenceDurationMs |
number | 200-2000, server_vad only (default 500) |
noiseReduction |
string | near_field / far_field (default) /
disabled |
transcriptionModel |
string | gpt-4o-transcribe / gpt-4o-mini-transcribe
(default) / whisper-1 |
transcriptionLanguage |
string | ISO 639-1 (e.g. en, es, fr).
Default en |
maxOutputTokens |
number | 256-4096 or -1 for unlimited (default 4096) |
voiceDisplayName |
string | Spoken voice label, also used in voice greetings |
isPreventKBAccessForVoiceChat |
string ("true"/"false") | Default false |
Two ways to set voice configuration: field-by-field via
PATCH /apps/{id} (useful for small tweaks), or atomically
as a single pipelineConfig blob — one write, internally
consistent. Atomic is preferred when you're setting several values that
belong together.
Requires edit_settings.
This is a draft change — take the assistant live to apply it.
curl -X POST https://api.everycallhandled.com/api/v1/apps/$APP_ID/voice/pipeline-config \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"pipelineConfig": {
"mode": "modular",
"stt": { "provider": "openai" },
"llm": { "provider": "openai", "model": "gpt-4o" },
"tts": {
"provider": "cartesia",
"voice": "794f9389-aac1-45b6-b726-9d9369183238",
"options": { "locale": "en-GB", "gender": "female", "speed": 1.0 }
},
"vad": { "type": "server_vad", "threshold": 0.5, "prefix_padding_ms": 200 },
"multilingual": true,
"noise_filter": "noizeless"
}
}'Requires edit_settings. Returns the merged voice
configuration (the pipelineConfig blob + individual fields
+ platform defaults) — what the assistant will actually use, after all
layers resolve.
curl 'https://api.everycallhandled.com/api/v1/apps/{appId}/voice/config' \
-H 'Authorization: Bearer YOUR_TOKEN'The web-chat widget is styled from a single
webchat.branding object, written via the web-chat endpoint
below. Set primaryColor and the platform derives the full
widget palette (header, bubbles, chat button, contrast text). Optional
hex overrides, each replacing its derived value when set and reverting
to derived when empty: headerBgColor,
headerTextColor, assistantBubbleColor,
assistantTextColor, userBubbleColor,
userTextColor, launcherColor,
launcherIconColor. Plus surface
(light|dark|auto),
radius (0–24), position
(bottom-right|bottom-left),
logoUrl (https), launcherIcon
(chat|logo|https URL),
fontFamily/fontUrl (see the font list
above).
Beyond branding, the widget reads a small set of conversational keys
from the same webchat config object. All are written
with:
Requires edit_settings. The body is
{ "webchat": { ... } } (a bare { ... } object
is also accepted). The write validates then merges:
keys you omit are left untouched, and sibling config (branding,
workflows, the tools array) is preserved — toggling one key never wipes
the others. GET /api/v1/apps/{appId}/webchat returns the
normalised, stored shape. Changes take effect on the live widget
immediately.
quickActions — up to 4
opening buttons shown on a fresh chat. Array of
{ "label", "message" }; tapping one sends
message as the visitor's first turn. label is
capped at 32 chars, message at 200; entries missing either
field are dropped, and the list is capped at 4.
startLanguage — the language the
assistant opens in. Values:
""(empty) — English (the default)."auto"— follow the visitor's browser / host-page language.- a language name (e.g.
"Spanish") or ISO code (e.g."es") — open in that language.
The 12 supported non-English languages are: French, Spanish,
German, Italian, Portuguese, Polish, Romanian, Arabic, Turkish, Urdu,
Hindi, Mandarin Chinese. An unrecognised value resolves to
English. The embed snippet can override the app setting per-page with
the data-ghost-chat-lang attribute (same
auto / name / code semantics); the embed attribute wins
over webchat.startLanguage.
Opening with an intent — the embed also accepts
data-ghost-chat-intent: a message
auto-sent as the visitor's first turn when the chat opens on a fresh
thread (e.g. a "Book now" button whose embed carries
data-ghost-chat-intent="I'd like to book an appointment"
opens the chat already in the booking flow). Resumed conversations and
threads with any prior visitor turn ignore it.
welcomeMessages — an object mapping
language to welcome-bubble text (up to 12 languages; values capped at
500 chars). Resolution rule for the static opening bubble:
- Default language (English / empty start language) — the app's standard welcome message.
- Non-default language with a stored translation here — that translation.
- Non-default language with no translation — no static bubble; the assistant's first turn greets the visitor in-language instead.
emergencySignpost — plain text (up to
200 chars), e.g. "call 999 or NHS 111". Used by urgent
escalation: when the office is closed and the visitor raises a genuine
emergency, the assistant advises this pointer. Omit it and no emergency
advice is appended.
curl -X PUT 'https://api.everycallhandled.com/api/v1/apps/{appId}/webchat' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"webchat":{"quickActions":[{"label":"Book an appointment","message":"I would like to book an appointment"}]}}'{
"webchat": {
"quickActions": [
{ "label": "Book an appointment", "message": "I'd like to book an appointment" },
{ "label": "Opening hours", "message": "What are your opening hours?" }
]
}
}<script src="https://<widget-host>/widget/embed-div.js"
data-config-id="YOUR_APP_ID"
data-ghost-chat-lang="auto"></script>{
"webchat": {
"startLanguage": "auto",
"welcomeMessages": {
"Spanish": "¡Hola! ¿En qué puedo ayudarle hoy?",
"French": "Bonjour ! Comment puis-je vous aider ?"
}
}
}{ "webchat": { "emergencySignpost": "For a genuine emergency, call 999." } }- Assistant-ended chats — when a conversation has naturally concluded (the visitor said goodbye, confirmed they're done, or asked to end), the assistant ends the chat itself. This sends the conversation summary to your team immediately (summary email + PDF transcript to the app's recipients, the same wrap-up voice calls get) and puts the widget into its "chat ended" state, from which the visitor can start a new chat.
- Visitor-ended chats — the visitor can end at any time via the widget's End chat control (with a confirm step). This runs the same wrap-up immediately.
- Idle chats — chats left inactive are wrapped up automatically (a sweep runs every ~10 minutes and closes chats idle past ~15 minutes), so a visitor who simply closes the tab still reaches your team.
- Localised chrome — the widget's own fixed strings (End chat, "This chat has ended…", the confirm prompts, Start a new chat) render in the resolved start language, matching the assistant's conversational language.
Chat doesn't have to be all text. Two mechanisms turn data into native chat UI (cards, tables, timelines, location cards) — in both, the platform owns layout and enforces limits; payloads that don't validate degrade to plain text, never an error.
Declare a render kind on any saved action (tools editor → "Show the
reply as", or configuration.render in the stored tool):
Kinds: cards | comparison |
timeline | table | metrics |
location. The tool's HTTP response body must
be the component payload, or carry it under a
_render key alongside your other data. Payload shapes match
the curated-component shapes below; validation caps counts and lengths,
images must be https, and the review-card machinery (editable fields /
edit actions) is platform-only and stripped. The "Test this action"
panel shows a render verdict (which component would appear, or exactly
why it fell back to text). The model's copy of the raw response is
capped at 16 KB.
"configuration": { "uri": "https://api.you.com/plans", "method": "GET", "render": "cards" }Two per-action fields turn saved actions into a full conversational controller for your own API — available to any app as pure configuration.
configuration.auth: "visitor-session" — run as
the signed-in visitor. Instead of app credentials, the chat
forwards the caller's own session token as the request's
Authorization header, so your API sees the person and
applies its own permissions per call. Enforced server-side at call
time:
- only real signed-in users — anonymous visitors never even see these actions in chat;
- https URLs only;
- the URL's origin must be in the app's trusted list
(
webchat.trustedSessionOrigins, tools editor → "Trusted session origins", up to 8) — the visitor's token is never forwarded anywhere else, and an unlisted origin returns a refusal instead of making the call.
{args.x} path parameters — one action per
endpoint, not per record. Tokens in the authored URI path
substitute from the action's own arguments at call time:
https://api.you.com/v1/orders/{args.orderId} plus a
parametersSchema declaring orderId lets the
assistant fetch any order. Values are URI-encoded (an argument can never
add path separators or change the host), tokens are honoured in the path
only, an argument used in the path doesn't also join the GET query
string or request body, and a missing argument leaves the token in place
so the call 404s loudly rather than hitting the wrong resource. For
visitor-session actions the origin check runs on the
resolved URL.
Structured (object) parameters — nested request
bodies. Some APIs want a nested body (e.g.
{"fields": {"Name": "…"}}). Give the action a parameter
with type object and describe its fields as a JSON Schema
fragment — in the tools editor, pick "Structured (JSON fields)" and
paste the fields as JSON, e.g.
{ "properties": { "Name": { "type": "string" } }, "required": ["Name"] }.
The assistant then supplies that parameter as one JSON object inside the
request body.
configuration.confirmRequired: true — ask for an
explicit yes. The action's generated schema gains a required
confirm flag and the assistant is instructed to state
exactly what will happen and wait for the user's explicit yes. The hard
floor is server-side: without confirm: true the platform
refuses and never calls your endpoint. Use it for
anything consequential (payments, cancellations, deletions).
Both compose with everything above: credentials stay available for
app-authenticated actions, render: shapes the reply, and
the webchat.tools allowlist still controls which actions
reach the chat at all.
"configuration": { "uri": "https://api.you.com/v1/me", "method": "GET", "auth": "visitor-session" }
"webchat": { "trustedSessionOrigins": ["https://api.you.com"] }Per-app, human-reviewed component payloads stored in
voiceChatTools.curatedComponents (up to 8). Each enabled
entry becomes a show_<id> tool the assistant may
elect, but the content shown is the stored payload
verbatim — the model chooses the moment, never the
words.
Entry shape (written via the normal app PATCH with
voiceChatToolsReplace: true, or managed in the dashboard's
Web chat tab):
Kinds and payloads: cards (≤10 items: title, subtitle,
badge, facts, sections, actions), table (≤6 columns × 20
rows + footnote), location (name, addressLines, phone,
hours, note — the get-directions URL is always server-built from the
address), timeline (≤8 steps). These four are the kinds
available to curated components; the full render contract for
tool results supports six (comparison and
metrics are render-directive-only today). Every payload is
validated on write AND re-validated at serve time.
Drafting and freshness — the dashboard's Web chat tab can draft a best-guess component set from the app's own knowledge base, and later re-read the knowledge to suggest per-entry updates with reasons. Applying a suggestion is always a human's call; nothing changes without you saving it.
{ "id": "our_services", "kind": "cards", "name": "Our services",
"election": "when the visitor asks what services you offer",
"enabled": true,
"payload": { "intro": "…", "items": [{ "title": "…", "badge": "£95", "facts": [{"label": "…", "value": "…"}] }] } }