Documentation Index
Fetch the complete documentation index at: https://docs.photon.codes/docs/llms.txt
Use this file to discover all available pages before exploring further.
Most apps should use Spectrum - it gives you
a unified, higher-level API across WhatsApp Business, iMessage, and other
platforms. Reach for @photon-ai/whatsapp-business directly only when you
need low-level WhatsApp control that Spectrum doesn’t expose.
@photon-ai/whatsapp-business is a TypeScript SDK for the WhatsApp Business API. It connects to our managed gRPC gateway, which fronts Meta’s Cloud API — you get typed message sending, a resumable event stream, and media handling without wiring webhooks yourself.
Installation
npm install @photon-ai/whatsapp-business
Requires Node.js 18+ or Bun.
Credentials
Three values are required. Get them in one of two ways:
Sign up at app.photon.codes, toggle WhatsApp on in your project, finish the guided config, and copy the credentials.Use these directly with createClient, or pass them to spectrum-ts to combine WhatsApp with iMessage and other platforms behind one unified API.
- Create an app at developers.facebook.com and add the WhatsApp product. (docs)
- Under WhatsApp → API Setup, copy your
phone_number_id.
- Generate a permanent access token via a System User — the 24-hour token on the API Setup page is not suitable for production:
- Open Business Settings → System users, click Add, and create a user with Admin role.
- Assign assets → pick your app (enable Manage app) and your WhatsApp account (enable Manage WhatsApp Business Accounts).
- Generate new token, select your app, check the
whatsapp_business_messaging, whatsapp_business_management, and business_management scopes. Copy the token — it never expires. (docs)
- Under App Settings → Basic, copy your
app_secret.
- Under WhatsApp → Configuration, set the webhook (docs):
- Callback URL:
https://whatsapp-business.spectrum.photon.codes/webhook
- Verify token: anything (the handshake always returns the challenge)
- Subscribe to the
messages field
- Pass the three credentials to
createClient.
The webhook endpoint is free, public, and shared across developers. You don’t register with us, and we never persist your access_token or app_secret.
Quick start
import { createClient } from "@photon-ai/whatsapp-business";
const client = createClient({
accessToken: process.env.WA_ACCESS_TOKEN!,
phoneNumberId: process.env.WA_PHONE_NUMBER_ID!,
appSecret: process.env.WA_APP_SECRET!,
});
await client.messages.send({
to: "+15551234567",
text: "Hello from the SDK!",
});
for await (const event of client.events.subscribe()) {
if (event.type === "message") {
console.log(event.message);
}
}
createClient
const client = createClient(options: ClientOptions): WhatsAppClient;
| Option | Type | Description |
|---|
retry | boolean | RetryOptions | Enable automatic retry with exponential backoff for retryable errors. Pass true for default settings, or a RetryOptions object to customise the behaviour. |
timeout | number | Default timeout in milliseconds for unary RPC calls. Sets a deadline on each call unless one is already provided. |
The returned WhatsAppClient exposes three resources:
client.messages // send, markRead
client.events // subscribe, fetchMissed
client.media // upload, getUrl, delete
Disposing the client
The client implements Symbol.asyncDispose, so await using handles teardown automatically:
await using client = createClient({
accessToken: "...",
phoneNumberId: "...",
appSecret: "...",
});
// closed automatically when the block exits
Or close it explicitly:
Your first echo bot
import { createClient } from "@photon-ai/whatsapp-business";
const client = createClient({
accessToken: process.env.WA_ACCESS_TOKEN!,
phoneNumberId: process.env.WA_PHONE_NUMBER_ID!,
appSecret: process.env.WA_APP_SECRET!,
});
for await (const event of client.events.subscribe()) {
if (event.type !== "message") continue;
if (event.message.content.type !== "text") continue;
await client.messages.send({
to: event.message.from,
text: event.message.content.body,
});
}
event.type is narrowed to "message" or "status" — only messages get a .message field.
InboundContent is a discriminated union; switch on content.type before reading fields.
event.message.from is the WhatsApp ID of the sender — pass it straight to send.to.
Reconnection and missed events
The stream reconnects automatically when the connection drops, and fetches any events buffered in the meantime. See Events for cursor management and Error handling for when to retry.