Skip to main content
Socialift
Technical guide

How Instagram webhooks work

A Meta webhook has two stages: address verification (GET) and event delivery (POST). Every POST arrives with an HMAC-SHA256 signature in the X-Hub-Signature-256 header, computed over the raw body. For Instagram events to arrive at all, the app must be Live, the account subscribed through subscribed_apps, and Advanced Access granted for comments.

Published: Updated:

In short: how the flow runs

You register a callback URL and verify token in the Meta App Dashboard. Meta sends a GET request: if hub.mode=subscribe and hub.verify_token match, return the exact hub.challenge value. Events then arrive as POSTs (up to 1000 per batch): verify the signature, split the events, deduplicate and return 200 immediately.

Verifying the signature

This is the platform-level check — the same logic applies without an SDK.

verify.py
import hmac, hashlib

def verify(raw_body: bytes, header: str, app_secret: str) -> bool:
    if not header or not header.startswith("sha256="):
        return False
    expected = hmac.new(app_secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header.split("=", 1)[1])

raw_body must be the raw bytes from your framework. Re-serialised JSON breaks the signature.

In the Socialift SDK the same logic is verify_request(app_secret, body, headers), and the GET check is verify_challenge.

Why the same event arrives twice

Meta delivers events at least once. If your endpoint answers slowly or returns an error, delivery is retried — and one event can land twice.

Meta sends no event ID, so the correct fix is to build a stable key from the payload (mid for messages, leadgen_id for leads, comment_id for comments) and drop repeats. Otherwise duplicate deals or leads appear in the CRM. In the SDK this is dedupe_key().

The second rule is to return 200 fast. Heavy processing — writing to the CRM, sending notifications — belongs on a queue.

Production checklist

  • Reject requests with an invalid signature using 401/403 and log them.
  • Meta sends no event ID: build a key from the payload (mid for messages, leadgen_id for leads, comment_id for comments) and store it with the delivery time in a unique column.
  • Even without a queue, never block the delivery response.
  • Keep verify_token and app_secret in environment variables.
  • Use a valid TLS certificate — self-signed certificates are not supported.
  • Set the app to Live in the App Dashboard — otherwise Instagram webhooks are not sent.
  • comments and live_comments events require Advanced Access; comment and @mention events fire only for public accounts.
  • Subscribe the account: POST /me/subscribed_apps with subscribed_fields=messages,comments,... (with a Page token on the Facebook Login path).

← Back to blog

Frequently asked questions

My webhook signature never matches — what now?

First confirm you are using the raw body; many frameworks parse it automatically. Then check the app secret is correct and that no intermediate proxy is rewriting the body.

Can I poll instead of using webhooks?

As a fallback, yes. As the primary stream, no: polling burns rate limits and slows down reaction time.

How many times does Meta retry?

Meta retries a failed delivery immediately, then a few more times with decreasing frequency over the next 36 hours; unacknowledged events are dropped after 36 hours. Return 200 fast and deduplicate.

Why is my Instagram webhook not working?

The usual causes: the app is not Live, the account was never subscribed via /me/subscribed_apps, there is no Advanced Access for comments, or the account is private. Meta also won't deliver to an endpoint with a self-signed certificate.

How many events arrive in one request?

Meta batches notifications: one POST can carry up to 1000 changes. Walk every changes and messaging array inside each entry — in the Socialift SDK, parse_events() turns each one into its own WebhookEvent.

Sources

  1. Meta: Getting started with webhooks
  2. Meta: Instagram webhooks

Related pages