Skip to main content
Meta’s webhook delivery system is designed for reliability — which means it retries failed deliveries. Your server must follow three rules to handle this correctly.

Rule 1 — Send 200 immediately, process after

Meta expects a 200 response within a few seconds. If it doesn’t get one, it retries the same event — which means your bot processes the same message twice.
app.post('/webhook', (req, res) => {
  res.sendStatus(200);          // ← FIRST — before any logic

  const message = changes.messages[0];
  // process here, after 200 is sent
});
ResponseWhat Meta does
200 immediatelyNever retries — done
No response / timeoutRetries for up to 24 hours
500 errorRetries

Rule 2 — Deduplicate by message ID

Because Meta retries, the same message can arrive twice with the same id. Store seen IDs and skip duplicates:
const seen = new Set(); // use a database in production

if (seen.has(message.id)) return; // already processed
seen.add(message.id);

// now process safely

Rule 3 — Don’t trust order

If a customer sends “Hi”, “How are you?”, “Hello?” quickly, Meta does not guarantee they arrive in order. If order matters, sort by the message timestamp:
const timestamp = parseInt(message.timestamp); // unix timestamp

Frequently asked

Your server is not responding 200 fast enough, or it’s doing slow work before sending 200. Meta retries and your handler runs twice. Fix: send 200 first, then process.
Not much — duplicates are rare in testing. But in production with real traffic, always deduplicate by message.id.

Gotchas & common mistakes

  • Slow processing before 200 — database writes, API calls, AI processing before sending 200 will cause timeouts and retries. Always 200 first.
  • Using an in-memory Set for deduplication — fine for testing, but restarts wipe it. In production, store seen IDs in a database with a TTL.