Quickstart
Build a working WhatsApp agent in 10 minutes.
Quickstart
We'll build a restaurant booking agent that lives on a WhatsApp sender. By the end, customers can text the number and the agent will show the menu, check availability, and confirm reservations.
1. Install the CLI
# No install step. Always runs the current version.
npx zavudev@latest --version
npx zavudev@latest resolves the newest release every time, so there is nothing
to upgrade. If you installed globally, update with npm install -g zavudev@latest.
2. Log in
terminalnpx zavudev login
This opens your browser, you sign in, pick the project this agent will live in,
and click Authorize. The CLI saves the API key to
~/.zavu/credentials.json (chmod 0600).
Confirm you're on the right project:
terminalnpx zavudev whoami
CodeProject: Acme Restaurants Project ID: jh72w2dnzytttrxjqjtaq267nn7wcw6y Team: Acme Mode: live API URL: https://api.zavu.dev Key ending: …1bmmn
3. Find your sender
terminalnpx zavudev senders list
Copy the ID of the WhatsApp sender you'll attach the agent to:
Codeid name phone whatsapp jn76vnxet8g5nq661by3v06y1581bmmn Pizzeria Main +15076323077 yes
terminalexport SENDER_ID="jn76vnxet8g5nq661by3v06y1581bmmn"
4. Scaffold the function
terminalnpx zavudev fn init --template restaurant-booking -y cd reservations
You'll get an index.ts like this (truncated):
TypeScriptimport { defineAgent, defineTool } from "@zavudev/functions" defineAgent({ senderId: process.env.SENDER_ID!, name: "Bella", provider: "zavu", model: "openai/gpt-5.6-luna", channels: ["whatsapp"], prompt: `Eres Bella, anfitriona de Bella Pizzeria…`, }) defineTool({ name: "view_menu", description: "Get the restaurant menu.", parameters: { type: "object", properties: { filter: { type: "string" } } }, handler: async (args) => ({ menu: [/* … */] }), }) // + check_availability, create_reservation, view_reservation
5. Set the sender ID as a secret
terminalnpx zavudev fn secrets set SENDER_ID "$SENDER_ID"
Output:
Code✓ Created SENDER_ID (…1bmmn) Environment updates on the next `npx zavudev deploy`. Run it now to apply.
6. Deploy
terminalnpx zavudev deploy
Watch the output:
Code› Deploying Restaurant reservations agent (reservations)… deployment id: nx7ztwe6cwg1dfp14bk6v7nb2s86hpn6 › status: bundling › status: uploading › status: publishing › status: active ✓ Deployed in 14s Agents synced: + Bella Tools synced: + view_menu + check_availability + create_reservation + view_reservation
Your agent is live. The WhatsApp sender will now hand off every inbound to it.
How the sender → function link works
You didn't run npx zavudev fn triggers add anywhere — and yet, the sender knows to
forward every inbound to your function. Here's the wiring:
defineAgent({ senderId, ... })registers your agent on that sender. Whennpx zavudev deploysyncs the manifest, Zavu writes a row that says "sender$SENDER_IDhas an active agent backed by functionreservations."- Every inbound message to that sender automatically hands off to the agent,
which runs your tools (
view_menu,check_availability, etc.) inside the function. - You can verify the link any time:
terminal
npx zavudev agents get --sender "$SENDER_ID" # → enabled: true, managedByFunctionId: fn_…, model: openai/gpt-5.6-luna
7. Try it on WhatsApp
From your phone (not the sender's number), send the sender:
hola, qué tienen vegano?
Expected flow:
Code👤 hola, qué tienen vegano? 🤖 [calls view_menu(filter="vegan")] 🤖 Tenemos Vegan Buddha Bowl ($11). ¿Te lo reservo? 👤 sí, mañana viernes a las 9 para 2 🤖 [calls check_availability(date="friday", partySize=2)] 🤖 Tengo 19:30 y 21:00. ¿Cuál preferís? 👤 21 🤖 [calls create_reservation(date="friday", time="21:00", partySize=2, customerName="…")] 🤖 ¡Listo! Reserva RES-LXXY confirmada para 2 personas el viernes a las 21:00.
8. Watch it run
In three terminals:
terminal# Live tool calls (your function's console.log + handler activity) npx zavudev fn logs --tail
terminal# Agent executions: which tools, tokens used, cost, latency npx zavudev agents executions --sender "$SENDER_ID"
terminal# Every message in & out npx zavudev messages list --limit 10
9. Iterate
Edit index.ts — say, add a cancel_reservation tool — and redeploy:
terminalnpx zavudev deploy
The summary shows what changed:
Code✓ Deployed in 11s Tools synced: + cancel_reservation
The new tool is immediately available to the agent on the next user message.
You don't need to update prompts — the LLM reads the tool's description and
parameters schema directly.
Common pitfalls
The agent doesn't respond on WhatsApp
Run npx zavudev agents get --sender "$SENDER_ID" and confirm enabled: true.
If false, check that defineAgent is being called (deploy must show
Agents synced: + Bella).
Tools never get called
Check that the tool description is specific enough. The LLM uses the
description to decide when to call the tool, so vague descriptions
("do stuff") don't trigger. Rewrite each description as the answer to
"when should the model call this?".
Also confirm npx zavudev agents tools list --sender "$SENDER_ID" shows the 4
tools with enabled: true.
A tool errors out
Watch npx zavudev fn logs --tail while you trigger the tool. The error stack
appears live. Common causes: missing env var (run
npx zavudev fn secrets list to confirm what's set), JSON parse errors on
response, unhandled async exceptions.
My slug is too long error
Function names cap at 64 chars internally, and we prefix yours with
zavu-fn-<projectId>- (41 chars used). Slugs over 23 chars get rejected
server-side with a clear message. Pick something short — bella, not
restaurant-reservations-agent-v2.
I'm using defineFunction (no agent) and nothing fires
Pure defineFunction handlers don't get traffic automatically — they
need an explicit trigger. Run:
terminalnpx zavudev fn triggers list # see what's bound npx zavudev fn triggers add --events message.inbound \ --senders "$SENDER_ID" # bind to a sender
defineAgent is the only declarative shortcut that auto-binds — every
other event flow goes through triggers. See Triggers.