pixellint

Blog · Pinterest CAPI

Pinterest CAPI wants checkout, lowercase. Purchase is a custom event that never trains.

Pinterest CAPI wants checkout, lowercase. Purchase is a custom event that never trains. A Meta body on api.pinterest.com/v5/ad_accounts/{id}/events is how ROAS never moves.

The paste looks close enough to ship. There are events, a clock, a hashed email, a click id. Every name is different. The host is api.pinterest.com, not graph.facebook.com. The event that means money is checkout, not Purchase. The clock is event_time in Unix seconds. The platform field is action_source web, not website. The tag side is pintrk('track', 'checkout'), not fbq('track', 'Purchase').

A Meta-shaped body on the Pinterest host can still look live. Custom names are legal. Unmapped custom events are untracked and cannot be used for reporting or optimization. That is the news, not a new validator feature.

The standard name is checkout, not Purchase

The Conversions API table is the enum. Name in API, name in the tag, purpose, and whether it is usable for audience creation and conversion reporting. checkout in the API is Checkout in the tag. Purpose: track people who complete transactions. Usable for audience creation and conversion reporting. add_to_cart, lead, page_visit, and signup sit next to it as the other names people actually optimize on. The rest of the enum is add_payment_info, add_to_wishlist, app_install, app_open, contact, customize_product, find_location, initiate_checkout, schedule, search, start_trial, submit_application, subscribe, view_category, view_content, and watch_video.

Purchase is not in that list. Meta's standard event is Purchase. Snap wants PURCHASE. Pinterest wants checkout. If you send Purchase, you have left the standard list. Pinterest lets you define a custom event with letters, numbers, underscores, and hyphens, up to 100 characters, case insensitive, up to 15 types per advertiser ID. You then map it to a standard event such as CHECKOUT. If you first passed the custom name on CAPI or the tag and never mapped it, those events are untracked. Ads Manager shows them under Custom events not tracked.

The API example is not gentle. A batch with event_name subscription and event_name checkout returns num_events_received 2 and num_events_processed 1. The failed row says Invalid event_name: subscription. Use a supported conversion event_name (for example subscribe, checkout). subscription is not subscribe. Purchase is the same class of miss with a Meta accent. Do not assume a 200 on the HTTP layer means the event trained a SALES campaign.

The envelope is not Graph, even when the keys rhyme

POST https://api.pinterest.com/v5/ad_accounts/{ad_account_id}/events. Auth is a conversion token from Ads Manager (Ad Account Overview, Conversions, Conversions API, Set up API) or an OAuth token with ads:write if you also call other endpoints. The path needs the advertiser id. A Meta pixel id on this host is the wrong identifier.

The body is data[] of events. Required per event: event_name, action_source, event_time, event_id, and user_data. action_source is the ingestion platform: app_android, app_ios, web, or offline. It is not device_type. Meta uses website for web. Pinterest enumerates web. Copying the Meta spelling is a miss on a vendor that lists web.

event_time is an integer Unix timestamp in seconds. The docs say enter as Unix timestamp in seconds. Date.now() is milliseconds. Math.floor(Date.now() / 1000) is the seconds helper. Sending thirteen digits is the OpenAI clock on a seconds field. event_id is required, not recommended. It is the dedup key with the tag. Pinterest keeps the first event and drops duplicates within 48 hours when event_id and event_name match across sources.

user_data is required and must include at least em, hashed_maids, or the pair of client_ip_address and client_user_agent. em is a SHA-256 hash of a lowercased email, as a list. IP and user agent stay plaintext. Hashing the IP because a Meta helper hashed em is how the event becomes unmatchable. click_id for web should be the _epik cookie when the epik query param is gone. event_source_url is optional but recommended for web, and should keep epik when you have it.

The tag says checkout too

The Pinterest Tag is a base code on every page plus an event code where you want conversions. The base code loads s.pinimg.com/ct/core.js, pintrk('load', tag id), pintrk('page'). The noscript image is ct.pinterest.com/v3/?tid=TAG_ID&noscript=1. Two base codes are two page visits.

Event code for a completed transaction is pintrk('track', 'checkout', { value, order_quantity, currency, event_id, line_items }). The tag table uses PascalCase labels in the docs (Checkout, AddToCart) and the JavaScript examples pass lowercase checkout. Match the example, not the column header, and match CAPI event_name to the same string so dedup works. A tag that fires Purchase and a server that posts checkout is two names. Dedup will not save you.

Value and quantity belong on checkout. The API wants custom_data.currency as ISO 4217, custom_data.value as a string parsed into a double (the example uses "66.95"), custom_data.order_id, and custom_data.num_items. contents[] holds per-item id, name, category, brand, price, quantity. Always send currency when you send value. Do not send a zero or a negative as a placeholder. Amount in minor units is a different vendor.

The body people will send first

A Meta CAPI purchase. This is not a Pinterest checkout event.

{
  "data": [{
    "event_name": "Purchase",
    "event_time": 1769818901,
    "action_source": "website",
    "event_source_url": "https://shop.example.com/checkout",
    "user_data": { "em": ["64ec88ca..."] }
  }]
}

That object is a legal Meta CAPI event if the hash and the rest of user_data are right. Posted to api.pinterest.com it is the wrong event name, the wrong action_source enum, and it is missing event_id. If Pinterest treats Purchase as a custom name, it still does not train until you map it to CHECKOUT. If the API rejects it the way it rejected subscription, you get a failed row and num_events_processed less than received.

The Pinterest shape for the same order is event_name checkout, action_source web, event_time in seconds, event_id reused from pintrk, user_data.em plus IP and UA, and custom_data with currency, value, order_id, num_items. Batches can hold up to 1,000 events. Test events use ?test=true and land in a sandbox. They are not processed for reporting or optimization. Leaving test=true in production is the same class of miss as leaving Meta test_event_code on.

Names that do not translate

Optimization still wants CHECKOUT

Under campaign objective simplification, conversion campaigns use SALES or LEADS. The conversion you optimize for sits on the ad group. Bid and delivery optimization is supported for CHECKOUT, ADD_TO_CART, SIGNUP, or LEAD. Ads Manager can report more specific events such as INITIATE_CHECKOUT, but a SALES campaign that never sees checkout will not train on Purchase. Include conversion_tag_id when conversion_event is one of those four, or validation fails.

Hybrid is the documented coverage: tag plus API, same event_id, same event_name. Pinterest retains the first event and removes duplicates within 48 hours. If the tag fires checkout and CAPI fires Purchase, they are not duplicates. They are a standard event plus an unmapped custom, or a failed row. Generate event_id on the order. Pass it to the page. POST it on the server. A random UUID in each pipe is two checkouts.

Event quality is a different API. Coverage on checkout for order_value, product id, click_id, and user agent is how you see whether the envelope is empty even when the name is right. A 200 with checkout and no value is a processed event that still cannot report ROAS. The docs tell you to send events in real time or with a maximum lag of one hour. A nightly warehouse dump of Purchase rows is late and still the wrong name.

If you first passed custom events using the Conversions API, Pinterest Tag, or other ingestion methods, make sure to also map them to Pinterest standard events using the advertiser-defined event endpoints in this guide or in Ads Manager. Otherwise, those events are untracked and cannot be used for reporting or optimization.

Pinterest Developers: define custom events

What to do

Keep one internal event: name, UTC instant, order id, hashed email, landing URL, click references. At the edge, emit checkout with action_source web and event_time in seconds. Persist Meta event_id and Pinterest event_id as the same order-scoped value if both pixels fire, but do not send the Meta event_name to Pinterest. Capture epik on the landing hit and copy it through checkout as click_id or on event_source_url.

Do not point a shared CAPI JSON at a new host. Lint the body you actually POST. The Pinterest pack is vendor/pinterest-conversions-api. The Meta pack is vendor/meta-conversions-api. One fixture cannot serve both. Strip test=true in production. If you must keep Purchase as an internal name, map it to CHECKOUT with advertiser_defined_events, then still prefer sending checkout so the enum matches the tag.

The contract page for the field list is the Pinterest conversions API guide. This post is the market fact: the money event is checkout, and Purchase is how ROAS never moves. Pixellint is not affiliated with Pinterest or Meta. Passing a linter means the artifact matches the published envelope. It does not mean Ads Manager attributed the order, and it does not mean event quality is high enough to bid.

Checklist

Sources

Contract pages

The dated argument is above. These pages are the field lists.

Open the Pinterest CAPI pack Docs