Skip to main content
Send messages with a normal Meta Cloud API request body, but pointed at the HookMyApp gateway. The gateway swaps your token for the real Meta credential server-side, so your Meta token never leaves HookMyApp.

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". Both the sandbox and your own number go through the HookMyApp gateway. The base-URL variable is the only difference: the sandbox env sets WHATSAPP_API_URL, your own number sets META_GRAPH_API_URL, and both point at the gateway (<gateway>/<graph-version>). The token is a scoped HookMyApp gateway token (hmat_…), not your raw Meta token. The gateway authenticates the hmat_ token, swaps in the real Meta token, and forwards to Meta. The Node example reads whichever base-URL variable is present (WHATSAPP_API_URL ?? META_GRAPH_API_URL). 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 (src/providers/whatsapp.js). It reads whichever base-URL variable your env has, so the same code runs against the sandbox and your own number.
export async function send(to, text) {
  const base = process.env.WHATSAPP_API_URL ?? process.env.META_GRAPH_API_URL;
  const url = `${base}/${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) {
    const err = await res.json().catch(() => ({}));
    throw new Error(`WhatsApp API error ${res.status}: ${JSON.stringify(err)}`);
  }
  return res.json();
}

Templates need your own number

Sandbox blocks template sends. The sandbox proxy rejects type: "template" messages. Test templates against a connected WhatsApp number, not the sandbox.

Rate limits and retries

Meta enforces per-phone messaging tiers and per-account 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 test number.