Send Messages
POST to Meta Graph API v24.0 with a Bearer token. Same shape for sandbox through the proxy and production direct.
The Basic Call
Every send is a POST to ${WHATSAPP_API_URL}/${WHATSAPP_PHONE_NUMBER_ID}/messages with Authorization: Bearer ${WHATSAPP_ACCESS_TOKEN} and a JSON body whose first key is messaging_product: "whatsapp".
The shape is identical for sandbox (through the HookMyApp proxy) and production (direct to Meta). For the full message-object reference see Meta's Cloud API docs.
curl Example
curl -X POST "${WHATSAPP_API_URL}/${WHATSAPP_PHONE_NUMBER_ID}/messages" \
-H "Authorization: Bearer ${WHATSAPP_ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"messaging_product": "whatsapp",
"to": "+15551234567",
"type": "text",
"text": { "body": "Hello from my app" }
}'Node/Express Example
Verbatim from the webhook starter kit.
export async function sendMessage(to, text) {
const url = `${process.env.WHATSAPP_API_URL}/${process.env.WHATSAPP_PHONE_NUMBER_ID}/messages`;
const res = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WHATSAPP_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
messaging_product: 'whatsapp',
to,
type: 'text',
text: { body: text },
}),
});
if (!res.ok) throw new Error(`WhatsApp API ${res.status}`);
return res.json();
}Templates Are Production-only
Sandbox blocks template sends.
The sandbox proxy rejects
type: "template"messages. Test templates against a connected production WABA, not the sandbox.
Rate Limits And Retries
Meta enforces per-phone messaging tiers and per-WABA business-initiated-conversation quotas. HookMyApp passes rate-limit error responses through untouched so your retry logic sees them directly.
For current tier thresholds, pair rates, and the 429 retry contract, see Meta's rate-limit docs.
Next Steps
- Receive Webhooks: Handle the delivery and read receipts.
- Sandbox: Try the send flow end-to-end with a shared test number.