Lead Gen Forms
Create and manage Meta lead generation forms
Lead Gen Forms
Create native Meta Lead Gen (Instant) Forms, attach them to ads, and receive submitted leads in real time. The full flow:
- Create a form on a connected Facebook account with
POST /v1/ads/lead-forms. Forms are Page-scoped (Instagram lead ads use the linked Page's form under the hood). - Attach it to an ad by setting
goal: "lead_generation"andleadGenFormIdonPOST /v1/ads/create(standalone) orPOST /v1/ads/boost(boost an existing post). The ad set's lead optimization and promoted Page are derived automatically, no destination URL is needed. - Receive leads in real time via the
lead.receivedwebhook, or pull them withGET /v1/ads/lead-forms/{formId}/leads.
One-time setup: your Facebook Page must accept Facebook's Lead Generation Terms of Service before lead ads can run. If ad creation returns Meta error subcode 1815089 ("Terms of Service Not Accepted"), accept them once at https://www.facebook.com/ads/leadgen/tos/?page_id=YOUR_PAGE_ID. Creating forms and reading leads works without it, only running ads requires it.
The form content now rides on platformSpecificData. The same /v1/ads/lead-forms endpoints serve Meta and LinkedIn, with the platform selected by accountId and the platform-shaped form content (questions, thank-you screen, context card, ...) inside platformSpecificData. The top-level Meta fields shown below (questions, thankYou*, contextCard, ...) are deprecated but still accepted when platformSpecificData is absent — existing integrations keep working. Mixing both shapes in one request returns a 400, and so does an unknown key inside platformSpecificData.
Create a form, then a lead ad
Prefilled question types (EMAIL, PHONE, FULL_NAME, FIRST_NAME, LAST_NAME, …) auto-generate their label and key, send only { "type": "..." }. CUSTOM questions require key, label, and (for choice questions) options.
New-shape example (the deprecated flat shape is the same fields at the top level):
{
"accountId": "FACEBOOK_ACCOUNT_ID",
"name": "Spring promo",
"privacyPolicyUrl": "https://example.com/privacy",
"platformSpecificData": {
"questions": [
{ "type": "EMAIL" },
{ "type": "FULL_NAME" },
{ "type": "CUSTOM", "key": "budget", "label": "Monthly budget?", "options": [
{ "key": "low", "value": "Under $1k" },
{ "key": "high", "value": "$1k+" }
] }
]
}
}// 1. Create the form
const { form } = await zernio.leadgen.createLeadForm({ body: {
accountId: 'FACEBOOK_ACCOUNT_ID',
name: 'Spring promo',
questions: [
{ type: 'EMAIL' },
{ type: 'FULL_NAME' },
{ type: 'CUSTOM', key: 'budget', label: 'Monthly budget?', options: [
{ key: 'low', value: 'Under $1k' },
{ key: 'high', value: '$1k+' },
] },
],
privacyPolicyUrl: 'https://example.com/privacy',
} });
// 2. Create an ad that opens the form (no linkUrl needed)
const { ad } = await zernio.adcampaigns.createStandaloneAd({ body: {
accountId: 'FACEBOOK_ACCOUNT_ID',
adAccountId: 'act_123',
name: 'Spring promo lead ad',
goal: 'lead_generation',
leadGenFormId: form.id,
budgetAmount: 10,
budgetType: 'daily',
headline: 'Get a quote',
body: 'Tell us about your project',
callToAction: 'SIGN_UP',
imageUrl: 'https://example.com/creative.jpg',
} });Receiving leads
The best way to capture leads is the lead.received webhook, fired the moment a lead is submitted. The payload carries lead.fields (the question-key to answer map) plus formId / adId provenance. You can also poll for them:
const { leads } = await zernio.leadgen.listFormLeads({
path: { formId: 'FORM_ID' },
query: { accountId: 'FACEBOOK_ACCOUNT_ID', limit: 50 },
});
for (const lead of leads) console.log(lead.fields);GET /v1/ads/leads is the cross-form view of the same data: every lead the connected account has captured, filterable by formId and since, for feeding a CRM without iterating forms.
Test leads
POST /v1/ads/lead-forms/{formId}/test-leads creates a synthetic lead on the form so you can exercise your lead.received webhook handler and field mapping end-to-end before spending. The body requires accountId and fieldData, an array of { name, values[] } entries (at least one) matching the form's questions, for example { "accountId": "...", "fieldData": [{ "name": "email", "values": ["test@example.com"] }] }. Meta allows one pending test lead per form: delete or consume it before creating another.
To attach a form when boosting an existing post, pass the same leadGenFormId (and goal: "lead_generation") to POST /v1/ads/boost. To retire a form, DELETE /v1/ads/lead-forms/{formId} (Meta archives it, there is no hard delete).
LinkedIn lead forms and lead retrieval share these endpoints (selected by accountId, with the LinkedIn-shaped platformSpecificData); they're documented on LinkedIn Ads.