CAPI · engineers
Tell Meta where the event happened
A website purchase with action_source=app is not a clever trick. It is a misfiled event. Physical stores and call centers have their own values because the matching graph is different.
Meta's nine values
vendor.meta-conversions-api.body.action_source.missing and .invalid enforce a fixed enum: email, website, app, phone_call, chat, physical_store, system_generated, business_messaging, other. There is no default. Omitting the field is an error, not 'probably website'.
website is for browser and web-app conversions. app is for in-app events (and needs the app parameters Meta documents: advertiser tracking enabled, extinfo, and friends). physical_store is POS. phone_call and chat are those channels. system_generated is not your cron inventing a source. other is a last resort, not a dump for events you have not classified.
website requires event_source_url
When action_source is website, Meta requires event_source_url. vendor.meta-conversions-api.body.website_requires_source_url is that pairing. Send the page URL, not a generic homepage, not the API path on your server.
The URL is how Meta ties the event to a verified domain and to the landing context. https://api.example.com/capi/meta is a helper-library bug. It will look like you convert on an origin that never ran a pixel.
The same English word, four enums
Pinterest CAPI action_source is web, app_android, app_ios, or offline. Lowercase web, unlike Meta website. vendor.pinterest-conversions-api.body.action_source.invalid is website pasted from a Meta client. Snap is WEB, OFFLINE, or MOBILE_APP. vendor.snapchat.body.action_source.invalid is website or web. Reddit CAPI v3 is WEBSITE, APP, PHYSICAL_STORE, or OTHER.
Bare JSON has no host. Pixellint's Meta, Snap, and Pinterest packs tell each other apart by action_source. A payload whose action_source is missing or misspelled matches more than one pack, and each reports it. That is why a typo looks like three vendors yelling at once.
app is not a website helper
Do not mark app events as website because your CAPI helper only implements website. Web-to-app purchases that actually charged in IAP are app events (or MMP S2S), not a website Purchase with a store URL. Two Purchases, one website and one app, with different event_id values, is how ROAS doubles on a download campaign.
Snap WEB requires event_source_url (vendor.snapchat.body.web_requires_source_url). Pinterest web does not use Meta's website spelling, so copying event_source_url logic without copying the enum still fails action_source first.
physical_store and offline
Offline uploads and CRM events. Matching leans on hashed PII, not on cookies. Using website here will look like a traffic spike from your HQ IP, because that is the client_ip_address you actually have. Pinterest and Snap spell this offline / OFFLINE. Reddit spells PHYSICAL_STORE.
Send the time of the sale, not the time of the CSV. Meta still wants event_time in seconds and will drop events older than about 7 days. Filing store sales as website does not extend that window.
Wrong enum, three packs
website is Meta. web is Pinterest. WEB is Snap. WEBSITE is Reddit. Copying the string across clients is the bug.
{
"event_name": "Purchase",
"event_time": 1770000000,
"action_source": "website",
"event_source_url": "https://shop.example/thanks",
"user_data": { "em": ["64-char-sha256-hex"] }
}
// Pinterest: "web" (and event_id is required)
// Snap: "WEB" (and event_name is PURCHASE)
// Reddit v3: "WEBSITE" (and event_at is 13 digits)
pixellint validate json @payload.json --rulepack vendor/meta-conversions-api
# vendor.meta-conversions-api.body.action_source.invalid
# vendor.meta-conversions-api.body.website_requires_source_url
Check the artifact
Paste the pixel URL or JSON body into the
playground. Same engine as
pixellint validate. Nothing leaves the tab.