BotNetwork

WhatsApp Cloud API: send your first message (and fix the errors)

The WhatsApp Cloud API is free to start testing, but the first message trips up many developers because of templates, test recipients and token lifetimes. Here is the shortest reliable path.

Published 4 min read

What you need

  • A Meta developer account and a Meta Business portfolio (the setup flow creates one if needed).
  • A phone with WhatsApp to receive test messages.
  • curl or any HTTP client. No server is needed to send; you only need one to receive (webhooks).

Background on how WhatsApp compares with other platforms: Telegram vs Discord vs WhatsApp. The full architecture is in our WhatsApp Cloud API guide.

1. Create the app and open API Setup

  1. In developers.facebook.com/apps, create an app and choose the WhatsApp use case (the flow is labeled around connecting with customers through WhatsApp; Meta renames these screens from time to time).
  2. Open WhatsApp > API Setup. You get: - a test business phone number and its Phone number ID (not the phone number itself), - a WhatsApp Business Account ID, - a temporary access token that expires after a short time (about a day at the time of writing).
  3. Under "To", add your own number as a recipient and confirm the code WhatsApp sends you. The test number can only message a small allow-list of verified recipients.

2. Send the hello_world template

Business-initiated conversations must start with an approved template. Every new account has the sample hello_world template:

export WA_TOKEN="EAAG..."              # temporary token from API Setup
export PHONE_ID="1234567890"           # Phone number ID, not the number
export TO="15551234567"              # recipient in international format, digits only

curl -s "https://graph.facebook.com/v23.0/$PHONE_ID/messages" \
  -H "Authorization: Bearer $WA_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"messaging_product\": \"whatsapp\",
    \"to\": \"$TO\",
    \"type\": \"template\",
    \"template\": { \"name\": \"hello_world\", \"language\": { \"code\": \"en_US\" } }
  }"

v23.0 is the Graph API version shown in Meta's examples at the time of writing; use the version your dashboard shows. A success response contains a messages[0].id starting with wamid.. That ID is how you match later status webhooks (sent, delivered, read).

3. Reply, then send free-form text

When the recipient replies to your template, a 24-hour customer service window opens. Inside it you can send normal messages:

curl -s "https://graph.facebook.com/v23.0/$PHONE_ID/messages" \
  -H "Authorization: Bearer $WA_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"messaging_product\": \"whatsapp\",
    \"recipient_type\": \"individual\",
    \"to\": \"$TO\",
    \"type\": \"text\",
    \"text\": { \"body\": \"Hi! This came from the Cloud API.\" }
  }"

After 24 hours without a message from the user, you are back to templates only. Buttons and lists can be designed in our WhatsApp message builder.

4. Replace the temporary token

The temporary token is fine for a first test, not for a bot. Create a system user in Business Settings, assign it your app and WhatsApp account, and generate a token with the whatsapp_business_messaging and whatsapp_business_management permissions (Meta's getting-started guide also lists business_management). Store it like any secret: see securing bot tokens.

5. Receive messages with a webhook

To read replies you need an HTTPS endpoint that:

  1. Answers the verification GET: if hub.mode is subscribe and hub.verify_token equals the value you chose in the dashboard, return hub.challenge as plain text.
  2. Accepts POST notifications and checks the X-Hub-Signature-256 header, an HMAC-SHA256 of the raw body keyed with your app secret.
  3. Returns 200 quickly.
# FastAPI sketch
import hmac, hashlib, os
from fastapi import FastAPI, Request, Response

app = FastAPI()

@app.get("/webhook")
def verify(request: Request):
    q = request.query_params
    if q.get("hub.mode") == "subscribe" and q.get("hub.verify_token") == os.environ["WA_VERIFY"]:
        return Response(q.get("hub.challenge"), media_type="text/plain")
    return Response(status_code=403)

@app.post("/webhook")
async def receive(request: Request):
    raw = await request.body()
    sig = "sha256=" + hmac.new(os.environ["WA_APP_SECRET"].encode(), raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(sig, request.headers.get("X-Hub-Signature-256", "")):
        return Response(status_code=401)
    # enqueue the payload and return immediately
    return Response(status_code=200)

Then subscribe the app to the messages field in the WhatsApp webhook settings. Our signature generator helps you test the HMAC check locally. Hosting options: deploying a WhatsApp bot.

Errors you will probably see

Code Meaning Fix
131030 Recipient phone number not in allowed list Add and verify the number under API Setup (test numbers only).
131047 Re-engagement message: more than 24 hours since the user's last message Send an approved template instead of free text.
132001 Template does not exist Check the template name and language code exactly.
190 Access token expired or invalid Generate a new token; move to a system user token.
100 Invalid parameter Usually a malformed to number or JSON field; read error.message.

Costs

Meta charges per delivered template message, with rates that depend on the category and the recipient's country, and service replies inside the customer service window have been free under the current model. Prices change, so check Meta's official pricing page; our WhatsApp cost calculator helps you estimate volumes.

Frequently asked questions

Can I use my personal WhatsApp number?

A number registered on the Cloud API cannot be used in the regular WhatsApp or WhatsApp Business app at the same time. Use a dedicated number for production.

Why did my template message return success but never arrive?

The API accepted it, but delivery failed later. Check the status webhook for that wamid; failures arrive there with an error code.

Do I need a verified business to start?

Not for testing with the test number. Higher messaging limits and some features require business verification, as described in Meta's documentation.

Sources