ChloeGet Started

Docs

WhatsApp

Putting an agent on a WhatsApp number through Meta's own API, delivered through your web server, and every option.


Meta posts each message to a public address and has nothing chloe can fetch from, so your own web server takes the delivery: it passes one path, /chloe/v1/<agent>/<name>, on to chloe's port, and nothing else.

The number has to be registered with Meta, and cannot be one already in the WhatsApp app.

  1. At developers.facebook.com, make an app and add WhatsApp to it. It gives you a number to try with, its id, and a token that lasts a day. A permanent token comes from a system user with the whatsapp_business_messaging permission. The app secret is on the app's settings page.
  2. Put the three in .env as CHLOE_AGENTS_<ID>_WHATSAPP_PHONE_NUMBER_ID, CHLOE_AGENTS_<ID>_WHATSAPP_TOKEN and CHLOE_AGENTS_<ID>_WHATSAPP_APP_SECRET, and in the config under agents: { <id>: { whatsapp: { phone_number_id, token, app_secret } } }, as the config on Channels does.
  3. Add the channel to the agent. The shop's hands every message to a job:
// The shop's WhatsApp number, where customers write in. The number's id, a
// token and the app secret go in .env, and chloe.config.ts hands them over
// under `agents: { shop: { whatsapp: { ... } } }`. Without them the channel
// says so and does not start.
//
// Meta posts each message to a public address and has nothing to fetch one
// with, so the shop's web server passes /chloe/v1/shop/whatsapp on to chloe's
// port. The address to paste into the app is written to the log on start.
//
// Anybody may write, so no message is a turn with the agent's tools. Every one
// goes to answer-whatsapp-customer, which is code: it looks the number up
// first, and a number on no account is answered without asking a model. The job
// is named here and nowhere else, so nothing but a message on this number
// starts it. A channel for some people only lists their numbers in allowFrom,
// in full international form.
import { whatsappChannel } from "@chloejs/core/channels";

import answerWhatsappCustomer from "../jobs/answer-whatsapp-customer.ts";

export default whatsappChannel({ job: answerWhatsappCustomer });
  1. Start chloe. It writes the route to the log, with the word Meta checks it with. On the app's WhatsApp page, register your web server's address for that route as the webhook, with that word, subscribed to messages.
  2. Write to the number from your phone. With allowFrom set to [], it answers with your number, which is what goes in it.

Besides the options every channel takes:

Option Default What it controls
allowFrom anybody Numbers in full international form, like "+447700900123". Unset, anybody may write, so pair that with a job or a short tools list.
publicUrl none Where your web server reaches the route, so the log prints the whole address.
uploadPolicy pictures, PDFs and text, up to 10 MB As on Telegram.
credentials the three in settings { phoneNumberId, token, appSecret, verifyToken }. verifyToken is the word Meta checks the address with, made on start when unsaid.

Every delivery carries Meta's signature, and chloe checks it against your app secret, which never leaves your machine. So your web server cannot make up a message, and a channel with no app secret refuses everything.

Two of WhatsApp's own rules: a message to somebody has to be within 24 hours of the last one they sent, so a job asking somebody who has not written today is refused; and there are no groups. A question with three answers or fewer arrives as buttons.