Lead Gen Forms
Create a Meta instant form with POST /v1/ads/lead-forms, attach it to an ad, and receive the leads by webhook or by polling.
Collect leads inside Meta instead of on your site:
- Create an instant form.
- Attach it to an ad with
goal: "lead_generation". - Receive submissions on the
lead.receivedwebhook.
Forms are Page-scoped. Use a connected Facebook account, or a metaads business-login connection with a selected Page, as accountId.
Instagram lead ads use the Page's form.
The Page must accept Facebook's Lead Generation terms once, at https://www.facebook.com/ads/leadgen/tos/, before lead ads can run. Until it does, ad creation returns Meta subcode 1815089. Creating forms and reading leads work without it.
Create a form
Call POST /v1/ads/lead-forms with accountId, name, privacyPolicyUrl and the form content in platformSpecificData.
Prefilled question types (EMAIL, PHONE, FULL_NAME, FIRST_NAME, LAST_NAME) generate their own label and key. Send only { "type": "..." }.
A CUSTOM question needs key, label and, for a choice question, options.
import Zernio from '@zernio/node';
const zernio = new Zernio();
const { data: created } = await zernio.leadgen.createLeadForm({
body: {
accountId: '66b2e19d8c3f5a7e9d0b1c2d',
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 and above' }
]
}
]
}
}
});
const formId = created.form.id;Response (200):
{
"status": "success",
"form": { "id": "1029384756102938", "name": "Spring promo" }
}platformSpecificData also carries:
- the thank-you screen (
thankYouTitle,thankYouBody,thankYouButtonText,thankYouButtonType,thankYouWebsiteUrl) - the
contextCard localefollowUpActionUrlisOptimizedForQuality, which adds a review step before submit
It is strict-parsed: an unknown key returns a 400.
The same endpoint serves LinkedIn Ads with a LinkedIn-shaped platformSpecificData, selected by accountId.
The older top-level Meta fields still work while platformSpecificData is absent. Mixing the two shapes in one request is a 400.
Creates are not idempotent, so never auto-retry one.
Cover photo. Set platformSpecificData.contextCard.coverPhoto to a direct public JPEG or PNG URL, up to 5 MB.
Zernio uploads it as an unpublished Page photo and attaches it to the form. Redirects, Ad Image hashes and image IDs are unsupported.
Attach the form to an ad
Send goal: "lead_generation" and leadGenFormId on the create request. This also works with carousel creatives.
No linkUrl is needed: the form is the destination. The ad set's lead optimization and promoted Page are derived for you.
{
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"adAccountId": "act_1234567890",
"name": "Spring promo lead ad",
"goal": "lead_generation",
"leadGenFormId": "1029384756102938",
"budgetAmount": 10,
"budgetType": "daily",
"headline": "Get a quote",
"body": "Tell us about your project",
"callToAction": "SIGN_UP",
"imageUrl": "https://cdn.example.com/creative.jpg"
}POST /v1/ads/boost takes the same 2 fields to put a form behind an existing post.
Every ad attached to a lead ad set needs its own leadGenFormId (creatives).
Receive leads
The lead.received webhook fires the moment a lead is submitted.
Its payload carries lead.fields (the question key to answer map) plus the formId and adId provenance.
Poll GET /v1/ads/lead-forms/{formId}/leads when you would rather pull.
const { data: page } = await zernio.leadgen.listFormLeads({
path: { formId: '1029384756102938' },
query: { accountId: '66b2e19d8c3f5a7e9d0b1c2d', limit: 50 }
});
for (const lead of page.leads) console.log(lead.fields);Response (200):
{
"status": "success",
"leads": [
{
"id": "5566778899001122",
"createdTime": "2027-02-14T10:31:05+0000",
"adId": "120260000000000000",
"formId": "1029384756102938",
"fields": { "email": "lead@example.com", "full_name": "Jamie Rivera", "budget": "high" }
}
],
"pagination": { "hasMore": false, "cursor": null }
}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.
It is the shape to feed a CRM without iterating forms.
since is Unix seconds, between 1 and 253402300799. Millisecond timestamps return 400 with instructions to divide by 1,000.
Test a lead before you spend
POST /v1/ads/lead-forms/{formId}/test-leads puts a synthetic lead on the form. Use it to exercise your webhook handler and field mapping first.
It takes accountId and fieldData, an array of at least one { name, values[] } entry matching the form's questions.
curl -X POST "https://zernio.com/api/v1/ads/lead-forms/1029384756102938/test-leads" \
-H "Authorization: Bearer $ZERNIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"fieldData": [{ "name": "email", "values": ["test@example.com"] }]
}'Response (200):
{ "status": "success", "testLead": { "id": "5566778899001123" } }Meta allows one pending test lead per form, so consume or delete it before creating another.
To retire a form, call DELETE /v1/ads/lead-forms/{formId}. Meta archives it, and there is no hard delete.
If it fails
A 422 on the form create is Meta rejecting the form:
{
"error": "Meta rejected the lead form",
"type": "platform_error",
"platform": "meta",
"platformError": { "code": 3 }
}Code 3 is Meta's generic app-capability error and does not name a field.
When the request set isPhoneSmsVerifyEnabled, the response names that field as the one to drop first. The toggle then has to be set in Meta's form builder.
On the ad create, subcode 1815089 is the Lead Generation terms the Page has not accepted yet.
Related
- Campaigns: the create request
leadGenFormIdgoes on. - Conversion campaigns: how
lead_generationdiffers fromlead_conversion. - Ads webhooks: the
lead.receivedevent and its payload. - Create lead form and List form leads: every field.
Boost a post
Turn a published Facebook or Instagram post into a Meta ad with POST /v1/ads/boost, keeping the post's likes and comments.
Targeting
Target Meta ads by country, city, region, ZIP, age, gender and interests, and resolve Meta's opaque location and interest ids with GET /v1/ads/targeting/search.