Documentation

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

bash
npm install @manano-ai/wham

Note: This is a private repository. Join the waitlist to get access.

2. Initialize the client

typescript
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

typescript
// Send a text message
await wa.sendMessage('+1234567890', 'Hello from WHAM!');
// Send a message with buttons
await 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 token
  • PHONE_NUMBER_ID- Your WhatsApp phone number ID
  • BUSINESS_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.

Your App
wham
WhatsApp API
Users

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 remote
planShow what changes would be applied to reach the declared state
applyPlan and apply changes to sync declared resources with WhatsApp
previewStart a local preview server for flows and templates
statusList 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 drift
promoteCut runtime traffic over to template versions Meta has approved
taintMark a resource for forced recreation on the next apply
untrackDrop entries from deploy state without deleting them on Meta
cleanupDrain old deprecated entries from state (best-effort delete)
stacksList all stacks declared in the config
stateInspect or move deploy state (push / pull)
skillsList or install the bundled Claude Code skills
doctorDiagnose config, credentials, and environment
versionPrint the resolved wham version and install path

Common Options

bash
-c, --config <path> Deployment config (default: wham.config.ts)
--stack <name> Stack to target (e.g. dev, prod)

Usage Examples

Onboard an existing account

bash
# Discover live resources, generate typed
# declarations, and seed state
wham sync --stack dev
# Non-interactive (CI)
wham sync --stack dev --yes

Plan and apply changes

bash
# Preview changes
wham plan --stack dev
# Apply (interactive)
wham apply --stack dev
# Apply in CI and wait until usable
wham apply --stack dev --auto-approve --wait-for-approval

Inspect and manage resources

bash
# List tracked resources and status
wham status --refresh
# Browse live resources (read-only)
wham explore
# Force recreate a resource on next apply
wham taint flow:booking --reason "endpoint health stuck"
# Local preview server for flows + templates
wham preview --stack dev

Claude Code skills

bash
# List the bundled agent skills
wham skills list
# Install them into .claude/skills
wham skills install

Config File Format

typescript
// wham.config.ts
import { 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-run

plan

--stack <name>, --show-orphans, --save <path>

apply

--stack <name>, --auto-approve, --wait-for-approval, --parallel

status / explore / audit

--stack <name>, --refresh, --json

taint / untrack

<kind:key>, --reason <text>

cleanup

--older-than <duration>, --target <kind:key>, --auto-approve, --dry-run

skills

list, install [--dir <path>] [--force]

Messaging API

Send text, media, buttons, lists, and more

Text Messages

Send simple text messages to any WhatsApp number.

typescript
// Simple text message
await wa.sendMessage('+1234567890', 'Hello! How can I help you?');
// The method returns the message ID
const 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.

typescript
// Send image by URL
await 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.

typescript
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.

typescript
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.

typescript
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.

typescript
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

typescript
// 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 name
await 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.

typescript
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.Text
  • Header.Image
  • Header.Video
  • Header.Document

Body

  • Body - with named params
  • Footer
  • Br - line break
  • example() - sample values

Buttons

  • Button.QuickReply
  • Button.URL
  • Button.PhoneNumber
  • Button.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.

typescript
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 = (
<Flow
id="booking"
publish
categories={['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.

typescript
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(), field
  • submit
  • view

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

typescript
import { WebhookHandler, filters } from '@manano-ai/wham';
const handler = new WebhookHandler();
// Handle all incoming messages
handler.onMessage((msg) => {
console.log(`New message from ${msg.from}: ${msg.type}`);
});
// Handle only text messages
handler.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 clicks
handler.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 selections
handler.onCallbackSelection(async (event) => {
console.log(`User selected: ${event.title}`);
});
// Handle flow completions
handler.onFlowCompletion(async (event) => {
console.log(`Flow completed: ${event.flow_token}`);
console.log('Response data:', event.response);
});

Express Middleware

typescript
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 messages
  • filters.image - Images
  • filters.document - Documents
  • filters.location - Locations
  • filters.audio - Audio/voice
  • filters.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.