Client-side Tools (Web SDK)

Handle tool-calls in the browser without a server URL

Overview

Use the Web SDK to handle tool-calls entirely on the client. This lets your assistant trigger UI-side effects (like showing notifications or changing state) directly in the browser.

In this guide, you’ll learn to:

  • Define a client-side tool with the Web SDK
  • Receive and handle tool-calls events on the client
  • Inject extra context during a call with addMessage

Client-side tools cannot send a tool “result” back to the model. If the model must use the output of a tool to continue reasoning, implement a server-based tool instead. See: Server-based Custom Tools.

To make a tool client-side, simply do not provide a server URL. The tool specification is delivered to the browser, and the Web SDK emits tool-calls messages that your frontend can handle.

Quickstart

  1. Install the Web SDK:
$npm install @vapi-ai/web
  1. Start a call with your tool defined in the model.tools array and subscribe to clientMessages: [‘tool-calls’].
  2. Listen for message.type === ‘tool-calls’ and perform the desired UI update. No response is sent back to the model.
  3. (Optional) Inject context mid-call using vapi.addMessage(…).

Complete example (React + Web SDK)

1import Vapi from '@vapi-ai/web';
2import { useCallback, useState } from 'react';
3
4const vapi = new Vapi('<YOUR_PUBLIC_KEY>');
5
6function App() {
7 const [notification, setNotification] = useState<string | null>(null);
8
9 const handleUIUpdate = useCallback((message?: string) => {
10 setNotification(message || 'UI Update Triggered!');
11 setTimeout(() => setNotification(null), 3000);
12 }, []);
13
14 // 1) Listen for client tool-calls and update the UI
15 vapi.on('message', (message) => {
16 console.log('Message:', message);
17
18 if (message.type === 'tool-calls') {
19 const toolCalls = message.toolCallList;
20
21 toolCalls.forEach((toolCall) => {
22 const functionName = toolCall.function?.name;
23 let parameters: Record<string, unknown> = {};
24
25 try {
26 const args = toolCall.function?.arguments;
27 if (typeof args === 'string') {
28 parameters = JSON.parse(args || '{}');
29 } else if (typeof args === 'object' && args !== null) {
30 parameters = args as Record<string, unknown>;
31 } else {
32 parameters = {};
33 }
34 } catch (err) {
35 console.error('Failed to parse toolCall arguments:', err);
36 return;
37 }
38
39 if (functionName === 'updateUI') {
40 handleUIUpdate((parameters as any).message);
41 }
42 });
43 }
44 });
45
46 // 2) Start the call with a client-side tool (no server URL)
47 const startCall = useCallback(() => {
48 vapi.start({
49 model: {
50 provider: 'openai',
51 model: 'gpt-4.1',
52 messages: [
53 {
54 role: 'system',
55 content:
56 "You are an attentive assistant who can interact with the application's user interface by calling available tools. Whenever the user asks to update, refresh, change, or otherwise modify the UI, or hints that some UI update should occur, always use the 'updateUI' tool call with the requested action and relevant data. Use tool calls proactively if you determine that a UI update would be helpful.",
57 },
58 ],
59 tools: [
60 {
61 type: 'function',
62 async: true,
63 function: {
64 name: 'updateUI',
65 description:
66 'Call this function to initiate any UI update whenever the user requests or implies they want the user interface to change (for example: show a message, highlight something, trigger an animation, etc). Provide an \'action\' describing the update and an optional \'data\' object with specifics.',
67 parameters: {
68 type: 'object',
69 properties: {
70 message: {
71 description:
72 'Feel free to start with any brief introduction message in 10 words.',
73 type: 'string',
74 default: '',
75 },
76 },
77 required: ['message'],
78 },
79 },
80 messages: [
81 {
82 type: 'request-start',
83 content: 'Updating UI...',
84 blocking: false,
85 },
86 ],
87 },
88 ],
89 },
90 voice: { provider: 'vapi', voiceId: 'Elliot' },
91 transcriber: { provider: 'deepgram', model: 'nova-2', language: 'en' },
92 name: 'Alex - Test',
93 firstMessage: 'Hello.',
94 voicemailMessage: "Please call back when you're available.",
95 endCallMessage: 'Goodbye.',
96 clientMessages: ['tool-calls'], // subscribe to client-side tool calls
97 });
98 }, []);
99
100 const stopCall = useCallback(() => {
101 vapi.stop();
102 }, []);
103
104 return (
105 <div style={{
106 minHeight: '100vh',
107 display: 'flex',
108 alignItems: 'center',
109 justifyContent: 'center',
110 background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
111 fontFamily:
112 '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
113 }}>
114 {notification && (
115 <div style={{
116 position: 'fixed',
117 top: 20,
118 left: '50%',
119 transform: 'translateX(-50%)',
120 background: '#10b981',
121 color: '#fff',
122 padding: '16px 24px',
123 textAlign: 'center',
124 borderRadius: '12px',
125 zIndex: 1000,
126 maxWidth: 400,
127 boxShadow: '0 10px 25px rgba(0, 0, 0, 0.2)',
128 fontSize: '14px',
129 fontWeight: 500,
130 }}>
131 {notification}
132 </div>
133 )}
134
135 <div style={{
136 background: 'white',
137 padding: '48px',
138 borderRadius: '20px',
139 boxShadow: '0 20px 60px rgba(0, 0, 0, 0.3)',
140 textAlign: 'center',
141 maxWidth: '400px',
142 width: '100%',
143 }}>
144 <h1 style={{
145 fontSize: '32px',
146 fontWeight: 700,
147 color: '#1f2937',
148 marginBottom: '12px',
149 marginTop: 0,
150 }}>
151 Vapi Client Tool Calls
152 </h1>
153
154 <p style={{
155 fontSize: '16px',
156 color: '#6b7280',
157 marginBottom: '32px',
158 marginTop: 0,
159 }}>
160 Start a call and ask the assistant to trigger UI updates
161 </p>
162
163 <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
164 <button
165 onClick={startCall}
166 style={{
167 background:
168 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
169 color: 'white',
170 border: 'none',
171 padding: '16px 32px',
172 borderRadius: '12px',
173 fontSize: '16px',
174 fontWeight: 600,
175 cursor: 'pointer',
176 transition: 'transform 0.2s, box-shadow 0.2s',
177 boxShadow: '0 4px 12px rgba(102, 126, 234, 0.4)',
178 }}
179 onMouseEnter={(e) => {
180 (e.target as HTMLButtonElement).style.transform = 'translateY(-2px)';
181 (e.target as HTMLButtonElement).style.boxShadow =
182 '0 6px 20px rgba(102, 126, 234, 0.5)';
183 }}
184 onMouseLeave={(e) => {
185 (e.target as HTMLButtonElement).style.transform = 'translateY(0)';
186 (e.target as HTMLButtonElement).style.boxShadow =
187 '0 4px 12px rgba(102, 126, 234, 0.4)';
188 }}
189 >
190 Start Call
191 </button>
192
193 <button
194 onClick={stopCall}
195 style={{
196 background: 'white',
197 color: '#ef4444',
198 border: '2px solid #ef4444',
199 padding: '16px 32px',
200 borderRadius: '12px',
201 fontSize: '16px',
202 fontWeight: 600,
203 cursor: 'pointer',
204 transition: 'all 0.2s',
205 }}
206 onMouseEnter={(e) => {
207 (e.target as HTMLButtonElement).style.background = '#ef4444';
208 (e.target as HTMLButtonElement).style.color = 'white';
209 }}
210 onMouseLeave={(e) => {
211 (e.target as HTMLButtonElement).style.background = 'white';
212 (e.target as HTMLButtonElement).style.color = '#ef4444';
213 }}
214 >
215 Stop Call
216 </button>
217 </div>
218 </div>
219 </div>
220 );
221}
222
223export default App;

Inject data during the call

Use addMessage to provide extra context mid-call. This does not return results for a tool; it adds messages the model can see.

1// Inject system-level context
2vapi.addMessage({
3 role: 'system',
4 content: 'Context: userId=123, plan=premium, theme=dark',
5});
6
7// Inject a user message
8vapi.addMessage({
9 role: 'user',
10 content: 'FYI: I switched to the settings tab.',
11});

If you need the model to consume tool outputs (e.g., fetch data and continue reasoning with it), implement a server-based tool. See Custom Tools.

Key points

  • Client-only execution: Omit the server URL to run tools on the client.
  • One-way side effects: Client tools do not send results back to the model.
  • Subscribe to events: Use clientMessages: [‘tool-calls’] and handle message.type === ‘tool-calls’.
  • Add context: Use vapi.addMessage to inject data mid-call.

Next steps

  • Server-based tools: Learn how to return results back to the model with Custom Tools.
  • API reference: See Tools API for full configuration options.