Facebook Lead Ads API: Retrieve Leads and Sync a CRM
The Facebook Lead Ads API returns instant form submissions two ways: a leadgen webhook on the Page that sends each new lead's ID in real time, and bulk reads from GET /{form-id}/leads or /{ad-id}/leads. Both need the leads_retrieval permission. Meta keeps lead data for 90 days, so sync it to a CRM promptly. Adeli lists a Page's lead forms and their submissions in one call.
The Facebook Lead Ads API gives you instant form submissions in two ways: a leadgen webhook that notifies you the moment someone submits a form, and bulk reads from the leads edge on a form (GET /{form-id}/leads) or an ad (GET /{ad-id}/leads). Both need the leads_retrieval permission, and Meta keeps lead data for 90 days, so the job is to get each lead into a CRM quickly.
This guide covers the permissions, both retrieval methods, and the sync patterns that keep a CRM complete. It's written for developers at platforms that run lead ads for their customers, such as real estate or home services software. It's part of our Meta Ads API series.
What do you need before you start?
Lead retrieval needs a Page or user token from someone who can advertise on both the Page and the ad account, plus five permissions. From Meta's retrieving leads guide:
| Permission | Why |
|---|---|
leads_retrieval |
Read the lead's answers. Required for every read. |
ads_management |
Read ad fields such as ad_id and campaign_id |
pages_manage_ads |
Manage ads for the Page |
pages_read_engagement |
Read the Page's content and metadata |
pages_show_list |
List the Pages the person manages |
pages_manage_metadata |
Subscribe your app to the Page's webhooks (webhooks only) |
Leads Access Manager can override Page roles. If a business admin has customized lead access, a Page admin without it gets nothing back. When a customer's leads come back empty, check Leads Access Manager first.
leads_retrieval arrived on July 2, 2018. Before that, Meta's announcement notes, "you could access the Lead Retrieval API with only the manage_pages permission." Older tutorials still describe that setup.
How do I get leads in real time with webhooks?
Subscribe your app to the Page's leadgen webhook field, then fetch each lead by the leadgen_id in the notification. The webhook carries IDs, not answers, so every notification is followed by one read.
- Set up a webhook endpoint in your app's Webhooks product, choose the Page object, and subscribe to the
leadgenfield. - Install your app on each customer's Page with
POST /{page-id}/subscribed_apps, using that Page's access token. A long-lived Page token keeps this working without re-authentication. - Read the lead when a notification arrives.
A notification's value looks like this:
{
"leadgen_id": 123123123123,
"page_id": 123123123,
"form_id": 12312312312,
"adgroup_id": 12312312312,
"ad_id": 12312312312,
"created_time": 1440120384
}Then fetch the answers:
curl -G "https://graph.facebook.com/v26.0/$LEADGEN_ID" \
--data-urlencode "fields=created_time,ad_id,form_id,field_data" \
--data-urlencode "access_token=$PAGE_TOKEN"field_data is a list of { "name": "email", "values": ["..."] } objects, one per question. Meta says real-time pings can arrive up to a few minutes after the submission. Notifications only arrive if the Page hasn't disabled the App platform in its settings.
How do I bulk read leads from a form or an ad?
Call the leads edge on a form ID or an ad ID. A form can be reused by many ads, so the form edge can return far more leads than any one ad using it.
curl -G "https://graph.facebook.com/v26.0/$FORM_ID/leads" \
--data-urlencode "fields=created_time,id,ad_id,form_id,field_data" \
--data-urlencode 'filtering=[{"field":"time_created","operator":"GREATER_THAN","value":1761945743}]' \
--data-urlencode "access_token=$ACCESS_TOKEN"- Filter by time with
time_createdandGREATER_THAN,GREATER_THAN_OR_EQUAL, orLESS_THAN, using Unix timestamps. This is how an incremental sync asks only for new leads. - Page with cursors. Results come back in pages with
beforeandaftercursors. - Read disclaimer checkboxes separately. Optional custom disclaimer answers aren't in
field_data; requestcustom_disclaimer_responseson the lead. - Mind the rate limit. Meta allows 200 × 24 × the number of leads the Page created in the past 90 days, per 24 hours.
How long are Facebook leads available?
Meta keeps lead data available for 90 days after submission. Meta's Business Help Center page on downloading leads data says leads older than that can't be downloaded. For your integration, that turns a delayed sync into lost data:
- A broken webhook for a weekend is recoverable with a bulk read.
- A broken webhook for a quarter is not.
- A customer who connects late only gets the last 90 days of history.
Meta's retrieval guide also offers a CSV export, GET https://www.facebook.com/ads/lead_gen/export_csv/ with from_date and to_date, but it's built for people, not syncs. The leads edge is the one to automate.
How should I sync Facebook leads to a CRM?
Use the webhook for speed and a scheduled bulk read as a safety net, and key every write on the lead ID. A lead that waits for a manual import waits to hear from your customer:
"Without this auto-syncing, leads will be forced to wait to hear from you until you've manually added them." Jon Loomer, Meta ads consultant, How to Use Meta Ads for Lead Generation (July 21, 2026)
A sync pattern for platforms:
- Webhook first. On each
leadgennotification, fetch the lead and upsert it in the CRM within seconds. - Idempotent writes. Use the lead
idas the CRM's external key. Webhooks can repeat, and your backfill will overlap with them. - Scheduled backfill. Every hour or night, bulk read each active form with
time_createdgreater than your last sync time, minus a margin. This catches missed or failed webhooks. - Map by field name, not position. Customers edit forms. Map
field_databyname, and store unmapped answers so nothing is dropped. - Keep attribution. Store
ad_id,form_id, and the campaign with the lead, so your customer can see which ad brought in which leads. - Treat it as personal data. Lead answers are names, emails, and phone numbers. Encrypt them at rest and delete them when the customer disconnects.
To test the pipeline, create a lead with Meta's Lead Ads Testing Tool or POST /{form-id}/test_leads. Each form holds one test lead at a time.
Is there a faster way to do this?
Adeli lists a customer's lead forms and returns each form's submissions, with the campaign, ad set, and ad each lead came from, in one call. Adeli holds the Meta app and the leads_retrieval grant, so you skip App Review for it and per-Page token handling. The endpoint is lead forms and leads:
curl -G "https://app.tryadeli.com/api/v1/ads/leads" \
-H "Authorization: Bearer $ADELI_API_KEY" \
--data-urlencode "accountId=$ACCOUNT_ID" \
--data-urlencode "formId=555666777888999"- Without
formId, the response lists the connected Page's lead generation forms. - With
formId, it returns that form's submissions: each lead'sid,createdAt,fields, and thecampaignName,adSetName, andadName. - Adeli doesn't store lead data. Submissions are read from Facebook when you ask for them and aren't retained after the response, so the copy in your CRM is the only one outside Meta.
- A missing grant is explicit. A connection without
leads_retrievalreturns403 ads_permission_requirednaming the missing scope.
Run it on a schedule per customer form and upsert by lead id, the same way as the backfill above. The same API key creates the campaigns that drive the leads and publishes the posts they promote. Pricing is per customer profile, so it follows how you bill.