Skip to main content
Why Our Instagram DM Webhooks Were Silently Failing
integrationsinstagrammeta-graph-api+4

Why Our Instagram DM Webhooks Were Silently Failing

A bug in our Zybo AI backend where Instagram DMs were dropped at the webhook. The cause: Meta's Instagram integration has a nested app secret that HMAC verification needs to know about.

The symptom looked like Meta wasn’t calling us

Instagram DMs were disappearing before they ever reached our agent. From the outside — and from most of the inside — it looked like Meta simply wasn’t delivering the webhooks. No message events, no errors on our dashboard, nothing to grep for. If you’ve ever chased a webhook that “never arrived,” you know the feeling.

It turned out Meta was calling us. Every time. We were rejecting the calls with a bare 400 and no log line, so the failure was invisible from both ends.

This post is the short version of what actually broke in our Zybo AI backend, why it broke, and what any team wiring up Instagram DMs via the Meta Graph API should check before shipping.

Two compounding bugs, one invisible failure

There were two problems stacked on top of each other. Either one alone would have been survivable. Together they made the bug look like it wasn’t our bug at all.

1. Meta’s Instagram integration has a nested app secret.

When you enable the “Instagram use case” inside Meta’s App Dashboard — the flow you use to let a Meta app receive DMs from Instagram business accounts — it provisions its own nested app id and secret. That inner secret lives underneath your main app credentials. It is not the same as META_APP_SECRET, the top-level secret most integration guides point you at.

Instagram may sign its webhook deliveries using that inner secret. If your HMAC check — the signature verification that proves the webhook really came from Meta and wasn’t forged — only knows about the top-level META_APP_SECRET, every Instagram delivery fails verification. Not some. All of them.

2. A failed HMAC check returned 400 with no log.

Our verifySignature path rejected the request and moved on. No log line for the mismatch, no log line for the parsed payload, nothing. From the server’s point of view the request was noise; from the developer’s point of view it was indistinguishable from Meta never sending anything at all.

Silent 400s are the failure mode that eats debugging hours.

The fix

Two changes, both small:

  • verifySignature now tries both secrets. It attempts HMAC verification against META_APP_SECRET first, then falls back to a new optional META_IG_APP_SECRET for the Instagram use case’s nested credential. If either matches, the webhook is accepted.
  • Every signature mismatch and every parsed payload now emits a log line. If Instagram signs with a secret we don’t have, we see it immediately instead of guessing.

That’s it. No architectural rework, no queue, no retry logic. The bug wasn’t complexity — it was a missing credential and a missing log.

What to take away if you’re integrating Instagram DMs

If your product routes Instagram DMs through the Meta Graph API — whether that’s a support inbox, a lead-capture bot, or an AI agent like ours — check three things before you go live:

  1. Look at your Meta App Dashboard for the Instagram use case and confirm whether it has a separate app id and secret. If it does, your webhook verifier needs to know about it.
  2. Verify against both secrets. Don’t assume the top-level META_APP_SECRET covers Instagram. Try each; accept if either matches.
  3. Log every signature failure and every accepted payload. A silent 400 is the same shape as “we were never called.” You will lose days to that ambiguity if you don’t fix it up front.

The transferable point is boring and specific, which is exactly why it’s worth writing down: Meta’s Instagram integration is not a flat app credential. If you only verify against the main secret, every Instagram DM silently fails HMAC and gets dropped. Any team building on this stack needs to plan for a second credential from day one.

Have a project in mind?

Fixed price after a paid discovery — no hourly billing. A real engineer reads every enquiry, and we reply within 24 hours.