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
scaffoldDiscover existing resources and generate declarationsimportLink declared resources to existing remote resources by nameplanShow what changes would be applied to reach the declared stateapplyPlan and apply changes to sync declared resources with WhatsApprefreshCheck remote state for drift, status changes, and deleted resourcespreviewStart a local preview server for flows and templatestaintMark a resource for forced recreation on the next applyuntaintClear a previously set taintcleanupDrain old entries from state.deprecated[] (best-effort delete)registerRegister a phone number with the WhatsApp Business APIderegisterDeregister a phone number from the WhatsApp Business APICommon Options
-c, --config <path> Config file with credentials (default: wham.config.ts)-r, --resources <path> Resource declarations module (default: wham/resources.ts)-s, --state <path> State file path (default: .wham-state.json)
Usage Examples
Scaffold existing resources
# Discover all resourceswham scaffold# Scaffold specific templates and flowswham scaffold --template hello_world --flow signup_flow# Custom config and output pathswham scaffold -c prod.config.ts -r src/wa/resources.ts
Plan and apply changes
# Preview changeswham plan# Save plan to filewham plan --save plan.json# Apply changes (interactive)wham apply# Apply saved plan (CI/CD)wham apply --plan plan.json --auto-approve
Resource management
# Force recreate a resourcewham taint flow:signup --reason "endpoint health stuck"# Clear taintwham untaint flow:signup# Cleanup old deprecated entrieswham cleanup --older-than 90d --auto-approve# Preview server for flowswham preview --port 8888
Phone number registration
# Register phone numberwham register --pin 123456# Deregister phone numberwham deregister --pin 123456
Config File Format
// wham.config.tsexport default {token: process.env.WA_TOKEN,phoneNumberId: '...',businessAccountId: '...',};
Command Options Reference
scaffold
--dry-run, --template <name|id>, --flow <name|id>plan
--show-orphans, --save <path>apply
--show-orphans, -p/--plan <path>, --auto-approve, --wait-for-approval, --parallelpreview
--port <port>, --mock-data <path>refresh
--update-statetaint / untaint
<kind:key>, --reason <text>cleanup
--older-than <duration>, --target <kind:key>, --auto-approve, --dry-runregister / deregister
--pin <6-digit PIN> (required)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
import { BodyText, HeaderImage } from '@manano-ai/wham';// Simple template with no parametersawait wa.sendTemplate('+1234567890', 'hello_world', { code: 'en_US' });// Template with parametersconst body = new BodyText('Hello {{1}}, your order {{2}} has shipped!','Alice', // {{1}} example'ORD-1234' // {{2}} example);await wa.sendTemplate('+1234567890','order_shipped',{ code: 'en_US' },[body.toParams('Alice', 'ORD-1234')]);
Creating a Template
import {Template,HeaderText,BodyText,FooterText,QuickReplyButton} from '@manano-ai/wham';const tmpl = new Template('order_update', 'en_US').header(new HeaderText('Order {{1}}', 'ORD-5678')).body(new BodyText('Hi {{1}}, your order is {{2}}.', 'Alice', 'on its way')).footer(new FooterText('Reply STOP to opt out')).buttons(new QuickReplyButton('Track Order'),new QuickReplyButton('Get Help'));// Submit for approvalawait wa.createTemplate('BUSINESS_ACCOUNT_ID', {name: 'order_update',language: 'en_US',category: 'UTILITY',components: tmpl.toComponents(),});
Template Components
Headers
HeaderText- Text with variablesHeaderImage- Image headerHeaderVideo- Video headerHeaderDocument- Document header
Buttons
QuickReplyButton- Quick replyURLButton- Open URLPhoneNumberButton- CallFlowButton- Launch 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.
Creating a Flow
import {FlowJSON,FlowJSONVersion,Screen,Layout,TextHeading,TextInput,RadioButtonsGroup,Form,FlowFooter,NavigateAction,CompleteAction,} from '@manano-ai/wham';// Create the flow in Metaconst flowId = await wa.createFlow(BUSINESS_ID, {name: 'Appointment Booking',categories: ['APPOINTMENT_BOOKING'],});// Build the flow screensconst flow = new FlowJSON({version: FlowJSONVersion.LATEST,screens: [new Screen({id: 'SELECT_SERVICE',title: 'Book Appointment',layout: new Layout({children: [new TextHeading({ text: 'Choose a Service' }),new Form({name: 'service_form',children: [new RadioButtonsGroup({name: 'service',label: 'Service Type',dataSource: [{ id: 'haircut', title: 'Haircut - $30' },{ id: 'color', title: 'Hair Color - $80' },{ id: 'style', title: 'Styling - $50' },],required: true,}),],onSubmitAction: new NavigateAction({ next: 'CONTACT_INFO' }),}),new FlowFooter({label: 'Continue',onClickAction: { name: 'submit', payload: {} }}),],}),}),new Screen({id: 'CONTACT_INFO',title: 'Your Details',terminal: true,layout: new Layout({children: [new TextHeading({ text: 'Contact Information' }),new Form({name: 'contact_form',children: [new TextInput({name: 'name',label: 'Full Name',required: true}),new TextInput({name: 'phone',label: 'Phone Number',inputType: 'phone',required: true}),],onSubmitAction: new CompleteAction(),}),new FlowFooter({label: 'Book Now',onClickAction: { name: 'submit', payload: {} }}),],}),}),],});// Upload and publishawait wa.updateFlowJson(flowId, flow.toJSON());await wa.publishFlow(flowId);
Sending a Flow
await wa.sendFlow('+1234567890','Book your appointment online.',flowId,'unique-session-123', // Token to track this session{flowCta: 'Book Now',flowAction: 'navigate',screenId: 'SELECT_SERVICE',flowData: {preselected_service: 'haircut'},});
Flow Components
Text
- TextHeading
- TextSubheading
- TextBody
- TextCaption
- RichText
Inputs
- TextInput
- TextArea
- DatePicker
- Dropdown
- RadioButtonsGroup
- CheckboxGroup
Actions
- NavigateAction
- CompleteAction
- DataExchangeAction
- OpenURLAction
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({handler,verifyToken: process.env.VERIFY_TOKEN!,appSecret: process.env.APP_SECRET, // For signature validation}));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.