Skip to main content
Before Meta sends you any real messages, it tests that you actually own the webhook URL you registered. This one-time test is called the verification handshake. Your server must pass it before Meta activates the webhook.

Step 1 — You need a public HTTPS URL

Your server must be reachable from the internet. During development, use ngrok to tunnel your localhost:
ngrok http 3003
→ gives you: https://xxxx.ngrok-free.app
Your webhook endpoint is that URL + /webhook: https://xxxx.ngrok-free.app/webhook

Step 2 — Register it in Meta Dashboard

Meta Dashboard → Your App → WhatsApp → Configuration → Webhook → Edit
FieldWhat to enter
Callback URLhttps://xxxx.ngrok-free.app/webhook
Verify TokenAny secret word you choose (e.g. myrahulsecret123)
Click Verify and Save.

Step 3 — The verification handshake

The moment you click Verify, Meta sends a GET to your URL:
GET /webhook?hub.mode=subscribe
             &hub.verify_token=myrahulsecret123
             &hub.challenge=RANDOM_STRING
Meta is asking: “Do you know the secret? If yes, send back the challenge.” Your server checks the token and echoes the challenge:
app.get('/webhook', (req, res) => {
  const mode      = req.query['hub.mode'];
  const token     = req.query['hub.verify_token'];
  const challenge = req.query['hub.challenge'];

  if (mode === 'subscribe' && token === process.env.WEBHOOK_VERIFY_TOKEN) {
    res.send(challenge);                    // ← pass: echo challenge back
  } else {
    res.sendStatus(403);                    // ← fail: wrong token
  }
});
If it passes → Meta activates your webhook → real messages start flowing.

Frequently asked

A secret word you choose and set in two places: your .env file and the Meta Dashboard webhook form. Meta sends it in the handshake GET request so your server can confirm the request is genuinely from Meta.
Either your server wasn’t running when Meta sent the GET request, the verify token in your .env doesn’t match what you entered in Meta Dashboard, or your URL isn’t publicly reachable (e.g. ngrok isn’t running).
During development yes — your localhost is not reachable from Meta’s servers. ngrok creates a public tunnel. In production, deploy your server to a real host (Render, Railway, etc.) and use that permanent URL instead.
Yes — free ngrok gives a new URL every restart. You’d need to update it in Meta Dashboard each time. Paid ngrok gives a fixed domain.

Gotchas & common mistakes

  • Server not running when you click Verify — Meta’s GET arrives instantly. If your server is down, it fails. Always start the server before clicking Verify in Meta.
  • Token mismatch — the verify token in .env must exactly match what you typed in Meta Dashboard. Case-sensitive.
  • HTTP not HTTPS — Meta requires HTTPS. ngrok provides HTTPS automatically. Plain http://localhost will be rejected.