action_source: the parameter the UI promises and the send ignores
It tells Meta where the conversion happened, and it influences attribution and optimisation. In a recent audit, 100% of one account's events went out as server — including the ones that happened in the person's browser.

action_source answers one simple question for Meta: where did this conversion happen? Website, app, phone, physical store, chat. It is a required field in the Conversions API, and one of the few that changes how Meta treats the event — not just whether it is accepted.
It is also one of the most quietly wrong fields out there, for a reason that has nothing to do with Meta.
What it actually changes
| Value | When to use it | What it changes |
|---|---|---|
| website | The person was on your site when it happened | Lets Meta match the click and use fbc and fbp |
| app | It happened inside the app | Attribution through the app identifier |
| chat | Closed over WhatsApp, Messenger or Instagram Direct | Connects with messaging campaigns |
| phone_call | Closed over the phone | Offline conversion, no browser signal |
| physical_store | In-store sale | Feeds store traffic measurement |
| system_generated | Generated by your system, no direct human action | Subscription renewal, for example |
| other | None of the above | Meta accepts it, but context is lost |
The most consequential case is website versus everything else. When the event is declared as coming from a site, Meta expects browser cookies and uses fbc and fbp to attribute precisely. When it is not, those fields lose weight — and you sent them for nothing.
The defect the audit found
In an audit of the payloads an integration actually delivered, action_source came through as server on 100% of events. Including page views and signups — which happened, by definition, in the person's browser.
The cause was not carelessness by whoever configured it. The screen offered the selector, saved the choice on the destination, and the code that assembles the send read the value from somewhere else, where nobody wrote. The selector worked. It just did not reach anything.
This is an entire family of defect, and it does not show up in tests: the interface promises a behaviour, the send ignores it, and Meta's report stays green because the event was accepted.
In the same audit, the data processing opt-out field had the same problem: offered on screen, zero occurrences in the sending code. The pattern is always the same — the option exists, the effect does not.
How to check yours in two minutes
Do not trust the configuration screen. Look at the body that went out. Any serious tool stores delivered payloads; if yours does not, that is already the answer.
{
"event_name": "Lead",
"action_source": "website", // <- check THIS value
"event_source_url": "https://...", // required when it is website
"user_data": { "em": ["..."], "fbc": "fb.1....", "fbp": "fb.1...." }
}Two quick checks cover most of it: if the value is website, event_source_url must exist; and if the event happened in a browser, fbc and fbp should be present on most sends. An event declared as coming from a site, with no URL and no cookie, is an event lying about its own origin.
The renewal case
There is a use for system_generated that almost nobody takes: subscription renewal. It is not a human action, there is no click, there is no browser, and sending it as website drags down the average of the whole account.
Sending renewal as a new sale is a bigger mistake still, but that one deserves its own piece — it inflates campaign return with money the campaign did not bring.
In CrazyLeads, action_source is resolved from the event's real origin, and the delivered payload is stored and visible in the delivery audit — which is where you check what went out, not what was configured.