Zodiac Tech CRM Docs

Meta lead capture pipeline

End to end — how a Facebook or Instagram form submission becomes a CRM lead with an owner and follow-up, with the exact gates it passes.

admin ad-specialist developer Medium ⏱ 15 min Updated 2026-10-05

Meta lead capture pipeline

Someone taps Submit on your Facebook/Instagram lead form. Seconds later their name is a lead in your CRM, assigned to a salesperson, with an automation already saying hello. This page traces that journey exactly — including the gates that can stop it — so you can operate and debug it with confidence.

Who this is for

  • Admins — choose which forms feed the CRM and map their fields.

  • Ad specialists — understand why a campaign's leads do (or do not) appear.

  • Developers/support — the webhook, signature and job internals.

  • Difficulty: Medium

  • Time: 15 minutes

  • Prerequisites: Meta is connected (OAuth & permissions) and at least one Instant Form exists on the connected Page.

The pipeline, visually

flowchart TD
    A[Person submits a Facebook / Instagram lead form] --> B["Meta POSTs to /webhook/{tenant}/{source}/{token}"]
    B --> C{Signature check: X-Hub-Signature-256 = sha256=HMAC of raw body}
    C -- invalid --> R[Rejected]
    C -- valid --> D[WebhookLog row: status = pending]
    D --> E[ProcessInboundWebhook job runs]
    E --> F[MetaConnector parses entry.changes where field = leadgen]
    F --> G["Fetch the lead from Graph API: /{leadgen_id}?fields=field_data,created_time,ad_id,form_id"]
    G --> H{Form ID in the allowlist of selected forms?}
    H -- no --> S["Skipped — log note written (not a failure)"]
    H -- yes --> I[MetaLeadFormService maps fields: standard + custom]
    I --> J[Lead created: source_id, meta_ad_id, meta_form_id, raw_data kept]
    J --> K[LeadObserver: auto-assignment via LeadAutoAssigner]
    K --> L[Automations fire — welcome WhatsApp/email, tasks, alerts]

What each stage means for you

  1. The webhook URL — every business has its own webhook endpoint with a secret token. Meta posts new lead events there. The same endpoint answers Meta's verification challenge (a GET request) so the subscription can be set up.
  2. Signature verification — every POST is signed by Meta with the app secret. Requests that fail the check are rejected, so nobody can inject fake leads.
  3. The webhook log — each accepted call is stored (status pending), then a background job processes it. The log is your audit trail: accepted, processed, skipped or failed, with notes.
  4. Form allowlist — only the lead forms you explicitly selected in the CRM are ingested. A form that is not on the list produces a deliberate skip note on the webhook log, not a failure. This is how you keep a test form (or another team's form) out of your pipeline.
  5. Field mapping — submitted answers are mapped to CRM fields (name, phone, email, and your custom fields). Answers with no mapping are preserved in the lead's raw data under unmapped_fields — nothing is thrown away.
  6. Lead creation + routing — the lead is created with its Meta identifiers (source id, ad id, form id) and the original payload. The standard assignment rules and automations then run exactly as for any other lead.

Steps

1. Choose which forms feed the CRM (admin)

  1. Go to Settings → Connections (/admin/lead-source-connections), open your Meta connection, and open its lead forms area.
  2. Select the forms whose submissions should become leads; deselect test forms.

Expected result: new submissions from selected forms create leads; submissions from unselected forms are skipped with a note.

2. Map the fields

  1. For each selected form, check the mapping of its questions to CRM fields; add custom-field mappings if your form asks extra questions.

Expected result: the lead view shows answers in their proper fields; anything unmapped is visible in raw data instead of being lost.

3. Run a live test

  1. Submit a test entry on the form (Meta forms support test submissions), then watch Leads for a few seconds, and Scheduled/Webhook logs under Settings for the processing trail.

Expected result: a new lead appears with source Facebook, the campaign/ad/form identifiers present, and (if configured) an automation has already started.

4. Debug with the webhook log

  1. If no lead appears, open the webhook log list and inspect the entry:
    • pending — arrived, waiting for the job; if it stays pending, the queue worker/cron is the problem (see Cron & queue workers).
    • processed — lead created (or intentionally skipped — read the note).
    • failed — the error is recorded; common causes: expired token, no form permission, Graph API refusal.

Expected result: you can always answer "did Meta send it, and what did we do with it?"

Expected result

A trustworthy conveyor belt: every selected form's submission becomes a routed lead within seconds, unselected forms stay out, and every accepted webhook is auditable from arrival to lead.

Common mistakes

  • Wondering where a form's leads are, when the form was never selected. Check the allowlist first.
  • Deleting form selections "to clean up". New submissions from that form will be skipped and (if the form was still advertised) you lose leads. Deselect only forms you truly retired.
  • Using the wrong Page's form. The form must belong to the connected Page.
  • Expecting skipped forms to raise errors. Skips are intentional and logged quietly; only real failures are marked failed.
  • Blaming the CRM when the test submission never left Meta. Confirm in Meta's own lead centre that the form received the submission at all.

Troubleshooting

Symptom Fix
Lead arrives without campaign info The submission had no ad attribution (for example an organic form test) or the ad was deleted in Meta.
All leads stop at once Token expired or connection unhealthy — see OAuth & permissions and Ads health.
Some answers missing The question is unmapped — add the mapping (answers remain in raw data until you do).
Webhook log shows repeated failures Read the recorded error; typical: re-authorise, re-select the form, or the Page form was deleted in Meta.

Technical details

  • Endpoint: App\Http\Controllers\InboundWebhookController — POST /webhook/{tenant}/{source}/{token} (alias under /api/webhook/...). Connection resolution bypasses the tenant scope deliberately (lookup by tenant + source + webhook_token); an unknown token returns a silent 200 and only writes a log entry. GET requests answer the hub_challenge verification echo. Signature: X-Hub-Signature-256 (sha256= HMAC of the raw body, connection or platform app_secret; an empty secret skips verification for legacy rows only).
  • Processing: WebhookLog (pending) → ProcessInboundWebhook job → App\Services\LeadSources\MetaConnector::handleWebhook parses entry[].changes[] with field = leadgen (requires leadgen_id), then fetchLeadFromGraph calls GET /{leadId}?fields=field_data,created_time,ad_id,form_id.
  • Allowlist/mapping: MetaLeadFormService (selected form_ids only; custom fields via CustomFieldDefinition; unmapped answers → leads.raw_data.unmapped_fields).
  • Lead row: source_id = leadgen id, plus meta_ad_id, meta_form_id, raw_data. Then LeadObserver runs routing (LeadAutoAssigner) and automations.

Was this page helpful?