Guides

WhatsApp Cloud API Developer Guide: Webhooks, Limits, Errors and Code

The parts of Meta's WhatsApp Cloud API docs you need on day one — webhook handshake and payload shape, the limits that actually bite, the errors you will see, and working Node.js and PHP calls.

By the wbm.link teamUpdated 14 September 2026 9 min read

Meta's documentation for the WhatsApp Business Platform is thorough, but it is spread across dozens of pages, and the numbers that matter in production — how fast you can send, what a webhook looks like, what error 131047 means — sit in different places. This guide pulls them together, checked against Meta's docs in September 2026. If you have not created your app and test number yet, start with the WhatsApp Cloud API setup guide.

WhatsApp Business API REST API documentation: where it lives

The Cloud API is not a separate service with its own SDK — it is a set of endpoints on Meta's Graph API, called over HTTPS at graph.facebook.com with a bearer token. Everything official is on Meta for Developers:

  • WhatsApp Business Platform docs — guides for getting started, messages, templates, webhooks, phone numbers and limits. Older links under developers.facebook.com/docs/whatsapp redirect or still work.
  • API reference — each endpoint (messages, phone numbers, WhatsApp Business Account, templates, media) with fields and sample requests.
  • Graph API changelog — the current version. At the time of writing that is v26.0, released 29 July 2026. Put the version in every URL and upgrade deliberately.
  • Error codes page — the full list of Cloud API error codes, summarised below.
  • Webhooks reference — one page per webhook field, with sample payloads for each message and status type.

The two IDs you use most: the Phone Number ID for sending (POST /PHONE_NUMBER_ID/messages) and the WhatsApp Business Account ID for templates, phone number lists and webhook subscriptions.

Webhooks: verification first

Incoming messages and delivery updates reach you only through webhooks. When you save a callback URL in the app's WhatsApp configuration, Meta first sends a GET request to it with three query parameters:

  • hub.mode — always subscribe.
  • hub.verify_token — the string you typed into the Verify Token field.
  • hub.challenge — a value you must send back.

Your endpoint checks that hub.verify_token matches your own secret and responds with status 200 and the hub.challenge value as the body. Anything else and Meta will not save the URL. Then subscribe the app to the messages webhook field.

After that, every event arrives as a POST with a JSON body and an X-Hub-Signature-256 header containing sha256= followed by an HMAC-SHA256 of the raw request body, keyed with your app secret. Compute the same HMAC over the raw bytes and compare before trusting the payload — parse the JSON only after the check.

WhatsApp Business API webhook payload structure

Every WhatsApp webhook has the same envelope: object is whatsapp_business_account; entry is an array whose items carry an id (the WABA ID) and a changes array; each change has a field (for example messages) and a value holding the event. Inside value for the messages field you will find:

Field in valueWhat it tells you
messaging_productAlways whatsapp
metadata.display_phone_numberYour business number, as digits
metadata.phone_number_idWhich of your numbers the event belongs to — route on this
contacts[].profile.name, contacts[].wa_idThe customer's WhatsApp profile name and WhatsApp ID
messages[]Incoming messages: from, id (a wamid), timestamp (Unix seconds), type, and a type-specific object such as text.body
statuses[]Updates on messages you sent: id (the wamid you got when sending), status, timestamp, recipient_id, and conversation and pricing objects with billing details
statuses[].errorsPresent when status is failed — carries the error code and title

A trimmed incoming text message, as in Meta's reference:

{"object":"whatsapp_business_account","entry":[{"id":"WABA_ID","changes":[{"field":"messages","value":{"messaging_product":"whatsapp","metadata":{"display_phone_number":"15550783881","phone_number_id":"PHONE_NUMBER_ID"},"contacts":[{"profile":{"name":"Sheena Nelson"},"wa_id":"16505551234"}],"messages":[{"from":"16505551234","id":"wamid.HBgL...","timestamp":"1749416383","type":"text","text":{"body":"Does it come in another color?"}}]}}]}]}

A status update uses the same envelope, with statuses in place of contacts and messages:

"statuses":[{"id":"wamid.HBgL...","status":"delivered","timestamp":"1750263773","recipient_id":"16505551234","pricing":{"billable":true,"category":"service"}}]

Status values move through sent, delivered and read, or end at failed. They can arrive out of order, and one POST can batch several entries or changes — loop over every level rather than reading index 0.

WhatsApp Business API webhook integration example

A minimal Node.js (Express) handler, with the verify token and app secret in environment variables:

  1. app.get("/webhook", (req, res) => { const ok = req.query["hub.mode"] === "subscribe" && req.query["hub.verify_token"] === process.env.VERIFY_TOKEN; ok ? res.status(200).send(req.query["hub.challenge"]) : res.sendStatus(403); });
  2. app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => { const sig = "sha256=" + crypto.createHmac("sha256", process.env.APP_SECRET).update(req.body).digest("hex"); if (sig !== req.get("x-hub-signature-256")) return res.sendStatus(401); res.sendStatus(200); handle(JSON.parse(req.body)); });
  • Answer 200 fast, work later. Acknowledge the POST, then push the payload to a queue or background job. If Meta does not get a 200, it retries with decreasing frequency for up to 7 days.
  • Deduplicate on the wamid. Retries mean the same message can arrive twice.
  • Expect big bodies. WhatsApp webhook payloads can be up to 3 MB. Media arrives as an ID you download separately, not as bytes in the webhook.
  • Use a timing-safe comparison for the signature in production (crypto.timingSafeEqual in Node).

WhatsApp Business API rate limits explained

“Rate limit” means four different things on the Cloud API. Mixing them up is why teams raise their throughput and still get errors.

LimitWhat Meta documentsError code or remedy
Throughput (per business number)80 messages per second by default; automatically upgraded up to 1,000 mps for eligible numbers. Numbers also on the WhatsApp Business app (coexistence) are fixed at 20 mps.130429
Pair rate (to one customer)1 message every 6 seconds to the same WhatsApp user, with bursts of up to 45 messages in 6 seconds that must be paid back by waiting131056
Messaging limit (unique customers)Business-initiated conversations with unique users in a rolling 24 hours: 250 for new portfolios, then 2,000, 10,000, 100,000 and unlimited. Set per business portfolio and shared by all its numbers.Raise it through verification and good quality, not retries
Graph API request rateRequests per app per WhatsApp Business Account in a rolling hour: 200 by default, 5,000 for an active WABA with a registered number4 (app) or 80007 (WABA)

The automatic throughput upgrade to 1,000 mps requires an unlimited messaging limit, messaging 100,000 or more unique users outside the customer-service window in a moving 24 hours, and a quality rating of yellow or better. The upgrade can take up to a minute, during which sends return 131057.

WhatsApp Business API error codes list

Errors come back in the API response as an error object with a code, and for asynchronous failures in the errors array of a failed status webhook. The codes you will meet most often, from Meta's error codes page:

CodeMeaningWhat to do
0Authentication failedGet a new access token — the current one expired or was invalidated
190Access token has expiredGenerate a new token; move to a system user token for production
3 / 10Capability or permission not granted, or removedCheck the token's permissions in the access token debugger
100Invalid parameter — unsupported or misspelledCompare the request body with the endpoint reference
4App has hit its API call rate limitBack off and retry later
80007WhatsApp Business Account has reached its rate limitReduce request frequency; retry later
130429Cloud API message throughput reachedSlow down sending or queue messages
131026Message undeliverable — not on WhatsApp, old app version or terms not acceptedConfirm the number with the customer; do not retry blindly
131042Payment method problemFix the payment method or credit line on the WABA
131047More than 24 hours since the customer last repliedSend an approved template instead of free-form text
131049Not delivered to maintain healthy ecosystem engagementDo not resend immediately; wait at least 24 hours
131050Customer has stopped marketing messages from youDo not retry; respect the opt-out
131051Unsupported message typeUse a type listed in the messages reference
131056Too many messages to the same recipient in a short timeWait before messaging that user again
132000Template variable count does not matchSend exactly as many parameters as the template defines
132001Template does not exist in that language or is not approvedCheck the template name, language code and approval status
132015 / 132016Template paused / permanently disabled for low qualityEdit the template, or create a new one if disabled
133010Phone number not registered on the platformCall the register endpoint for the number
368Account restricted for a policy violationReview the enforcement notice in WhatsApp Manager

Log the full error object, not just the code — the message and any details alongside it usually say which parameter or rule failed. Meta's error codes page lists every code, including the registration and two-step verification errors (the 133xxx range) you meet when adding numbers.

Sending messages from your code

Every send is the same request: POST to https://graph.facebook.com/v26.0/PHONE_NUMBER_ID/messages with a bearer token and a JSON body containing messaging_product whatsapp, the recipient in to (international format, no plus sign), a type, and an object for that type.

WhatsApp Business API Node.js integration example

Node.js 18 and later ship fetch, so no library is needed. Sending an approved template:

  1. const url = "https://graph.facebook.com/v26.0/" + process.env.PHONE_NUMBER_ID + "/messages";
  2. const res = await fetch(url, { method: "POST", headers: { Authorization: "Bearer " + process.env.WA_TOKEN, "Content-Type": "application/json" },
  3. body: JSON.stringify({ messaging_product: "whatsapp", to: "91XXXXXXXXXX", type: "template", template: { name: "order_update", language: { code: "en" }, components: [{ type: "body", parameters: [{ type: "text", text: "A1042" }] }] } }) });
  4. const data = await res.json(); if (!res.ok) throw new Error(data.error.code + ": " + data.error.message);
  5. console.log(data.messages[0].id); // wamid — match it to status webhooks

For a free-form reply inside the 24-hour window, change the body to type text with text: { body: "Your order ships today" }.

WhatsApp Business API PHP SDK example (cURL)

Meta does not publish a PHP SDK for the WhatsApp Cloud API. PHP's built-in cURL extension is all you need to call the REST API — here, a text reply:

  1. $payload = json_encode(["messaging_product" => "whatsapp", "to" => "91XXXXXXXXXX", "type" => "text", "text" => ["body" => "Your order ships today"]]);
  2. $ch = curl_init("https://graph.facebook.com/v26.0/" . getenv("PHONE_NUMBER_ID") . "/messages");
  3. curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_POSTFIELDS => $payload,
  4. CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("WA_TOKEN"), "Content-Type: application/json"]]);
  5. $result = json_decode(curl_exec($ch), true); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
  6. if ($status !== 200) { error_log($result["error"]["code"] . ": " . $result["error"]["message"]); }

Third-party PHP packages for the Cloud API are available on Packagist and GitHub. They are not maintained by Meta, so check that a package tracks recent Graph API versions before you depend on it.

A production checklist

  • Use a system user token stored as a server secret, never a temporary token.
  • Pin the Graph API version in one config value and plan upgrades from the changelog.
  • Verify signatures on every webhook and acknowledge within your handler before heavy work.
  • Queue outbound sends and respect throughput (130429) and pair rate (131056) with backoff.
  • Store wamid → your record so status webhooks update the right message.
  • Handle 131047 by switching to a template, and 131050 by marking the contact opted out.
  • Watch template quality — a paused template (132015) will fail every send until fixed.

Building this versus using a panel

Everything above is what it takes to send and receive reliably. A team inbox, agent assignment, template management for non-developers, broadcasts and a chatbot are further projects on top. If WhatsApp is a feature inside your own product, build on the Cloud API directly.

If your goal is simply for your team to work on WhatsApp, wbm.link gives you that as a no-code panel on the WhatsApp Business Platform — shared inbox, template builder that submits to Meta, broadcasts to labels or CSV contacts, keyword auto-replies, a flow chatbot and an AI assistant — for ₹999 per connected number per month with unlimited team members. It has no public API or customer webhooks, so it is not a replacement for a custom integration; it is the choice when you do not want to write one.

No webhooks to host

Connect your WhatsApp number to wbm.link and your team can reply, broadcast and automate from day one — without writing or maintaining integration code.

Try wbm.link

FAQ

Frequently asked questions

Something else on your mind? Ask us.

Where is the WhatsApp Cloud API documentation for developers?

On Meta for Developers, under the WhatsApp Business Platform section at developers.facebook.com. It contains the get-started guide, API reference, webhooks, limits and the error code list. The Graph API changelog there shows the current API version.

How do I verify a WhatsApp Cloud API webhook?

Meta sends a GET request with hub.mode set to subscribe, hub.verify_token and hub.challenge. Check that the verify token matches the one you entered in the app dashboard and respond with status 200 and the hub.challenge value. Validate later POST requests with the X-Hub-Signature-256 header using your app secret.

What are the WhatsApp Business API rate limits?

Business numbers send up to 80 messages per second by default, automatically upgradable to 1,000 for eligible numbers. You can send one message every 6 seconds to the same user, with limited bursts. Messaging limits cap unique customers per 24 hours from 250 up to unlimited, and Graph API requests are limited per app per WhatsApp Business Account per hour.

What does WhatsApp error 131047 mean?

More than 24 hours have passed since the customer last messaged you, so the customer-service window is closed. You can only send an approved template message until the customer replies.

Is there an official WhatsApp Business API PHP SDK?

No. Meta does not publish a PHP SDK for the WhatsApp Cloud API. You call the REST endpoints directly with PHP's cURL extension or an HTTP client, or use a community package that Meta does not maintain.

Does Meta have an official Node.js SDK for the WhatsApp Cloud API?

Meta published one on GitHub, but the repository was archived in June 2023 and is read-only. Most Node.js integrations now call the REST API with fetch or an HTTP client.

Related features

More from the blog

Put your whole team on WhatsApp today.

Create your account, connect your number with Meta's official sign-up and start replying from one shared inbox.