I run a WhatsApp Business Platform, so I spend a lot of time watching the Meta Cloud API do things the documentation does not prepare you for. None of what follows is secret. It is just scattered, or only discoverable after it has already cost you a day.
Nine things I wish someone had written down.
1. A new number can only start 250 conversations a day
Register a number today, plan a 10,000-contact launch broadcast for tomorrow, and it will not send.
Every newly registered number begins on the 250 tier: it can start conversations with 250 unique customers per rolling 24 hours. The ladder from there is 250 → 1,000 → 10,000 → 100,000 → unlimited, and Meta raises you automatically as volume and quality hold.
Two consequences people miss:
- Replies inside an open 24-hour window do not count. The tier limits business-initiated conversations only. A support-heavy number can talk to thousands of people a day on the 250 tier.
- Quality rating moves the tier both ways. Send templates people mute or report and you go down, not up.
If you are migrating a brand with a real list, plan for the ramp. It is not a setting you can ask to have raised on day one.
2. /register has a rate limit that gets worse when you retry
This one cost a customer of mine most of a day.
POST /{phone_number_id}/register
→ 400
(#133016) Registration or Deregistration failed because there were
too many attempts for this phone number in a short period of time
Error 133016 is a per-number limit on register/deregister. The critical part: it is not a fixed cooldown you can wait out predictably — each additional attempt extends it. A user who taps your "Activate" button eight times in thirty seconds because nothing appears to happen will lock that number out for hours.
If you build a UI around registration, put a server-side cooldown behind the button. Client-side is not enough — mobile apps hit the same endpoint.
3. The useful error message is not in message
Meta's error envelope buries the sentence you actually need:
{
"error": {
"message": "(#100) Invalid parameter",
"code": 100,
"error_data": {
"details": "Phone Link to WABA Failed - Unverified WABA: You cannot
proceed with this operation since your WhatsApp Business
account is not verified..."
}
}
}
If you surface error.message to your users — as most integrations do — they see "(#100) Invalid parameter" and learn nothing. The explanation is in error.error_data.details, and it frequently contains HTML markup you will want to strip.
Read error_data.details first, fall back to error_user_title / error_user_msg, and only then message.
4. status: PENDING means the number cannot send anything
GET /{phone_number_id}?fields=status,platform_type,display_phone_number
A number that has been connected but never successfully registered sits at status: PENDING with platform_type: NOT_APPLICABLE. It looks connected in your database. It can neither send nor receive.
Check status == "CONNECTED" before you tell a user they are live. Storing a phone_number_id is not the same as having a working number.
5. +1 555-xxx-xxxx is a Meta test number
Meta issues a free test number with every app, and it is formatted like a real US number. Users going through Embedded Signup pick it more often than you would think, then wonder why nothing works.
Test numbers can only message up to five pre-approved recipients. If display_phone_number starts with +1 555, that is what you are looking at — surface it clearly rather than showing "Connected".
6. Coexistence does not change platform_type
Coexistence lets a number stay live in the WhatsApp Business app and work through the Cloud API. I assumed platform_type would tell me which numbers were in that mode. It does not — coexistence numbers still report CLOUD_API.
The field that actually distinguishes them is is_on_biz_app: true. I found this out by comparing a number I knew was in coexistence (it was emitting smb_message_echoes) against one I knew was not.
7. Coexistence has a 7-day age requirement and a 6-month history replay
Two numbers to plan around:
- The number must have been live on the WhatsApp Business app for at least 7 days before it can be linked.
- After onboarding, Meta replays up to 6 months of that number's existing chats to your webhook as
historyevents.
That second one is easy to miss entirely, which brings me to the next point.
8. You must subscribe to history and smb_app_state_sync explicitly
Webhook fields are subscribed at the app level:
GET /{app-id}/subscriptions
messages is almost always there. history (the chat replay), smb_app_state_sync (the phone's contact book) and smb_message_echoes (messages the business sent from the phone app) frequently are not. If they are missing, Meta sends them to nobody and you will spend a long time debugging code that was never going to receive anything.
Two things worth knowing about the payload shape:
- History messages are nested at
value.history[].threads[].messages[]— not atvalue.messages. If you handle them in your normal inbound loop by accident, you will replay six months of old chats through your automation and message people who have not talked to you since March. Guard it. - Direction is not reliably in
history_context.from_me. The documented signal is the presence of atofield, which appears only on business-sent messages.
9. Imported history breaks chronological order if you sort by insertion
If your inbox pages messages by autoincrement id — which is the obvious implementation — a six-month history import lands after today's messages, because the rows are newer even though the messages are older.
SQLite lets you insert explicit negative rowids, including on an AUTOINCREMENT table, and sqlite_sequence is unaffected. Deriving the id from the message's own timestamp puts imported history in correct order below every live row:
id = timestamp * 1000 + sequence - 4_000_000_000_000_000
Always negative, always monotonic in time. Whatever your database, the general point stands: decide deliberately how imported history sorts against live messages, because the default will be wrong.
One billing note
Meta moved from per-conversation to per-message billing on 1 July 2025. It is also reported that from 1 October 2026, free-form replies inside the 24-hour service window — free until now — become billable. Check Meta's current pricing docs before you model anything on "replies are free".
I work on QuickWA, a WhatsApp Business Platform built on the Cloud API. Everything above came out of running it, not out of the docs. If you have hit something on this list — or something that should be on it — I would genuinely like to hear it.
Top comments (0)