Back to all articles
Integrations

What the Facebook webhook doesn't tell you

FT
Formwork Team
Integrations
Jun 17, 2026 · 7 min read

Meta's Lead Ads webhook does not contain the lead.

That is the whole post, in a sense. Almost every operational surprise with Lead Ads follows from that one fact, and once you have internalised it the rest stops being mysterious.

What actually arrives

The webhook payload is a notification. It says: a lead with this ID was created, on this form, on this page, at this time. It does not include the name, the phone number, or any of the answers.

json
1{
2  "leadgen_id": "1234567890",
3  "form_id": "9876543210",
4  "page_id": "1111111111",
5  "created_time": 1755000000
6}

To get the actual lead you make a second call back to Meta, authenticated with an access token for that Page, and ask for the contents of leadgen_id.

So the flow is: Meta notifies you, you call Meta back, Meta gives you the answers, and only then do you have a lead.

Consequence one: your connection is load-bearing

With a normal webhook, once the request has landed the data is yours. Nothing that happens afterwards can take it away.

With Lead Ads, a notification you cannot act on is worthless. If the Page token has expired, been revoked, or lost a permission, you receive a perfectly valid webhook telling you a lead exists and you cannot open it.

This is why token lifecycle is not housekeeping. It is the difference between having leads and having a list of lead IDs you cannot read. Tokens need refreshing on a schedule, before they lapse, and the failure needs to be loud — a broken connection should be as visible as a buyer outage, because commercially it is the same thing.

The most common ways a connection breaks have nothing to do with your code: someone removed the app from the Business account, someone's Page role changed, or someone approved a subset of the requested permissions during authorisation. That last one is worth guarding against explicitly, because it looks like success. The connection completes, the webhooks arrive, and every fetch fails.

Consequence two: subscription is per Page, not per form

You do not subscribe to a lead form. You subscribe to a Page, and then receive notifications for every lead form on it.

This surprises people in both directions. Connecting your second form on a Page requires nothing at Meta's end — it is already sending you those notifications. And disconnecting one form does not stop the notifications for the others, nor should it.

Practically, it means your system needs to decide what to do with a notification for a form nobody connected. Ignoring it is usually right. Logging that you ignored it is better, because "we are getting leads from a form we did not set up" is a question that comes up.

Consequence three: you need a reconciliation sweep

Webhooks get lost. Not often, but reliably enough to matter at volume. A deploy at the wrong moment, a timeout, a brief outage, a certificate hiccup — any of these and the notification is gone. Meta retries, but not forever.

Because the lead is fetched rather than pushed, you have an unusually good recovery option: ask Meta for recent leads on each connected form and compare against what you already have. Anything you are missing, fetch and process now.

Run this on a schedule — every fifteen minutes is a reasonable starting point — and a lost webhook becomes a lead that arrived a few minutes late instead of a lead that never arrived. There is no equivalent safety net for a source that only pushes.

This sweep is also what saves you during your own incidents. If your endpoint is down for twenty minutes, the leads are still sitting at Meta's end waiting to be collected.

Consequence four: test leads look broken

Meta's Lead Ads Testing Tool sends placeholder strings rather than plausible data:

text
<test lead: dummy data for phone_number>

Every phone formatter you have will reject that, correctly. A preview screen will show a red failure on a form that is configured perfectly.

Two things follow. First, do not spend an afternoon debugging a mapping that is fine. Second, if you are building this tooling, detect test payloads and say so in a colour that does not mean "broken" — a warning that fires on the healthy state is a warning people learn to ignore, and then they ignore the real one.

Consequence five: the form can change without telling you

Someone in Ads Manager adds a qualifying question, or renames one. Your mapping does not know about the new field name, so that answer stops arriving in your normalised data. No error is raised anywhere.

If the changed field is one a condition tests, the condition quietly stops matching and every lead from that campaign starts going somewhere else.

Whenever a lead form is edited on Meta's side, pull a fresh lead and look at what actually arrives. It takes a minute and it is the difference between noticing immediately and noticing in the monthly reconciliation.

The checklist

  • Refresh tokens on a schedule; alert loudly when a refresh fails.
  • Verify at connect time that every requested permission was actually granted.
  • Run a reconciliation sweep and treat it as part of the pipeline, not a nice-to-have.
  • Recognise test payloads and label them as test data.
  • Re-check your mapping after any change to the form at Meta's end.

None of it is difficult. All of it is invisible until the day it is not.