You have done everything right. GET /{app-id}/subscriptions returns your callback URL with active: true and the leadgen field. GET /{page-id}/subscribed_apps returns your app. Your token has leads_retrieval granted. You are a Page admin. You submit a test lead.
Nothing arrives. No error, no rejected delivery, no failed request. Nothing.
We wrote previously about five reasons Meta lead ads stop reaching your CRM. This post is about the harder case: when all five check out and it still does not work. These four causes have one thing in common. The Graph API reports success for every one of them.
1. Your app is in Development mode
This is the big one, and it is the one nobody writes down.
An unpublished app returns a perfectly healthy subscription. active: true. Correct callback URL. Correct field. Meta verified your endpoint with a real hub.challenge request, so you know it can reach you. Every API call agrees the integration is live.
It will not send you a single lead from anyone who does not have a role on your app.
That last clause is what makes it so expensive to diagnose. Your own test leads work, because you are the app admin. So you test, it works, you launch, and real prospects fill the form into a void. The failure only appears with strangers, which is to say only in production, which is to say only with money on the line.
2. Leadgen reads need a Page token, not a user token
leads_retrieval on a user or system-user token is not enough. Under Meta's new Pages experience, fetching a lead fails:
(#190) This method must be called with a Page Access Token
The permission is granted. The token is valid. It can read the Page. It just cannot read the Page's leads.
Trade it for a Page token first:
GET /{page-id}?fields=access_token&access_token={user-token}
Then use that token for GET /{leadgen-id}. The distinction is easy to miss because minting the Page token and using the user token both "work" right up until the fetch.
The trap is sharper if you use a system user. A system-user token can mint a Page token, subscribe the Page, and report success at every step, all while being the wrong token for the one call that matters. It is entirely possible to build a working setup flow and a broken read path from the same credential.
3. leads_retrieval is not where you would look for it
Meta's app dashboard only offers permissions that a configured use case unlocks. leads_retrieval sits under Capture & manage ad leads, not under Manage Pages.
If that use case is not added to your app, the permission does not appear in the system-user token dialog at all. Not greyed out. Absent. You will search the list, not find it, and reasonably conclude you lack access, when in fact you have not told Meta what you are building.
It also does not work alone. Meta requires the whole dependency chain:
| Permission | Why |
|---|---|
leads_retrieval |
read the form answers |
pages_manage_ads |
required dependency |
pages_read_engagement |
required dependency |
pages_show_list |
required dependency |
ads_management |
required dependency |
ads_read |
required dependency |
business_management |
required dependency |
pages_manage_metadata |
subscribe the Page to your webhook |
A token with leads_retrieval and nothing else looks correct and fails.
4. A 200 is a promise you cannot take back
Meta treats 200 as "handled". It will never send that lead again.
So the natural defensive instinct, always return 200 so Meta does not retry-storm my endpoint, quietly converts every transient failure into permanent data loss. Token briefly expired? Lead gone. Graph API hiccup? Lead gone. Your database was failing over for ten seconds? Those leads are gone, and Meta believes it delivered them, because you said so.
Watch out: "Always return 200" is good advice attached to a precondition people drop. The full rule is: persist the raw payload, then return 200, then process out of band. If you return 200 before the payload is safely stored, you have promised to handle something you may have just dropped.
If you process inline, answer non-2xx when the work fails. Meta redelivers for roughly 36 hours, which is your only safety net. Redelivery is safe as long as you deduplicate, and you should be deduplicating anyway, because Meta retries on its own schedule regardless.
The pattern
Every one of these fails without an error.
That is the whole difficulty. A failing integration announces itself; a silent one looks exactly like an ad campaign that is not working. You check Ads Manager, see leads, check your CRM, see none, and start debugging your parser. The parser is fine. The parser was never called.
So build for the silence. Record every delivery attempt, including the rejected ones, with the reason attached. A graph_fetch_failed written into an HTTP response body that only Meta ever reads is not error handling. It has to land somewhere a human will look, on a screen, next to a timestamp.
Then when it breaks, and it will, the question stops being "is it Meta or is it us" and becomes a line you can read.
That is the difference between an integration you own and one that owns you. Kredoo connects Meta and Google lead forms straight to your pipeline, with every delivery logged and every failure visible.
If you are weighing this route against the alternatives rather than debugging one you already built, how to connect Meta ads to your CRM compares all four, including what an automation tool costs you in latency.
Questions people ask
Why is my Meta lead ads webhook connected but not sending anything?
Four causes, none of which report an error. The app is in Development mode, which delivers no webhooks for Pages you do not own. The retrieval call is using a user token where a Page token is required. The leads_retrieval permission was never granted because it lives under a use case rather than the flat permission list. Or your endpoint returned 200 before storing the lead, so Meta considers it delivered.
Does a Meta app in Development mode receive webhooks?
Not for Pages outside your own app roles. Development mode looks fully configured and silently delivers nothing for a client's Page, which is why the integration appears correct in every dashboard and produces no leads. Switch the app to Live mode.
What token do I need to read Meta lead ads?
A Page access token. A user token authenticates the request but the leadgen retrieval will fail or return nothing, because the lead belongs to the Page rather than to you. For production, derive the Page token from a System User so it does not expire.
Where is the leads_retrieval permission in the Meta app dashboard?
Under a use case rather than in the flat list of permissions, which is why people conclude it does not exist. Select the relevant use case first and the permission becomes available to request there.
Should I return 200 to a Meta webhook before saving the lead?
No. A 200 tells Meta the lead was delivered and it will never resend it. Write the payload to storage first, then acknowledge. Returning 200 early is unrecoverable data loss that leaves no trace on either side.
Stop losing leads to the follow-up gap
Kredoo captures leads from Meta and Google Ads, then follows up on WhatsApp automatically. Live in 5 minutes.
Get Started