What you'll build

This is Part 1 of a series where you build a WhatsApp bot for a small food business called Quick Bites. Customers send a WhatsApp message to place orders, browse the menu, and receive a Paystack payment link — no app download required.

By the end of the series you'll have:

By the end of Part 1 you'll have a running webhook that receives WhatsApp messages and sends back a reply.

Prerequisites

Join the Twilio WhatsApp sandbox

  1. In your Twilio console, go to Messaging → Try it out → Send a WhatsApp message
  2. You'll see a sandbox number (e.g. +1 415 523 8886) and a join code (e.g. join bronze-sunset)
  3. On your phone, send the join code to that number on WhatsApp — you'll get a confirmation
  4. Your phone is now connected to the sandbox

Sandbox limitations: Each person who wants to test the bot must send the join code first. This is a sandbox restriction — production WhatsApp Business numbers don't require it. For a real launch, apply for a WhatsApp Business account through Twilio's console.

Create the project

mkdir quickbites-bot && cd quickbites-bot
npm init -y
npm install express dotenv twilio
npm install -D typescript ts-node @types/node @types/express nodemon

Create tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true
  }
}

Create .env:

TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TWILIO_AUTH_TOKEN=your_auth_token
TWILIO_WHATSAPP_NUMBER=whatsapp:+14155238886
PORT=3000

Find your Account SID and Auth Token in the Twilio console dashboard.

Tell BlocWeave about the project

Create BLOCWEAVE_PLAN.md:

# Quick Bites WhatsApp Bot

## Stack
- Node.js 20 + Express + TypeScript
- Twilio SDK for WhatsApp messaging
- Session state in a Map (later: KV store on Cloudflare)
- Deploying to Cloudflare Workers in Part 4

## Conventions
- All source in src/
- Webhook handler in src/bot/handler.ts
- Menu data in src/bot/menu.ts
- Session management in src/bot/session.ts
- Twilio sends POST with body fields: Body (message text), From (sender's WhatsApp number), To (your sandbox number)
- Reply by calling twilio.messages.create({ from: To, to: From, body: replyText })

Build the webhook

BlocWeave prompt

Create the WhatsApp webhook server for the Quick Bites bot. 1. src/index.ts — Express server on PORT. POST /webhook route. GET /health. Start listening. 2. src/bot/handler.ts — export webhookHandler(req, res): - Parse Twilio POST body fields: Body (trim and lowercase), From, To - Initialise a Twilio client using TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN - Reply by calling client.messages.create({ from: To, to: From, body: replyText }) - For now: if the incoming message is "hi" or "hello" or "menu", reply with "Welcome to Quick Bites! 🍔\n\nReply with:\n1. View menu\n2. My order\n0. Help" - For anything else: reply with "Send menu to see what we have, or hi to start." - Respond to Twilio with 200 immediately (before awaiting the send) to avoid Twilio timeout retries - Use res.sendStatus(200) right after parsing — fire the Twilio send as a Promise without awaiting

Start ngrok and connect Twilio

Start the dev server:

npx ts-node src/index.ts

In a second terminal, start ngrok:

ngrok http 3000

Copy the https:// URL. In Twilio console, go to Messaging → Sandbox for WhatsApp → Sandbox settings and set:

Save settings.

Test it

On your phone, send hi to the Twilio sandbox number on WhatsApp. You should see the welcome menu reply within a few seconds.

Try sending menu — same reply. Try sending hello there — you should get the fallback message.

Twilio retries: If your webhook takes more than 15 seconds to respond, Twilio retries the request, which can cause duplicate messages. Always send a 200 response immediately after parsing — before awaiting external calls. The fire-and-forget pattern for sending the reply is intentional.

Project structure after Part 1

src/
  bot/
    handler.ts     ← webhook handler
  index.ts
BLOCWEAVE_PLAN.md
.env
package.json

What's next

In Part 2 we build the full menu flow — a numbered catalogue the customer navigates by replying with numbers, with session state tracking their position in the conversation.