36 days to goFree AI automation workshop · Saturday 31 October 2026Sat 31 Oct · PHIVE, Parramatta · 10 seats

23 September 2026

What We Learned Wiring Facebook and Instagram Into Our CRM

What We Learned Wiring Facebook and Instagram Into Our CRM

Every enquiry we get on Facebook and Instagram now lands in our CRM within seconds. Comments on posts, Instagram DMs, Messenger messages, lead ad submissions. One list, in the place we actually work from, instead of four apps we have to remember to open.

The workflow that does it took about an hour to build. The rest of the day went on four things that aren't in Meta's documentation in any useful way, and that all failed the same way: silently, with a green tick beside them.

This is the follow-up to what we learned wiring up an AI receptionist, and it's the same lesson wearing a different hat. The tools work. Finding out which part isn't working is where the day goes.

Meta has two subscriptions, and you will only find one

This one cost the most, so it goes first.

In the Meta app dashboard you set a callback URL, pick an object (page, instagram) and subscribe to the fields you want. feed for comments, leadgen for lead form submissions. It says Subscribed. There's a Test button beside each field. You click it, the payload lands on your endpoint, and you conclude, reasonably, that you're done.

You are not done.

That subscribes the app to the object. Each individual Page also has to be subscribed to the app, and there's no screen for it anywhere in the dashboard. It exists only as an API call:

POST /{page-id}/subscribed_apps?subscribed_fields=feed,leadgen

Until you make that call, real events never fire. Comments go nowhere, leads go nowhere, and nothing anywhere tells you why.

Why the Test button makes this worse

The Test button fires at app level and skips the Page entirely. So it succeeds perfectly while the thing you actually care about is dead. We had a green test and zero real events for a long stretch before we accepted that both facts could be true at once.

One more trap in the same call. subscribed_fields replaces the whole set. When we later added Messenger by sending subscribed_fields=messages on its own, that silently unsubscribed feed and leadgen. List everything, every time.

Page-scoped endpoints want a Page token, and permissions won't save you

We'd done this properly, or thought we had. A System User with a token that never expires, the Page assigned with full control, every relevant permission ticked. That's the right way to hold long-lived credentials and it works for most of the Graph API.

Then this:

(#210) A page access token is required to request this resource.

It isn't a permissions problem. The endpoint wants a Page token specifically, and no amount of scope on a System User token substitutes for one. Fetching an individual lead fails the same way in a different disguise: GraphMethodException 100/33, which reads like "this object doesn't exist" and means "not with that token".

We burned an hour on the second one, because by then the lead genuinely didn't exist. We'd deleted and recreated the test lead in between. Two causes, one identical error message.

The fix is one extra hop. Call /me/accounts?fields=id,name,access_token with the System User token, then use the per-Page access_token it hands back for anything Page-scoped. The good part: a Page token derived from a non-expiring System User token doesn't expire either. No refresh logic, nothing re-minting credentials at 3am.

We hit this on subscribed_apps, on leadgen_forms, and on reading a lead. Assume anything belonging to a Page wants one.

Our automation platform was running code we weren't looking at

This one's on n8n rather than Meta, and it's the one we'd least like to repeat.

Newer n8n separates the draft you're editing from the version that's actually published. You edit a workflow, the canvas shows your changes, the workflow is marked Active, and production traffic still runs the last published version. We'd added an entire branch for lead ads, watched a real event arrive, and watched it take a route that no longer existed in the workflow on screen.

The tell was in the API response. versionId and activeVersionId were different values. Nothing in the interface said so.

If you're editing workflows programmatically rather than clicking Save in a browser, check whether your platform does this, and publish explicitly. "Active" does not mean "running what you just wrote".

A 400 nobody reads is a feature that doesn't exist

Two of our CRM writes had been failing for days. Every execution was green.

Both causes were mundane. Twenty CRM's task object has no body field. It's bodyV2, a rich text composite that takes an object rather than a string, and we'd guessed the field name from the shape of a GET response. Separately, the CRM rejects an entire contact create if the phone number fails validation, so one unparseable number was taking the name and email down with it.

The reason it went unnoticed is the part worth keeping.

Both nodes had error handling set to "continue", which is sensible for a webhook receiver you don't want crashing on one malformed payload. The effect is that a failed API call returns an error object as ordinary data, the workflow carries on, and the run is recorded as a success.

It gets worse. The two nodes failing this way were the error notification and the manual review task. The safety nets. They had never fired, not once. The thing built to tell us when something broke was the broken thing.

We changed the pattern. Acknowledge the webhook with a 200 immediately, then let the CRM writes fail loudly. Meta already has its receipt by that point, so a hard failure afterwards costs nothing and shows up red in the execution list where somebody will actually see it.

The one we couldn't fix

Meta's Lead Ads Testing Tool creates a real lead, in the real form, retrievable through the API. It does not reliably fire the leadgen webhook.

We verified both subscription levels from the dashboard and read them back through the API. We confirmed comments arriving on the same callback URL within seconds. We confirmed the dashboard's own leadgen test reaching our endpoint. Real test leads: nothing, every time.

Our best guess is that the notification hangs off ad delivery, and a test lead has no ad behind it. We can't prove that, and we couldn't find anyone who could.

What we'd do instead of trusting it: poll the form's leads on a schedule and process anything you haven't already seen, deduplicated on the lead ID. A dropped webhook then costs fifteen minutes instead of a lead you paid for. Meta will also push leads straight into a Google Sheet through its own integrations, which takes about two minutes and gives you an independent record to reconcile against.

One deliberate decision, in case it's useful

Comments and DMs create a task in our CRM. They do not create a contact.

Facebook gives you a display name and a page-scoped user ID. Instagram gives you a handle. Neither is an email address or a phone number. A contact record made from one of those is someone you cannot contact, and there's no reliable key to deduplicate on, so the same person commenting three times becomes three records.

Only people who complete a lead form become a contact. Everyone else becomes a task with their handle on it, and a human decides. It's written into the workflow's own notes so nobody later "fixes" it.

What we'd tell ourselves, starting this again tomorrow

None of this was one big mistake. It was a run of small ones, each invisible until it wasn't.

Before touching the dashboard

Make the subscribed_apps call for every Page and every field you care about, then read it back and look at what it says. Don't let the dashboard toggle be your evidence.

The moment you see a permissions error on a Page endpoint

Get a Page token before debugging anything else. Both errors we hit, #210 and 100/33, look like several other problems and are neither.

Verify the thing, not the thing next to it

This is the habit that would have saved us most of the day. Read the subscription back rather than trusting the toggle. Query the CRM for the record rather than trusting the node that claimed to write it. Compare the active version ID against the one you just saved.

Slower for the first hour. Considerably faster by lunchtime.

AI AutomationBehind the Build