How to Build Context-Aware Voice Agents with Vocade
Most voice agents are blind. They know nothing about the user they're talking to, the page they're on, or what they were doing five seconds ago. A user calls your agent from a dashboard showing three overdue invoices, and the agent cheerfully asks "How can I help you today?" like it's never seen the app before. That disconnect is the gap between a voice widget and a voice experience.
Vocade's Contextual Agent Intelligence solves this with a 3-layer system that feeds your app's live data directly into the agent's brain. The result: your agent knows who the user is, what they're looking at, and what they probably need help with - before they say a word.
This guide covers the first two layers: Static Context Injection (Phase 1) and Dynamic Context via context_url (Phase 2). By the end, you'll have a voice agent that greets users by name, references their pending tasks, and proactively offers help based on real app state.
What Context-Aware Agents Can Actually Do
Once your agent has context, the conversations change completely. Instead of generic "How can I help?" openers, your agent Riva can say things like:
- "Hey John, I see you have 2 documents pending signature. Want me to take you there?"
- "Looks like you're on the pricing page. Want me to walk you through the Business plan features?"
- "Your last login was 3 weeks ago - would you like a quick refresher on what's new?"
That's not a script. It's the agent reading your app's state and responding like a colleague who actually knows what's going on.
Phase 1: Static Context Injection
Static context is the simplest way to make your agent aware. You pass a JSON object when the widget loads, and that data gets prepended to the agent's system prompt for the entire session. No backend changes required.
There are two ways to do this:
Method A: The data-context Attribute
Drop context directly into the widget script tag. Good for server-rendered pages where the context is known at page load.
<script
src="https://vocade.ai/widget.js?v=2"
data-agent-id="YOUR_AGENT_ID"
data-api-key="YOUR_API_KEY"
data-context='{"page":"/dashboard","user":{"name":"John","plan":"Business"},"appState":{"pendingSignatures":2}}'
></script>
The JSON goes into data-context as a string. Keep it compact - there's a 4096-byte limit on the payload.
Method B: JavaScript API with setContext()
For SPAs and dynamic apps, use the JavaScript API. This lets you build context from live app state right before the user starts a call.
// Set before starting the call
window.VocadeWidget.setContext({
page: window.location.pathname,
user: { name: currentUser.name, plan: currentUser.plan },
appState: {
pendingSignatures: documents.filter(d => d.status === 'pending').length,
lastActivity: user.lastLoginAt
}
})
Method B is the better choice for most modern apps. You can call setContext() on every route change so the agent always knows which page the user is on. In a React app, that's a one-liner in your router's navigation handler.
How the Agent Receives It
Behind the scenes, the context object gets serialized and prepended to the agent's system prompt. The agent sees it as structured data at the top of its instructions, before any of the personality or knowledge base content you've configured in the dashboard.
Security Details
A few things to know about how static context is handled:
- 4096-byte limit on the context payload. This is intentional - it forces you to send only what matters.
- Prompt injection sanitization runs on all context values before they reach the agent. Users can't sneak instructions into the context object.
- Session-scoped only - context lives for the duration of one voice session. It's not stored, not logged beyond the call transcript, and not shared across sessions.
Don't put passwords, full credit card numbers, or SSNs in context. The agent doesn't need them, and you don't want them in a system prompt.
Phase 2: Dynamic Context via context_url
Static context works well when your frontend already has the data. But what if the richest context lives on your server? User account details, pending actions, recent activity logs, subscription status - this data often isn't available client-side, or shouldn't be exposed to the browser.
That's what context_url solves. You give Vocade a URL on your server. At the start of every voice session, Vocade calls that URL and pulls fresh context directly from your backend.
Setting It Up
In the Vocade dashboard, go to Integrations tab → Context & Data → Context URL. Paste in the URL of your endpoint. That's the dashboard side done.
Building the Endpoint
Your endpoint needs to return a JSON object with whatever context you want the agent to have. Here's an Express.js example:
// Express.js example
app.get('/api/agent-context', authenticate, async (req, res) => {
const user = req.user
const [pendingDocs, recentActivity] = await Promise.all([
Document.findAll({ where: { assignedTo: user.id, status: 'pending' } }),
ActivityLog.findRecent(user.id, 5)
])
res.json({
user: { name: user.fullName, email: user.email, plan: user.plan },
pendingSignatures: pendingDocs.map(d => ({ title: d.title, dueDate: d.dueDate })),
recentActivity: recentActivity.map(a => a.description),
currentPage: req.headers['x-page'] || 'unknown'
})
})
The endpoint should be fast. Vocade enforces a 2-second hard timeout on context_url requests. If your endpoint is slow or down, the agent starts the session without context - it falls back gracefully, so users never see an error. But they also don't get the personalized experience, so aim for sub-500ms response times.
Passing the Auth Token
Your endpoint needs to know which user is calling. The widget passes an auth token from the client side, which Vocade forwards to your context_url as a bearer token:
window.VocadeWidget.setContext({
authToken: localStorage.getItem('authToken') // or from your auth system
})
Vocade sends this token in the Authorization header when calling your endpoint. Your authenticate middleware validates it like any other API request. One important security detail: the auth token is stripped from the context before it reaches the agent's prompt. The agent never sees the raw token - it only sees the data your endpoint returns.
HTTPS is required for context_url endpoints. Vocade will reject plain HTTP URLs.
What the Agent Actually Sees
Whether you use static context, context_url, or both, the agent receives the data as a structured block at the top of its system prompt. Here's what it looks like:
[CURRENT SESSION CONTEXT]
user:
name: John Smith
email: john@acme.com
plan: Business
pendingSignatures: NDA - Acme Corp (due Mar 5), Service Agreement (due Mar 7)
recentActivity: Sent envelope to supplier, Viewed pricing page, Completed onboarding
---
[Agent's normal system prompt continues here...]
The agent reads this like any other part of its instructions. It can reference the user's name, mention their pending documents, and tailor its responses based on plan type or recent activity. You don't need to prompt-engineer any special handling - the agent naturally incorporates the context into its conversation.
Best Practices
After working with teams building context-aware agents, here's what works:
- Send only what the agent needs. A dump of your entire user object is noise. Pick 5-10 fields that actually change how the agent should respond. Name, plan, current page, and 2-3 app-specific data points cover most use cases.
- Call setContext() on route changes. If your SPA user navigates from /dashboard to /billing, the agent should know. Hook into your router and update the page field on every navigation.
- Keep your context_url endpoint fast. Under 500ms is ideal. Cache aggressively if needed. The 2-second timeout exists because users shouldn't wait for context to load before they can talk.
- Use clear, descriptive keys. The agent interprets key names literally.
pendingSignaturesis better thanps.user.planis better thantier_id. Write keys as if you're labeling data for a colleague. - Don't include sensitive data. No passwords, no full SSNs, no payment card numbers. If the agent doesn't need it to help the user, don't send it.
- Test with real user data. Synthetic test data hides edge cases. Use actual (anonymized) user profiles to test how the agent responds to different contexts - a new user with no activity looks very different from a power user with 50 pending items.
Coming Soon: Phase 3 - UI Actions
Phases 1 and 2 give the agent eyes into your app. Phase 3 gives it hands.
With UI Actions, the agent will be able to emit navigation commands like [NAVIGATE:/settings/billing] that the widget executes in the browser. The user says "take me to my billing settings" and the app navigates there. The agent says "let me show you where to upload documents" and the page changes.
This turns your voice agent into a guided tour system that can walk users through complex workflows step by step. Phase 3 is currently in development and will be available to Pro+ plans.
Wrapping Up
Generic voice agents answer questions. Context-aware voice agents anticipate needs. The difference is a few lines of code on your end and a fundamental shift in what the agent can do for your users.
Phase 1 (static context via the widget) is available on all Vocade plans today. Phase 2 (dynamic context via context_url) is available on Pro plans and above. Both can be set up in under 30 minutes.
Start with static context to get your agent aware of the user's name and current page. Then add a context_url endpoint to pull in live data from your backend. The jump in conversation quality is immediate and measurable - your users will notice the difference on their very first call.