Embed an AI Chat Widget on Any Website in 5 Minutes
Want to add an AI assistant to your website — one that can answer questions, use tools, and even talk? Universal API's embed widget lets you do it with a single <script> tag. No React, no npm install, no build step.
In this tutorial, we'll add a text chat widget to a website. By the end, your visitors will be chatting with an AI agent that streams responses with markdown, shows tool call details, and displays usage metrics — all without leaving your page.
Already have a voice agent?
If you're looking for voice-only embedding, check out our companion post: Add a Voice Agent to Your Website in 5 Minutes. The widget supports text, voice, or both modes!
What You'll Build
A floating chat button in the corner of your website that opens an AI assistant panel with:
- ✅ Streaming text responses — words appear in real-time as the agent thinks
- ✅ Markdown rendering — code blocks, tables, headers, links all render beautifully
- ✅ Tool call details — expandable section showing which tools the agent used
- ✅ Metrics bar — token count, cycle count, duration, and tools used per response
- ✅ Agent branding — your agent's name in the header, your brand color everywhere
Prerequisites
- A Universal API account (free tier works!)
- An agent already created (see Creating Agents)
Step 1: Create an Embed Token
Embed tokens are publishable keys — safe to include in client-side HTML. They're domain-restricted and rate-limited, so your agent is protected even though the token is visible in your page source.
Via the Dashboard
- Go to API Keys at universalapi.co/api-keys
- Click Create Embed Token
- Select your agent
- Add your domains (e.g.,
yourdomain.com,localhost:3000for development) - Choose mode: Text, Voice, or Both
- Copy the token (starts with
emb_pk_live_)
Via the API
curl -X POST https://api.universalapi.co/embed/create \
-H "Authorization: Bearer YOUR_BEARER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"agentId": "YOUR_AGENT_ID",
"allowedDomains": ["yourdomain.com", "localhost:3000"],
"mode": "text"
}'Step 2: Add the Script Tag
Paste this before </body> on any page:
<script
src="https://cdn.universalapi.co/embed/widget.js"
data-text-agent="YOUR_AGENT_ID"
data-token="emb_pk_live_YOUR_TOKEN"
data-mode="text"
data-greeting="Hi! How can I help you today?"
data-color="#6366f1"
data-agent-name="Support Assistant">
</script>That's it! A floating chat button (FAB) appears in the bottom-right corner. Click it to open the assistant panel.
Step 3: Customize
Brand Color
Change data-color to match your brand:
data-color="#10b981" <!-- Emerald green -->
data-color="#ef4444" <!-- Red -->
data-color="#3b82f6" <!-- Blue -->Position
Move the button to the bottom-left:
data-position="bottom-left"Greeting Message
Set a contextual greeting:
data-greeting="Welcome to Acme! Ask me about our products, pricing, or support."Agent Name
Override the agent name shown in the header:
data-agent-name="Acme Assistant"Auto-Open on Page Load
Want the chat panel to open automatically when the page loads — for example, on a support page or landing page? Add data-auto-open="true":
<script
src="https://cdn.universalapi.co/embed/widget.js"
data-text-agent="YOUR_AGENT_ID"
data-token="emb_pk_live_YOUR_TOKEN"
data-auto-open="true"
data-greeting="Welcome! How can I help you today?">
</script>By default, the panel opens after a 1-second delay to let the page settle. You can customize the delay with data-auto-open-delay (in milliseconds):
data-auto-open-delay="2000"Works reliably with cookie banners & other widgets
The auto-open uses a robust two-phase timing strategy that waits for the page to fully render before opening. This means it works even when your site has cookie consent banners, chat widgets, or other elements that load asynchronously.
Enable Both Voice and Text
Want to give users the choice? Set data-mode="both" and specify both a text agent and a voice agent:
<script
src="https://cdn.universalapi.co/embed/widget.js"
data-text-agent="YOUR_TEXT_AGENT_ID"
data-voice-agent="YOUR_VOICE_AGENT_ID"
data-token="emb_pk_live_YOUR_TOKEN"
data-mode="both"
data-default-tab="text"
data-greeting="Hi! Type or click the mic to talk."
data-color="#6366f1">
</script>Use data-default-tab="voice" to open the voice tab by default instead of text chat. If omitted, text is the default.
Why two separate agents?
Text and voice agents use completely different technology stacks. Text agents use Strands Agents with streaming HTTP responses, while voice agents use Amazon Nova Sonic with real-time bidirectional WebSocket audio. Because of this, you need to create and specify each one separately — data-text-agent for text chat and data-voice-agent for voice conversations.
The panel header now shows a 🎤/💬 toggle so users can switch between typing and speaking.
Platform-Specific Instructions
WordPress
Add the script to your theme's footer.php, or use a plugin like Insert Headers and Footers:
- Install the "WPCode" plugin
- Go to Code Snippets → Add Snippet
- Paste the script tag
- Set location to Site Wide Footer
- Activate
React / Next.js
'use client';
import { useEffect } from 'react';
export default function ChatWidget() {
useEffect(() => {
const script = document.createElement('script');
script.src = 'https://cdn.universalapi.co/embed/widget.js';
script.dataset.textAgent = 'YOUR_TEXT_AGENT_ID';
script.dataset.voiceAgent = 'YOUR_VOICE_AGENT_ID';
script.dataset.token = 'emb_pk_live_YOUR_TOKEN';
script.dataset.mode = 'both';
script.dataset.color = '#6366f1';
script.dataset.greeting = 'Hi! How can I help?';
document.body.appendChild(script);
return () => { document.body.removeChild(script); };
}, []);
return null;
}Add <ChatWidget /> to your layout component.
Webflow
- Go to Project Settings → Custom Code
- Paste the script tag in the Footer Code section
- Publish
Squarespace
- Go to Settings → Developer Tools → Code Injection
- Paste the script tag in the Footer section
- Save
How It Works Under the Hood
widget.jsloads on your page and reads thedata-*attributes- It calls
GET /embed/configto fetch agent configuration and validate the token - The widget creates a closed Shadow DOM — completely isolated from your site's CSS and JavaScript
- Text mode: Messages are sent via
POST /embed/chat, which returns streaming credentials, then the widget streams directly from the agent API - Voice mode: A WebSocket connects to
wss://voice.api.universalapi.cofor real-time audio streaming - Both mode: Toggle tabs let users switch between 💬 Chat and 🎙️ Voice
Security
- Domain restriction — Tokens only work on domains you specified during creation
- Rate limiting — Default 10 requests per IP per day, 1,000 total per day per token
- No credentials exposed — The
/embed/chatproxy uses the token owner's bearer token server-side - Shadow DOM isolation — Widget CSS/JS can't interfere with your site and vice versa
What's New: Tool Call Details & Metrics
The latest version of the text chat widget includes two features borrowed from our dashboard:
Tool Call Details
After each response, if the agent used any tools, you'll see an expandable "🔧 N tool calls" section. Click it to see each tool name, its input parameters (as JSON), and the result. This is perfect for debugging or showing transparency to technical users.
Metrics Bar
Every response includes a subtle metrics bar showing:
- Tokens — Total input + output tokens used
- Cycles — Number of reasoning cycles
- Duration — Wall-clock time for the response
- Tools — Number of tool calls made
Billing
All interactions are billed to the embed token owner's account (that's you). Your website visitors don't need Universal API accounts. See our pricing page for current credit rates.
Next Steps
- 📖 Embed Widget Docs — Full reference documentation
- 🔊 Add Voice to Your Website — Voice-only embed tutorial
- 🤖 Creating Agents — Build the agent behind your widget
- 🔑 API Reference — All data attributes and token endpoints