Documentation
Build with VoiceAgent.sbs
Everything you need to configure an AI voice agent, connect a phone number, ground it in your own content, and embed a live voice-call widget on your website - with real, copy-pasteable examples.
On this page
Getting Started
Website Voice Widget
Platform
Overview
VoiceAgent.sbs is made up of three services that work together: a Next.js dashboard (where you configure agents), a Django API (organizations, agents, knowledge base, calls, billing-adjacent data), and a FastAPI realtime voice server (the actual speech-to-text → LLM → text-to-speech loop, for both phone calls and the website widget). Telephony runs over Twilio or Telnyx; call audio and transcripts, knowledge base documents, and post-call analysis are all stored per-organization.
This page documents the parts you'll actually configure and integrate: agents, the knowledge base, phone numbers, and - in the most depth - the embeddable website voice widget, since that's the piece you wire into an external site's own codebase.
Architecture at a glance
A call - whether it arrives by phone or through the website widget - always flows through the same pipeline:
| Stage | What happens |
|---|---|
| Transport | Twilio/Telnyx (phone) or a browser WebSocket (widget) streams live audio to the FastAPI voice server. |
| Speech-to-text | Incoming audio is transcribed in real time. |
| Reasoning (RAG) | An LLM decides what to say, grounded in the agent's knowledge base - it won't invent answers outside that content. |
| Text-to-speech | The reply is streamed back as audio, with support for the caller interrupting mid-sentence. |
| Persistence | Transcript, recording (if enabled) and call metadata are saved via the Django API as the call progresses and on hang-up. |
| Post-call analysis | After the call ends, an LLM pass extracts a summary, sentiment, and any structured fields you've configured (e.g. a booking) from the transcript. |
Note
Quickstart
To get an agent live in a few minutes:
- Create an agent - open the console and go to Agents → New Agent. Set a greeting message and a system prompt, or start from a template (Customer Support, Sales & Booking, Lead Qualification, Technical Helpdesk).
- Pick a voice and tune speech settings (response eagerness, interruption sensitivity, background sound) under Speech Settings.
- Add knowledge - upload documents or crawl a website under Knowledge Base so the agent answers from real content (see below).
- Test it - use the built-in Live Call (Mic) tester on the agent page to talk to it directly from your browser before connecting a phone number or the widget.
- Go live - either connect a phone number (Phone Numbers) for inbound/outbound calling, or enable the Website Widget to embed a voice button on your own site.
Knowledge Base
Every answer your agent gives is grounded in documents you provide - this is retrieval-augmented generation (RAG), not free-form guessing. Open Knowledge Base, pick a library, and add content in either of two ways:
Upload a file
Supports .pdf, .docx and .txt. The file is parsed, split into overlapping ~1000-character chunks, and each chunk is embedded into a vector database so the agent can retrieve the most relevant passages for any question asked mid-call.
Add a website
Switch to the Add Website tab and paste a URL. The platform crawls that page (checking /sitemap.xml first, then following same-origin links up to a page limit), strips navigation/boilerplate, and embeds the extracted text the same way as an uploaded file.
Note
Either way, documents move through the same lifecycle you can watch live in the dashboard: PENDING → PROCESSING → COMPLETED (or FAILED with an error message if something goes wrong).
Phone Numbers & Calls
Under Phone Numbers, connect a Twilio or Telnyx number and assign it to an agent. Once assigned:
- Inbound calls to that number are answered automatically by the assigned agent.
- Outbound campaigns can place calls from that number using the same agent configuration.
- Call status (ringing, in-progress, completed) updates in the dashboard in real time via telephony status callbacks.
- Recording can be toggled per agent (Call Settings) - when on, both sides of the call are captured and stored as playable audio.
Calendar & Bookings
If a caller books an appointment during the conversation, post-call analysis extracts the date, time, type and any other details you've configured under Post-Call Data Extraction on the agent, and writes them to the call record. The Calendar page reads directly from that extracted data and only shows entries where a real booking was actually discussed - calls that didn't result in a booking won't clutter the calendar.
Call History
The Calls page lists every conversation - phone or widget - with direction (Inbound, Outbound, or Website Widget), duration, live/turn-by-turn transcript, recording playback (if enabled), and the structured post-call analysis output. This is the single place to review what an agent actually said on any given call.
Website Voice Widget
The widget lets a visitor on your own website talk to an agent by microphone directly in the browser - no phone number, no app install. Clicking the embedded button opens a small floating call window (an iframe hosted on VoiceAgent.sbs) that streams the visitor's microphone audio to the same real-time pipeline described above, and plays the agent's voice back in the browser.
Two things make this safe to expose publicly:
- A per-agent domain allowlist - only websites you explicitly authorize can start a session with your agent.
- Short-lived, server-issued call credentials - the browser never sees or needs any permanent secret; each call gets a fresh, single-use token.
1. Enable & configure the widget
- Open the agent you want to expose, expand Website Widget in its settings.
- Toggle Enable Website Widget on.
- Under Allowed Domains, add every exact origin the widget will run on - scheme + host, no path, no trailing slash, e.g.
https://yourcompany.com. Addwwwand non-wwwseparately if your site answers on both - they count as different origins. - Click Save Agent. This list is a fail-closed allowlist: if it's empty, or the requesting site isn't on it, the widget refuses to start a session.
Tip
2. Embed it on your site
Copy the ready-made snippet from the Embed Snippet box (it already has your real agent key filled in) and paste it just before the closing </body> tag. No package to install, no build step - it's a single script tag.
<script
src="https://voiceagent.sbs/widget-loader.js"
data-agent-key="YOUR_WIDGET_KEY"
async
></script>In a Next.js / React app, use next/script instead of a raw tag:
import Script from "next/script";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://voiceagent.sbs/widget-loader.js"
data-agent-key="YOUR_WIDGET_KEY"
data-label="Talk to us"
strategy="afterInteractive"
/>
</body>
</html>
);
}For a plain HTML / WordPress / Webflow site, paste the same script tag directly into the page footer or a custom HTML block.
Configuration reference
All configuration is passed as attributes on the script tag itself:
| Attribute | Required | Description |
|---|---|---|
data-agent-key | Yes | The public widget key for the agent, copied from the Embed Snippet box. |
data-label | No | Text shown on the button. Defaults to "Voice Agent". |
data-api-base | No* | Base URL of the voice server, if it's hosted on a different domain than the widget script itself (see Production deployment below). Defaults to the script's own origin. |
Production deployment
Read this before going live
localhost for local testing) - browsers block microphone access on plain http:// pages, and an HTTPS page cannot load a script or call an API over plain HTTP at all (mixed-content blocking). Both the page embedding the widget and the voice server it talks to must be HTTPS.The widget makes two kinds of requests: an HTTPS POST /widget/session to start a call, and a wss:// WebSocket for the call audio itself. Both are served by the FastAPI voice server, not the Next.js dashboard. In production, put a reverse proxy in front of all three services under one domain so every request the browser makes - static assets, the API, and the WebSocket - resolves correctly without extra configuration:
# voiceagent.sbs - one domain in front of all three services
server {
listen 443 ssl;
server_name voiceagent.sbs;
# Next.js dashboard + widget-loader.js + the /widget/:key iframe page
location / {
proxy_pass http://127.0.0.1:3000;
}
# Django API
location /api/ {
proxy_pass http://127.0.0.1:8000;
}
# FastAPI voice server - session start + WebSocket upgrade
location /widget/session {
proxy_pass http://127.0.0.1:8001;
}
location /ws/ {
proxy_pass http://127.0.0.1:8001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}If instead you run the voice server on its own subdomain (e.g. api.voiceagent.sbs), set data-api-base on the script tag to that URL so the session-start request goes to the right place:
<script
src="https://voiceagent.sbs/widget-loader.js"
data-agent-key="YOUR_WIDGET_KEY"
data-api-base="https://api.voiceagent.sbs"
async
></script>Warning
data-api-base override only affects the session-start request. The call's WebSocket always connects back to whatever host served the widget's iframe page - so with a split-subdomain setup you still need that domain to proxy /ws/* traffic through to the voice server, as shown in the Nginx example above.Testing checklist
- Confirm the agent's Website Widget toggle is on and your site's exact origin is in Allowed Domains.
- Deploy (or tunnel) the voice server so it's reachable over HTTPS from the public internet -
localhostis only reachable from your own machine. - Open your live page, hard-refresh, and confirm the floating button appears.
- Click it, allow microphone access, and confirm you hear the agent's greeting and can talk back.
- Check the Calls page - the call should appear with direction Website Widget.
- If a booking was discussed, confirm it shows up on the Calendar page - this proves the full pipeline (transcript → analysis → booking) ran end to end, not just the audio.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Button doesn't appear at all | Script failed to load, or the wrong data-agent-key. | Check the browser console/network tab for a failed request to widget-loader.js. Confirm the src URL is correct and reachable. |
403 | The page's origin isn't in the agent's Allowed Domains list. | Add the exact origin (check http vs https, and www vs non-www) and save the agent. |
| Script or session request silently fails on an HTTPS page | Mixed content - the widget script or API is being loaded over plain http:// from an https:// page. | Serve the voice server and widget-loader.js over HTTPS. Browsers block this before any error reaches your code. |
429 | The agent hit its concurrent widget-call limit. | Wait for an existing call to end, or reach out if you expect higher concurrent volume. |
| Call connects but no audio either direction | Microphone permission was denied, or the WebSocket never reached the voice server. | Re-check browser mic permissions for the site, and confirm /ws/* is actually proxied through to the voice server in production (see Production deployment above). |
| Call never appears in Call History | Session never actually started (fails before the WebSocket opens). | Work backwards through the checklist above - a Call row is only created once /widget/session succeeds. |
Security model
- Dashboard auth - JWT-based, scoped per organization; every dashboard API call is authorized against the organization you belong to.
- Service-to-service auth - the voice server talks to the API using a private internal key, never a user's credentials.
- Encrypted credential storage - third-party API keys/secrets you connect (telephony, etc.) are encrypted at rest, not stored in plain text.
- Rate limiting - sensitive endpoints (including widget session starts and knowledge-base URL ingestion) are rate-limited per IP and per key to prevent abuse.
- SSRF-safe crawling - the website knowledge-base ingester validates and re-validates every URL and redirect against private/internal IP ranges before fetching.
- Widget domain allowlisting - short-lived, single-use call tokens issued only to origins you've explicitly authorized, as described above.
- Signed telephony webhooks - inbound requests from Twilio/Telnyx are signature-verified before being trusted.
FAQ
Do phone calls and the website widget use different agents?
No - the same agent (same prompt, voice, knowledge base and settings) can serve phone calls and the website widget simultaneously. Enabling the widget doesn't change how the agent behaves on calls.
Can I run the widget on more than one website?
Yes - add every site's origin to the same agent's Allowed Domains list, and use the same embed snippet on each.
Does the widget work on mobile browsers?
Yes, as long as the browser supports microphone capture over HTTPS (all modern mobile browsers do), the same button and call flow work on mobile.
Is widget call audio recorded the same way as phone calls?
Yes - it's subject to the same per-agent recording toggle under Call Settings, and appears in Call History identically to a phone call.
Support
Can't find what you need here? Reach out at [email protected] or use the contact form.
Ready to configure your first agent?
Open the console and deploy an agent, then come back here whenever you need the exact widget snippet or a config reference.
Open the console