Every WhatsApp booking tool, ours included, sits on top of the same thing: the official Meta WhatsApp Cloud API. Before any assistant can answer a customer, someone has to connect a number to it. The Meta documentation covers each screen, but it does not tell you which parts quietly fail and leave you staring at a webhook that never fires.
This is the honest version, written after doing it and getting stuck.
What you need before you start
- A Facebook Business account, or the willingness to create one.
- A phone number that is not currently active on the WhatsApp app. This is the step most people get wrong. A number already registered on regular WhatsApp or WhatsApp Business app has to be deleted from that app first, and you lose that chat history. Use a fresh number if you can.
- A server with HTTPS. Meta will not send webhooks to plain HTTP, and self-signed certificates are rejected.
Nothing here needs a big server. Shared hosting with a valid certificate is enough for a single location business.
The shape of the setup
Meta renames its buttons regularly, so treat the labels below as landmarks rather than exact text. The shape has been stable for years even as the wording moved around.
- Create a Meta app at developers.facebook.com, of the Business type, and add the WhatsApp product to it.
- Meta gives you a test number immediately. It can only message up to five recipients that you add and verify by hand, but that is enough to build and check everything before your real number is involved.
- Note two identifiers from that screen, you will need both: the Phone number ID and the WhatsApp Business Account (WABA) ID. They are different numbers and mixing them up produces confusing API errors.
- Generate an access token. The one on this screen is temporary, see the warning below.
- Configure the webhook: a callback URL on your server and a verify token you invent yourself. Meta immediately calls your URL with that token and expects it echoed back. If your endpoint is not live yet, this step fails.
- Subscribe to the messages field. An unsubscribed webhook is a webhook that never fires.
- When everything works on the test number, add your real business number and verify it.
The traps
These are the ones that cost real time. None of them produce a useful error message.
The test token expires in 24 hours
The access token on the setup screen is a short lived one. It works perfectly, you build your whole integration on it, and the next morning every call fails with an authentication error that reads like your credentials are wrong.
They are not wrong. They are expired. For anything permanent you need a token generated from a System User in Business Settings, which does not expire on a timer. Do the test with the temporary token, then switch before you rely on it.
The app can be subscribed to the webhook and still receive nothing
This is the worst one, because every screen in the Meta panel looks correct. The webhook is configured, the messages field shows as subscribed, the verify handshake passed, and messages still never arrive.
The subscription on the app configuration screen is not the same thing as the app being subscribed to your WhatsApp Business Account. Check it directly:
GET https://graph.facebook.com/v23.0/{WABA_ID}/subscribed_apps
If your app is not in that list, subscribe it:
POST https://graph.facebook.com/v23.0/{WABA_ID}/subscribed_apps
(Use whichever Graph API version is current when you read this. The endpoint has been stable across versions; only the prefix moves.)
Messages start arriving immediately. There is no error state anywhere in the interface telling you this was missing.
Verify the webhook signature, and fail closed
Your callback URL is a public address that anyone can find and post JSON to.
Meta signs every request with an X-Hub-Signature-256 header computed from
your app secret. Check it, and reject anything that does not match.
Fail closed, meaning if the signature is missing or malformed, reject. It is tempting to be lenient during setup while you are debugging. Leniency that ships is an open endpoint that lets a stranger inject fake customer messages into your booking system.
Meta will retry, so handle duplicates
If your endpoint is slow or returns an error, Meta retries the delivery: immediately, then with decreasing frequency over the next 36 hours. Store the WhatsApp message ID and ignore an ID you have already processed. Without that, one delivery hiccup turns into a customer being answered twice, or worse, booked twice.
Templates are the only way to message first
Inside the 24 hour window after a customer writes to you, you can reply freely. Outside it, you can only send pre-approved template messages. Appointment reminders are therefore templates, and they must be submitted and approved before your first reminder can go out. Utility templates are usually approved quickly, often within minutes, but do not discover this requirement on the evening you planned to go live.
Keep promotions out of reminder templates. Mixed content gets reclassified into the more expensive marketing category automatically. We covered that in what WhatsApp appointment booking actually costs.
How to test without a real customer
Two things make this far less painful:
- Use the test number for everything until the integration is solid. Add your own phone as an allowed recipient and message yourself.
- Use a tunnel such as ngrok while developing, so Meta can reach your local machine over HTTPS. Remember that a free tunnel gets a new URL each restart, and Meta keeps calling the old one until you update the callback URL. A webhook that worked yesterday and is silent today is usually this.
Is it worth doing yourself?
For a developer, the Cloud API is genuinely well built. It is a normal REST API with normal webhooks, and once the traps above are behind you it is dependable.
What takes the time is not the connection, it is everything after it: deciding what to say, checking real availability before confirming anything, preventing two customers from taking the same slot, storing the conversation, and sending reminders on schedule.
That gap is exactly what Rendezo is. It is a self-hosted script that handles the Cloud API plumbing, the booking logic and the AI conversation, on your own server with your own keys. You still do the Meta setup above once, because that connection belongs to your business and not to a vendor. After that, the rest is configuration rather than code.