Conversions for Meta and Google Ads
Tell Meta and Google Ads which purchases, appointments and leads came from your ads, through the gate that decides what qualifies.
When an ad brings in someone who ends up buying, booking or leaving their details, the ad platform needs to know in order to optimize. With met.conversions you report that fact from your system —the one that knows the payment came in— and Met carries it to Meta (Conversions API) and to Google Ads.
You do not emit directly. Everything you report goes through the conversion gate, the same one the conversions proposed by the Met and by flows go through. The gate checks that there is an ad behind it, that the event is on the business's list and that the fact is backed up, and it tells you what it decided. That is what keeps a buggy integration from inflating campaign results.
There are two paths:
| Method | What for | Identified by |
|---|---|---|
conversions.report() | A conversion by a contact who came in through a WhatsApp, Messenger or Instagram ad. | contact_id |
conversions.reportWeb() | The server-side mirror of an event from your site's Pixel. | The Pixel's event_id and the visitor's cookies or details |
Before you start
- Your key needs
conversions:write. It does not work with a test key (403test_mode_restricted): every accepted conversion is a real event in the ad account. - The business has to have conversions turned on in the panel, with its list of events. Without that, everything comes out
discardedwithreason: "integration_inactive". - Both methods return
201even when the gate discards: always readdecision.
A contact's conversion
const r = await met.conversions.report({
contact_id: 4821,
event_name: 'Purchase',
value: 250000,
currency: 'COP',
bucket: 'pedido-10293',
});
if (r.decision !== 'accepted') console.log('Not sent:', r.decision, r.reason);r = met.conversions.report(
contact_id=4821,
event_name="Purchase",
value=250000,
currency="COP",
bucket="pedido-10293",
)
if r["decision"] != "accepted":
print("Not sent:", r["decision"], r.get("reason"))curl -X POST https://api.met.meteor.com.co/api/v1/conversions \
-H "Authorization: Bearer $METEOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contact_id":4821,"event_name":"Purchase","value":250000,"currency":"COP","bucket":"pedido-10293"}'| Field | What it is |
|---|---|
contact_id | The CRM contact (see CRM and contacts). It has to have come in through an ad: otherwise it comes out no_attribution. |
event_name | An event on the business's list. For WhatsApp, Meta asks for messaging names: Purchase, LeadSubmitted, InitiateCheckout… not the web Pixel ones (Lead, Contact). |
value, currency | Value and three-letter ISO 4217 currency (COP, USD). A purchase requires them. |
evidence | What you are basing it on, in one sentence ("paid invoice 10293"). Almost every event requires it. |
bucket | What makes the conversion unique: the order id, the appointment id. Without it, the unit is the contact: the same contact only converts once per event. |
What the gate requires
| Event | For it to go out |
|---|---|
Purchase | value greater than zero and a valid currency. |
LeadSubmitted, InitiateCheckout, AddToCart, Schedule, ReviewProvided, QualifiedLead | evidence. |
| Any other event on the list | evidence. |
On top of that, a contact does not accept more than 10 conversions in 24 hours (the default cap): it is the brake for a flow stuck in a loop.
A web conversion (Pixel)
If your site fires the Pixel in the browser, also report it from your server with the same event_id: Meta merges the two and does not count it twice.
// In the browser: fbq('track', 'Purchase', { value: 250000, currency: 'COP' }, { eventID: 'ord-10293' });
const r = await met.conversions.reportWeb({
event_name: 'Purchase',
event_id: 'ord-10293',
fbp: req.cookies._fbp,
fbc: req.cookies._fbc,
email: customer.email,
event_source_url: 'https://mystore.co/thanks',
client_ip_address: req.ip,
client_user_agent: req.headers['user-agent'],
value: 250000,
currency: 'COP',
});r = met.conversions.report_web(
event_name="Purchase",
event_id="ord-10293",
fbp=request.cookies.get("_fbp"),
email=customer_email,
client_ip_address=visitor_ip,
client_user_agent=visitor_user_agent,
value=250000,
currency="COP",
)curl -X POST https://api.met.meteor.com.co/api/v1/conversions/web \
-H "Authorization: Bearer $METEOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"event_name":"Purchase","event_id":"ord-10293","fbp":"fb.1.1730000000.123","gclid":"Cj0KCQ…","value":250000,"currency":"COP"}'- At least one way of recognizing the visitor is required:
fbp,fbc,email,phoneorexternal_idfor Meta;gclid,gbraid,wbraid,email,phoneorexternal_idfor Google Ads. With none, it comes outno_match_key. email,phoneandexternal_idtravel in plain text to Met and leave Met as SHA-256 hashes;fbp,fbcand Google's click identifiers go as they are.client_ip_addressandclient_user_agentare the visitor's, not your server's: they improve matching.- This path does not ask for
evidence: the fact already happened in the browser. - If the visitor came in through a Google ad, also pass
gclid(orgbraid/wbraid), taken from the landing URL.
What it returns
{ "decision": "discarded", "reason": "uncorroborated" }
decision | What happened |
|---|---|
accepted | It passed the gate and is on its way to the platform. eventId is the key it travels with. |
pending_corroboration | It is waiting for a confirmation from the CRM. It does not happen through the API —your system is the source of the fact—: it happens to a purchase or a qualified lead proposed by the Met or a flow. |
duplicate | An identical one already went out: same contact, event and bucket, or the same event_id on the web. It is not sent twice. |
discarded | It does not go out. reason says why. |
reason | What to do |
|---|---|
integration_inactive | The business does not have conversions turned on. |
no_attribution | The contact did not come in through an ad (or their click is no longer usable). |
not_in_taxonomy | event_name is not on the business's list. |
uncorroborated | evidence was missing, or value and currency on a purchase. |
rate_limited | The contact already had 10 accepted conversions in 24 hours. |
missing_waba | The contact is on WhatsApp and the WhatsApp Business account is missing from the configuration. |
no_match_key | A web conversion with no way of recognizing the visitor. |
Hearing about delivery
accepted says it left the gate; delivery to Meta comes afterwards. Subscribe to the events (scope conversions:read; see Webhooks):
| Event | When |
|---|---|
conversion.sent | Meta received the conversion. Carries event_id, event_name and Meta's fbtrace_id. |
conversion.discarded | The gate discarded a conversion, whether you, the Met or a flow proposed it. Carries reason. |
Common errors
| Code | What happened |
|---|---|
400 validation_error | contact_id or event_name is missing (event_id on the web), or a field exceeds its maximum length. |
403 missing_scope | The key does not carry conversions:write. |
403 test_mode_restricted | A met_test_ key does not report conversions. |