Reference
Examples
Production-ready recipes and real-world patterns for building chatbots, e-commerce stores, automated notifications, and webhook handlers.
1. Express Webhook Server (One-liner Setup)
A complete Express server that sets up the GET verification endpoint, verifies HMAC signatures, and handles incoming text and interactive button clicks:
server.ts
import express from "express";
import { Whatsapp } from "wapi-cloud";
const app = express();
const whatsapp = new Whatsapp({
accessToken: process.env.WA_TOKEN!,
phoneNumberId: process.env.WA_PHONE_ID!,
businessAccountId: process.env.WA_WABA_ID!,
appSecret: process.env.WA_APP_SECRET!,
});
// Setup webhook endpoints and verification challenge
whatsapp.webhooks.handleExpress(app, "/webhook", {
verifyToken: process.env.WA_VERIFY_TOKEN!,
});
// Handle incoming messages
whatsapp.webhooks.on("message", async (msg) => {
if (msg.messageType === "text") {
// Reply to text messages
await whatsapp.messages.reply(msg.from, msg.id, {
body: "Hello! You said: " + msg.text.body,
});
} else if (msg.messageType === "interactive") {
console.log("Interactive button response from", msg.from, msg.interactive);
}
});
// Handle delivery status receipts
whatsapp.webhooks.on("status", (status) => {
console.log("Message " + status.messageId + " -> " + status.status);
});
app.listen(3000, () => console.log("Server running on http://localhost:3000"));2. Next.js App Router Webhook Handler
Full Route Handler for Next.js 14 and 15 (app/api/whatsapp/webhook/route.ts):
app/api/whatsapp/webhook/route.ts
import { Whatsapp } from "wapi-cloud";
const whatsapp = new Whatsapp({
accessToken: process.env.WA_TOKEN!,
phoneNumberId: process.env.WA_PHONE_ID!,
appSecret: process.env.WA_APP_SECRET!,
});
// GET: Meta Webhook Verification Handshake
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
try {
const challenge = whatsapp.webhooks.verifyHandshake({
mode: searchParams.get("hub.mode"),
token: searchParams.get("hub.verify_token"),
challenge: searchParams.get("hub.challenge"),
expectedToken: process.env.WA_VERIFY_TOKEN!,
});
return new Response(challenge, { status: 200 });
} catch {
return new Response("Unauthorized", { status: 403 });
}
}
// POST: Process incoming WhatsApp events
export async function POST(request: Request) {
const rawBody = await request.text();
const signatureHeader = request.headers.get("x-hub-signature-256") ?? undefined;
// 1. Verify HMAC Signature
const isValid = whatsapp.webhooks.verifySignature({
payload: rawBody,
signatureHeader,
});
if (!isValid) {
return new Response("Invalid signature", { status: 401 });
}
// 2. Parse batch of events
const events = whatsapp.webhooks.parse(JSON.parse(rawBody));
for (const event of events) {
if (event.type === "message" && event.messageType === "text") {
await whatsapp.messages.sendText(event.from, {
body: "Thanks for reaching out! A representative will reply shortly.",
});
}
}
return new Response("EVENT_RECEIVED", { status: 200 });
}3. E-Commerce Order Confirmation & Interactive Menu
ecommerce-bot.ts
import { Whatsapp } from "wapi-cloud";
const whatsapp = new Whatsapp({
accessToken: process.env.WA_TOKEN!,
phoneNumberId: process.env.WA_PHONE_ID!,
businessAccountId: process.env.WA_WABA_ID!,
businessId: process.env.WA_BUSINESS_ID!,
});
export async function sendOrderFlow(customerPhone: string, orderNumber: string) {
// Step 1: Send approved Utility template outside 24h window
const { data: tplResult, error: tplError } = await whatsapp.messages.sendTemplate(
customerPhone,
{
name: "order_confirmation_v2",
language: "en_US",
components: [
{
type: "body",
parameters: [
{ type: "text", text: "Alex" },
{ type: "text", text: orderNumber },
],
},
],
}
);
if (tplError) {
console.error("Failed to send template:", tplError.message);
return;
}
console.log("Template sent with ID:", tplResult.messageId);
// Step 2: Send product recommendations list
await whatsapp.catalogs.sendProductList(customerPhone, {
catalogId: process.env.WA_CATALOG_ID!,
header: "Recommended Add-ons",
body: "Customers also purchased these items with their order:",
sections: [
{
title: "Accessories",
productRetailerIds: ["ACC-CASE-01", "ACC-CHARGER-02"],
},
],
});
}4. Media Upload & PDF Invoice Dispatch
send-invoice.ts
import { readFile } from "node:fs/promises";
import { whatsapp } from "./whatsappClient";
export async function sendInvoice(to: string, invoiceFilePath: string, invoiceNumber: string) {
// 1. Read binary buffer from disk or S3
const fileBuffer = await readFile(invoiceFilePath);
// 2. Upload to Meta media store
const { data: media, error: uploadError } = await whatsapp.media.upload(fileBuffer, {
type: "application/pdf",
filename: "Invoice_" + invoiceNumber + ".pdf",
});
if (uploadError) {
console.error("Failed to upload invoice:", uploadError.message);
return;
}
// 3. Send as document message
const { data: sendResult, error: sendError } = await whatsapp.messages.sendDocument(to, {
mediaId: media.id,
filename: "Invoice_" + invoiceNumber + ".pdf",
caption: "Here is your invoice for order #" + invoiceNumber + ".",
});
if (sendError) {
console.error("Failed to deliver invoice message:", sendError.message);
} else {
console.log("Invoice delivered! Message ID:", sendResult.messageId);
}
}5. Template Auto-Pagination & Broadcast Campaign
broadcast.ts
import { whatsapp } from "./whatsappClient";
export async function runBroadcast(recipients: string[], templateName: string) {
// 1. Verify template is approved across all pages
let isApproved = false;
for await (const t of whatsapp.templates.listAll()) {
if (t.name === templateName && t.status === "APPROVED") {
isApproved = true;
break;
}
}
if (!isApproved) {
throw new Error("Template " + templateName + " is not approved on Meta.");
}
console.log("Broadcasting " + templateName + " to " + recipients.length + " recipients...");
// 2. Dispatch in batches with error logging
const results = await Promise.all(
recipients.map((phone) =>
whatsapp.messages.sendTemplate(phone, {
name: templateName,
language: "en_US",
})
)
);
const successful = results.filter((r) => r.data !== null);
const failed = results.filter((r) => r.error !== null);
console.log("Broadcast finished: " + successful.length + " sent, " + failed.length + " failed.");
}6. Embedded Signup Customer Onboarding
embedded-signup-onboard.ts
import { whatsapp } from "./whatsappClient";
export async function onboardTenant(code: string, customerWabaId: string) {
// 1. Exchange browser popup code for permanent system user token
const { data: tokenData, error: tokenError } =
await whatsapp.embeddedSignup.exchangeCodeForToken(code);
if (tokenError) {
throw new Error("Token exchange failed: " + tokenError.message);
}
const { accessToken } = tokenData;
// 2. Subscribe your Meta app to customer's WABA webhooks
const { data: subData, error: subError } =
await whatsapp.embeddedSignup.subscribeToWaba(customerWabaId, {
accessTokenOverride: accessToken,
});
if (subError) {
throw new Error("Webhook subscription failed: " + subError.message);
}
console.log("Tenant onboarded and webhooks subscribed successfully!");
}