Skip to content

Pinterest CAPI (Server-Side)

The Pinterest Conversions API (CAPI) sends events from your server directly to Pinterest Ads, sidestepping the browser restrictions that increasingly blind the client-side Pinterest Tag. For any advertiser running meaningful Pinterest spend — especially in shopping categories where Safari and mobile web traffic is significant — CAPI is the difference between attribution you can trust and attribution that drifts downward every quarter.

Valid as of April 2026, Pinterest Conversions API v5.

Pinterest’s audience skews heavily toward iOS and mobile web — exactly the cohort most affected by Safari’s Intelligent Tracking Prevention and by content/ad blockers. The client-side Pinterest Tag is missing events for a meaningful share of converters long before you notice it in reporting.

The three concrete problems CAPI solves:

  1. ITP caps client-side cookies at 7 days on Safari, shortening Pinterest’s attribution window for a large slice of its audience.
  2. Shopping-category ad blockers (uBlock, Brave) routinely block s.pinimg.com pixel loads.
  3. Pinterest’s click identifier (epik) is sometimes stripped by corporate proxies, so click-side attribution quietly fails without CAPI as a fallback.

When it is not worth it: If Pinterest is less than ~5% of your paid spend and your sGTM container doesn’t already exist for another platform, the marginal attribution lift probably doesn’t justify the setup effort. Run the client-side tag alone and revisit when spend grows.

Pinterest CAPI v5 accepts a batch of events at a per-ad-account endpoint. Every event needs a name, an action_source, a timestamp, and at least one user match key.

Required per event:

FieldNotes
event_nameOne of Pinterest’s standard names (see below) or custom
action_sourceweb, app_android, app_ios, or offline
event_timeUnix epoch seconds
user_dataAt least one hashed identifier OR IP + UA

Standard event names: page_visit, view_category, search, add_to_cart, checkout, signup, lead, watch_video, custom.

Match keys (all SHA-256 hashed, lowercase trimmed, UTF-8):

FieldSourceHashed?
emEmailYes
phPhone (E.164 first)Yes
external_idYour internal user IDYes
hashed_maidsMobile advertising IDsYes
client_ip_addressRequest headerNo
client_user_agentRequest headerNo
click_idepik URL param or _epik cookieNo

The click_id is Pinterest’s equivalent of Meta’s fbc — it travels from an ad click through the URL and into the landing page cookie. Preserving it through to the server-side event is the single biggest quality lever.

  1. Generate a Conversion Access Token: Pinterest Business → Conversions → API access → Create token. Store it in an sGTM User-Defined Variable of type “Constant.” Do not hardcode it in tag templates.

  2. Get your Ad Account ID from the Pinterest Business → Business manager URL (the numeric ID after /accounts/). Store it as a second constant variable.

  3. In your sGTM container, go to Tags → New → Community Template Gallery. Search “Pinterest Conversions API” and install Pinterest’s official template. If your region does not have the template, use a Custom Template with the HTTP shape shown below.

  4. Configure the tag:

    • Ad Account ID: your stored variable
    • Conversion Access Token: your stored variable
    • Event Name: mapped from GA4 event name (see mapping table)
    • Event ID: mapped from the event_id event parameter
  5. Add user data variables: Cookie Variable for _epik, Request Header Variable for X-Forwarded-For and user-agent, and Event Data variables for any hashed em / ph / external_id pushed into the GA4 event.

  6. Set the firing trigger on the relevant GA4 events (purchase, add_to_cart, view_item, generate_lead, sign_up).

Raw HTTP shape (for custom templates or debugging):

POST https://api.pinterest.com/v5/ad_accounts/<ad_account_id>/events
Authorization: Bearer <conversion_access_token>
Content-Type: application/json
{
"data": [{
"event_name": "checkout",
"action_source": "web",
"event_time": 1713571200,
"event_id": "evt-ORDER-12345-1713571200",
"event_source_url": "https://example.com/thank-you",
"user_data": {
"em": ["<sha256 of email>"],
"ph": ["<sha256 of phone>"],
"external_id": ["<sha256 of user id>"],
"client_ip_address": "203.0.113.45",
"client_user_agent": "Mozilla/5.0 ...",
"click_id": "dj0yJnU9..."
},
"custom_data": {
"currency": "USD",
"value": "79.99",
"order_id": "ORDER-12345",
"content_ids": ["SKU-A", "SKU-B"],
"num_items": 2
}
}]
}

Pinterest’s server-side names mostly match the client-side tag, with a few quirks: checkout (not purchase) is the purchase event, and there is no explicit BeginCheckout.

GA4 eventPinterest CAPI eventNotes
page_viewpage_visitOptional server-side if client covers it
view_itempage_visit with content_idsOr custom if you want a distinct event
view_item_listview_categoryPass category name in custom_data
searchsearchPass search_string
add_to_cartadd_to_cart
begin_checkoutcustom (name: BeginCheckout)Pinterest has no native BeginCheckout
purchasecheckoutThe critical one — value, order_id, content_ids
generate_leadlead
sign_upsignup

Parameter mapping for checkout (purchase):

// sGTM event data variables mapped into the Pinterest CAPI tag
{
event_name: "checkout",
event_id: eventData.event_id, // from dataLayer
event_time: Math.floor(eventData.timestamp_ms / 1000),
event_source_url: eventData.page_location,
custom_data: {
currency: eventData.ecommerce.currency,
value: String(eventData.ecommerce.value), // Pinterest prefers stringified decimals
order_id: eventData.ecommerce.transaction_id,
content_ids: eventData.ecommerce.items.map(i => i.item_id),
num_items: eventData.ecommerce.items.length
}
}

Pinterest deduplicates by event_id plus event_name. If both the client-side Pinterest Tag and the CAPI event carry the same event_id for the same event name, Pinterest counts them once.

The dedup window is approximately 7 days — generous compared to Meta’s 48 hours, but do not rely on the slack: generate fresh event_id values per event firing.

Propagation flow:

  1. Your frontend generates a stable event ID and pushes it with the purchase event:
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: 'purchase',
event_id: 'pin-' + order.id + '-' + Date.now(),
ecommerce: {
transaction_id: order.id,
currency: 'USD',
value: 79.99,
items: [...]
}
});
  1. Client-side GTM maps {{DL - event_id}} to the Pinterest Tag’s event_id parameter.
  2. The GA4 tag sends the same event_id to sGTM as an event parameter.
  3. The sGTM Pinterest CAPI tag reads event_id from the Event Model and sends it on.

Pinterest Ads Manager → Conversions → Tag Health shows inbound events broken down by source (Tag, CAPI, both). After you deploy:

  1. Fire a test purchase on staging with real event_id propagation.
  2. Wait ~5 minutes — CAPI events typically appear within 2–5 minutes; tag events within 1 minute.
  3. In Tag Health, confirm the event shows under both “Pinterest Tag” and “Conversions API.”
  4. Check the “Match Quality” score Pinterest displays — below 5 means you are not sending enough match keys.

Pinterest does not offer a “test event code” mechanism like Meta, so test events go into your real data. Use a staging ad account if you have one, or at minimum filter by timestamp in Tag Health during test windows.

Sending page_visit from both Tag and CAPI without dedup

Section titled “Sending page_visit from both Tag and CAPI without dedup”

The Pinterest Tag fires page_visit on every page load automatically. If your sGTM also sends page_visit for every GA4 page_view event with a different event_id, you double-count page views. Either skip page_visit server-side entirely, or propagate the same event_id for both.

Forgetting to normalize phone numbers before hashing

Section titled “Forgetting to normalize phone numbers before hashing”

Pinterest expects phone in E.164 (+15551234567) normalized to lowercase — ph: sha256("+15551234567"). Hashing (555) 123-4567 gives a different hash, and the match fails silently. Normalize on the server before hashing.

Using Pinterest Tag ID instead of Ad Account ID

Section titled “Using Pinterest Tag ID instead of Ad Account ID”

The endpoint path takes the Ad Account ID (from Business Manager), not the Pinterest Tag ID (which is a separate numeric value used in the client-side pixel). Mixing them up gives a 404 on every request.

value without currency is treated as unknown and silently excluded from ROAS reporting. Always send both together.