WHAM Documentation
Learn how to build WhatsApp Business applications with the wham library. Type-safe, simple, powerful.
Getting Started
Get up and running with wham in under 5 minutes
1. Install the package
npm install @manano-ai/wham
Note: This is a private repository. Join the waitlist to get access.
2. Initialize the client
import { WhatsApp } from '@manano-ai/wham';const wa = new WhatsApp({token: process.env.WHATSAPP_TOKEN!,phoneNumberId: process.env.PHONE_NUMBER_ID!,businessAccountId: process.env.BUSINESS_ACCOUNT_ID, // optional});
3. Send your first message
// Send a text messageawait wa.sendMessage('+1234567890', 'Hello from WHAM!');// Send a message with buttonsawait wa.sendButtons('+1234567890','How can I help you today?',[{ id: 'support', title: 'Get Support' },{ id: 'sales', title: 'Talk to Sales' },]);
Environment Variables
You'll need these credentials from the Meta Developer Portal:
WHATSAPP_TOKEN- Your WhatsApp Cloud API access tokenPHONE_NUMBER_ID- Your WhatsApp phone number IDBUSINESS_ACCOUNT_ID- Your WhatsApp Business Account ID (optional)
What is WHAM?
A type-safe TypeScript client for the WhatsApp Cloud API
wham is a modern TypeScript library that wraps the WhatsApp Cloud API in a clean, type-safe interface. It handles all the complexity of the Meta Graph API so you can focus on building great WhatsApp experiences.
Type-safe API
Full TypeScript support with autocomplete and compile-time error checking
WhatsApp Flows
Build multi-screen forms and experiences with the Flow builder
Template Builder
Create and manage message templates with a fluent API
Webhook Handler
Type-safe event handling with filters and middleware
How it works
wham acts as a bridge between your application and the WhatsApp Cloud API. When you call methods like sendMessage(), wham constructs the proper API request, handles authentication, and returns typed responses.
CLI Reference
Manage WhatsApp resources from the command line
The wham CLI helps you manage WhatsApp Business API resources declaratively. Define templates and flows in code, then sync them with Meta.
Commands
syncBring live WhatsApp resources into config and reconcile code, state, and remoteplanShow what changes would be applied to reach the declared stateapplyPlan and apply changes to sync declared resources with WhatsApppreviewStart a local preview server for flows and templatesstatusList tracked resources and their last-known status (offline by default)exploreBrowse, inspect, and filter live remote resources (read-only)auditProbe every resource in state against Meta for driftpromoteCut runtime traffic over to template versions Meta has approvedtaintMark a resource for forced recreation on the next applyuntrackDrop entries from deploy state without deleting them on MetacleanupDrain old deprecated entries from state (best-effort delete)stacksList all stacks declared in the configstateInspect or move deploy state (push / pull)skillsList or install the bundled Claude Code skillsdoctorDiagnose config, credentials, and environmentversionPrint the resolved wham version and install pathCommon Options
-c, --config <path> Deployment config (default: wham.config.ts)--stack <name> Stack to target (e.g. dev, prod)
Usage Examples
Onboard an existing account
# Discover live resources, generate typed# declarations, and seed statewham sync --stack dev# Non-interactive (CI)wham sync --stack dev --yes
Plan and apply changes
# Preview changeswham plan --stack dev# Apply (interactive)wham apply --stack dev# Apply in CI and wait until usablewham apply --stack dev --auto-approve --wait-for-approval
Inspect and manage resources
# List tracked resources and statuswham status --refresh# Browse live resources (read-only)wham explore# Force recreate a resource on next applywham taint flow:booking --reason "endpoint health stuck"# Local preview server for flows + templateswham preview --stack dev
Claude Code skills
# List the bundled agent skillswham skills list# Install them into .claude/skillswham skills install
Config File Format
// wham.config.tsimport { defineDeployment } from '@manano-ai/wham/deploy';import { resources } from './wham/resources';export default defineDeployment({stacks: {dev: {client: {token: () => process.env.WA_TOKEN,phoneNumberId: () => process.env.WA_PHONE_ID,businessAccountId: () => process.env.WA_BUSINESS_ID,},state: { file: 'wham/state.dev.json' },},},resources: () => resources,});
Command Options Reference
sync
--stack <name>, --yes, --target <kind:key>, --out <path>, --dry-runplan
--stack <name>, --show-orphans, --save <path>apply
--stack <name>, --auto-approve, --wait-for-approval, --parallelstatus / explore / audit
--stack <name>, --refresh, --jsontaint / untrack
<kind:key>, --reason <text>cleanup
--older-than <duration>, --target <kind:key>, --auto-approve, --dry-runskills
list, install [--dir <path>] [--force]Messaging API
Send text, media, buttons, lists, and more
Text Messages
Send simple text messages to any WhatsApp number.
// Simple text messageawait wa.sendMessage('+1234567890', 'Hello! How can I help you?');// The method returns the message IDconst messageId = await wa.sendMessage('+1234567890', 'Your order has shipped!');console.log('Sent message:', messageId);
Images
Send images by URL or media ID with optional captions.
// Send image by URLawait wa.sendImage('+1234567890', {link: 'https://example.com/product.jpg',caption: 'Check out our new product!',});// Send image by media ID (after uploading)await wa.sendImage('+1234567890', { id: 'MEDIA_ID_HERE' });
Documents
Send PDFs, spreadsheets, and other documents.
await wa.sendDocument('+1234567890', {link: 'https://example.com/invoice.pdf',caption: 'Here is your invoice',filename: 'invoice-2024.pdf',});
Location
Share locations with coordinates, name, and address.
await wa.sendLocation('+1234567890', 37.7749, -122.4194, {name: 'Our Office',address: '123 Main Street, San Francisco, CA',});
Interactive Buttons
Send up to 3 quick reply buttons for user selection.
await wa.sendButtons('+1234567890','How would you like to pay?',[{ id: 'pay_card', title: 'Credit Card' },{ id: 'pay_cash', title: 'Cash on Delivery' },{ id: 'pay_transfer', title: 'Bank Transfer' },],{header: 'Payment Options',footer: 'Choose your preferred method',});
Interactive Lists
Send a list with up to 10 items organized in sections.
await wa.sendList('+1234567890','Browse our menu and pick your favorites.',[{title: 'Pizzas',rows: [{ id: 'margherita', title: 'Margherita', description: 'Classic tomato & mozzarella' },{ id: 'pepperoni', title: 'Pepperoni', description: 'Spicy pepperoni & cheese' },],},{title: 'Drinks',rows: [{ id: 'cola', title: 'Cola', description: 'Refreshing cola drink' },{ id: 'water', title: 'Sparkling Water' },],},],{buttonText: 'View Menu',header: 'Our Menu',footer: 'Tap an item to order',});
Templates
Pre-approved message formats for notifications and marketing
Note: Templates must be approved by Meta before they can be used. They're required for messaging users outside the 24-hour customer service window.
Sending a Template
// Preferred: send a declared template by key, fully typed.// Named params map straight to the template's placeholders.await wa.template.orderUpdate.send('+1234567890', {name: 'Alice',status: 'on its way',});// Or send any approved template by nameawait wa.sendTemplate('+1234567890', 'hello_world', { code: 'en_US' });
Declaring a Template
Templates are declared as typed resources with the template() DSL. Add it to your resource set and deploy with wham apply — no manual approval calls.
import { template, example, Body, Footer, Button } from '@manano-ai/wham/templates';export const orderUpdate = template({id: 'order_update',language: 'en_US',category: 'UTILITY',params: {name: example('Alice'),status: example('on its way'),},view: ({ param }) => (<><Body>Hi {param.name}, your order is {param.status}.</Body><Footer>Reply STOP to opt out</Footer><Button.QuickReply payload="TRACK">Track order</Button.QuickReply><Button.QuickReply payload="HELP">Get help</Button.QuickReply></>),});// Add orderUpdate to your ResourceSet, then:// wham plan --stack dev// wham apply --stack dev
Template Components
Headers
Header.TextHeader.ImageHeader.VideoHeader.Document
Body
Body- with named paramsFooterBr- line breakexample()- sample values
Buttons
Button.QuickReplyButton.URLButton.PhoneNumberButton.Flow
WhatsApp Flows
Build multi-screen forms and interactive experiences
Pro tip: Flows are perfect for booking forms, surveys, registrations, and any multi-step data collection.
Declaring a Flow
Flows use a schema-first DSL: each screen() has typed data, form, submit, and view layers, wired together with a typed routing model.
import { Flow, routes } from '@manano-ai/wham';import {screen, expect, fields, field,dataExchange, complete,TextHeading, TextInput, Footer,} from '@manano-ai/wham/flows/schema';export const SELECT_SERVICE = screen({id: 'SELECT_SERVICE',data: expect<{ service: string }>({ service: 'haircut' }),form: () => fields({ service: field.text() }),submit: ({ form }) => dataExchange({ ...form.payload }),view: ({ form, submit }) => (<><TextHeading>Book an appointment</TextHeading><TextInput field={form.service} label="Service" required /><Footer label="Next" action={submit} /></>),});export const CONFIRM = screen({id: 'CONFIRM',data: expect<{ service: string }>({ service: 'haircut' }),submit: ({ data }) => complete({ service: data.service }),view: ({ data, submit }) => (<><TextHeading>Confirm {data.service}</TextHeading><Footer label="Book now" action={submit} /></>),});export const bookingFlow = (<Flowid="booking"publishcategories={['APPOINTMENT_BOOKING']}version="7.0"routingModel={routes([SELECT_SERVICE, [CONFIRM]])}>{SELECT_SERVICE}{CONFIRM}</Flow>);
Sending a Flow
Once deployed, send a declared flow by key. The starting screen is type-checked against the flow.
await wa.flow.bookingFlow.send('+1234567890', {body: 'Tap below to book your appointment.',token: 'booking-session-123',flowCta: 'Book now',screen: bookingFlow.screens.SELECT_SERVICE,flowData: { service: 'haircut' },});
Flow Building Blocks
Screen layers
data/ expect()form/ fields(), fieldsubmitview
View components
- TextHeading, TextBody
- TextInput, TextArea
- DatePicker, Dropdown
- RadioButtonsGroup, CheckboxGroup
- Footer
Submit actions
dataExchange()navigate()complete()
Webhooks & Events
Handle incoming messages and events with type safety
Setting up the Webhook Handler
import { WebhookHandler, filters } from '@manano-ai/wham';const handler = new WebhookHandler();// Handle all incoming messageshandler.onMessage((msg) => {console.log(`New message from ${msg.from}: ${msg.type}`);});// Handle only text messageshandler.onMessage(filters.text, async (msg) => {if (msg.type === 'text') {const text = msg.text.body.toLowerCase();if (text.includes('hello')) {await wa.sendMessage(msg.from, 'Hi there! How can I help?');}}});// Handle button clickshandler.onCallbackButton(async (event) => {console.log(`User clicked: ${event.title}`);if (event.data === 'get_support') {await wa.sendMessage(event.from, 'A support agent will be with you shortly.');}});// Handle list selectionshandler.onCallbackSelection(async (event) => {console.log(`User selected: ${event.title}`);});// Handle flow completionshandler.onFlowCompletion(async (event) => {console.log(`Flow completed: ${event.flow_token}`);console.log('Response data:', event.response);});
Express Middleware
import express from 'express';import { whatsappWebhook } from '@manano-ai/wham/express';const app = express();app.use('/webhook', whatsappWebhook({client,handler,verifyToken: process.env.VERIFY_TOKEN!,}));// Signature validation: set appSecret on the client// new WhatsApp({ token, phoneNumberId, appSecret })app.listen(3000);
Message Filters
Type Filters
filters.text- Text messagesfilters.image- Imagesfilters.document- Documentsfilters.location- Locationsfilters.audio- Audio/voicefilters.media- Any media
Content Filters
filters.contains('word')filters.startsWith('hi')filters.matches(/regex/)filters.command('help')filter1.and(filter2)filter1.or(filter2)
Ready to build?
Try the AI builder to generate your WhatsApp bot code, or get started with the library directly.