I recently built a proactive-notification channel on top of the WhatsApp Cloud API for a small business — the kind of thing that sends "your X is ready" or "there's an outage at location Y" straight to a customer's WhatsApp, triggered by a backend system rather than a human typing.
On paper it's a few API calls. In practice I lost hours to failures that returned HTTP 200 and looked successful, to a UI button that was simply broken, and to rules Meta enforces but doesn't surface until you trip over them.
This is the write-up I wish I'd found. Every ID, token, and number below is a placeholder — swap in your own. Cloud API vs. the WhatsApp Business app — pick the right tool first
If your goal is proactive messaging — notifying a customer who has not messaged you first — the WhatsApp Business app on a phone will not do it. It can only reply inside an open conversation window.
Proactive, system-triggered messages require: the Cloud API (or on-premise API), and a pre-approved message template.
The Cloud API is a pipe, not a faucet. There is no screen where someone logs in and types a message. It only accepts calls from a program. To actually operate it you either build an integration or plug in a third-party platform. Out of the box, all you can do is send via curl.
Decide this on day one. It changes your whole architecture. The silent killer: accepted does not mean delivered
HTTP 200. A valid wamid. And the message never arrived — not on my phone, not on a second test phone. No error anywhere.
Root cause: the Meta app was still in Development / Unpublished mode. In that mode Meta accepts the API call and returns success but does not deliver the message. The failure is 100% silent.
The only prerequisite is a Privacy Policy URL under App settings → Basic. It has to resolve to a real page with real content or validation fails. Once published, the exact same request delivered on the first try.
Lesson: accepted ≠ delivered. Without webhooks configured you have no way to tell the difference. If the API accepts your message but nothing arrives, check the app mode before any other hypothesis. When the Meta UI lies, go straight to the Graph API
To activate a number you have to register it. The dashboard has a "Register" button for this. In my case that button returned:
…every time, with no real cause. I burned hours ruling things out: the SIM had never had WhatsApp installed, the OTP was entered correctly, two-step verification was off, it wasn't a rate limit (same failure after 22h), and business verification isn't required to register.
The button was simply bugged. The identical operation via the Graph API worked on the first attempt:
Lesson: when Meta's UI throws a generic error, hit the Graph API directly. It either returns the real error (far more actionable than "try again") or, as here, just works because the UI was the only broken part.
The same applies to reading state. The developer dashboard often shows stale status. The API is the source of truth: Template gotchas
Templates are where a surprising number of rules hide. Category = money. Choose Utility, not Marketing, whenever the message is transactional (status, alert, confirmation). In some markets Marketing costs on the order of ~12× more per message. Miscategorizing is a silent budget leak. helloworld only sends from the test number. You cannot validate your production pipe with the sample template. From a real number you get:
Plan for it: you can't do an end-to-end send from your real number until your own template is approved. (A failed helloworld attempt still proves token validity, sender acceptance, active billing, and API reachability — the infra is fine, you're just waiting on the template.) Keep v1 dead simple. No header, no footer, no buttons approves faster. Buttons don't render on WhatsApp Web. A template with buttons can show This message couldn't load on desktop. Always verify on a phone, not Web. Editing or resending a pending template restarts the review queue. Submit once, then wait. Approval is usually well under 48h; past that, contact support instead of poking it. Watch the message validity period (TTL). The form defaults to a short validity (as little as 10 minutes) when you don't set a custom one. For a time-sensitive alert that's fine; for something useful for hours, a phone that was briefly off will silently expire the message. It's editable later — but review it before you operate at volume.
