Skip to content

Quickstart

Build a working WhatsApp agent in 10 minutes.

View .md

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

terminal
npx 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:

terminal
npx zavudev whoami
Code
Project: Acme Restaurants Project ID: jh72w2dnzytttrxjqjtaq267nn7wcw6y Team: Acme Mode: live API URL: https://api.zavu.dev Key ending: …1bmmn

3. Find your sender

terminal
npx zavudev senders list

Copy the ID of the WhatsApp sender you'll attach the agent to:

Code
id name phone whatsapp jn76vnxet8g5nq661by3v06y1581bmmn Pizzeria Main +15076323077 yes
terminal
export SENDER_ID="jn76vnxet8g5nq661by3v06y1581bmmn"

4. Scaffold the function

terminal
npx zavudev fn init --template restaurant-booking -y cd reservations

You'll get an index.ts like this (truncated):

TypeScript
import { 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

terminal
npx 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

terminal
npx 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.

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. When npx zavudev deploy syncs the manifest, Zavu writes a row that says "sender $SENDER_ID has an active agent backed by function reservations."
  • 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:

terminal
npx 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:

terminal
npx 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.

Next steps