pixellint

CAPI · engineers · marketers

Value without currency is a non-event

Pixellint flags Meta Purchase without value and currency, and GA4 value without currency. The vendor docs are blunt. Dashboards that show conversions but $0 ROAS are often a string in the value field.

Purchase is a money event

Meta CAPI Purchase requires custom_data.value and custom_data.currency (vendor.meta-conversions-api.body.purchase_requires_value_and_currency). The browser pixel requires cd[value] and cd[currency] on Purchase (vendor.meta.purchase_requires_value_and_currency). A payload that only names the product is legal JSON and an incomplete conversion.

GA4 Measurement Protocol purchase needs currency, value, transaction_id, and items (vendor.google-analytics.body.purchase_requires_ecommerce_fields). Any event with value set needs currency (vendor.google-analytics.body.value_requires_currency). TikTok flags properties.currency when present if it is not a three-letter code (vendor.tiktok-events-api.body.properties.currency.invalid).

JSON numbers, not display strings

Send 19.99, not "$19.99", not "19,99" unless you like locale bugs. Divide by 100 if your store keeps cents as integers, but then you must be consistent with refunds. Mixing 1999 on the pixel and 19.99 on CAPI is a ROAS fight that looks like a dedup bug.

Type coercion in a tag manager (string from the dataLayer, number on CAPI) is how one pipe trains on $19.99 and the other on $0. Validate both artifacts. The HTTP 200 will not.

Currency is ISO 4217

USD, EUR, GBP. Three letters. US, $, usd, dollar, and 840 are all wrong on someone. Meta's custom_data.currency contract is a three-letter pattern. Multi-currency shops must send the transaction currency, not the headquarters currency, unless you converted and documented it in the event dictionary.

A shop that charges EUR and reports USD because the ads account is USD will look like a miracle ROAS or a disaster, depending on the rate. Convert once, label it, or send native currency and let the platform convert. Do not mix the two week to week.

Tax and shipping are a policy

Agree whether value is revenue you want to optimize toward. If ads see tax-inclusive and finance sees product-only, everyone argues about ROAS. Put the policy in the event dictionary, not in Slack. Refunds must use the same definition or partial refunds will not net out.

Using 1 as a placeholder so the field is 'required and present' trains value optimization to bid toward $1 orders. That is worse than omitting Purchase until the amount is real.

LinkedIn needs both halves too

LinkedIn CAPI conversionValue is an amount plus currencyCode. vendor.linkedin-conversions-api.body.value_needs_both_fields flags an amount without a code. Same idea, different names. X conversion API price_currency is ISO 4217 when present.

Do not send value on events you will not optimize for money. A Lead with value: 1 and currency USD is a fake purchase the model will chase. Leave value off, or send a real pipeline amount you are willing to bid toward.

Number plus three letters

Wrong is a symbol, a locale string, or value without currency. Right is 84.50 and USD on both pipes.

// Wrong Meta Purchase
{ "event_name": "Purchase", "custom_data": { "content_name": "Shoes" } }

// Right
{ "event_name": "Purchase", "custom_data": { "value": 84.50, "currency": "USD" } }

pixellint validate json @meta.json --rulepack vendor/meta-conversions-api
# vendor.meta-conversions-api.body.purchase_requires_value_and_currency

// GA4: value requires currency; purchase also needs transaction_id and items
{
  "events": [{
    "name": "purchase",
    "params": {
      "currency": "USD",
      "value": 84.50,
      "transaction_id": "1842",
      "items": [{ "item_id": "sku-9", "item_name": "Shoes", "price": 84.50, "quantity": 1 }]
    }
  }]
}

pixellint validate json @ga4.json --rulepack vendor/google-analytics
# vendor.google-analytics.body.value_requires_currency
# vendor.google-analytics.body.purchase_requires_ecommerce_fields

Check the artifact

Paste the pixel URL or JSON body into the playground. Same engine as pixellint validate. Nothing leaves the tab.