Tracking & Attribution

How Do You Set Up the Pinterest Conversions API?

Pinterest's Conversions API takes nine event names, array-wrapped user data, and a string value field. The setup, and the three quirks that break it.

Quick answer

Setting up the Pinterest Conversions API means POSTing events to `https://api.pinterest.com/v5/ad_accounts/{ad_account_id}/events` with a conversion token as a Bearer credential. Three things differ from Meta and catch most implementations: Pinterest accepts exactly nine event names and rejects anything else, its hashed user-data fields must be arrays rather than scalars, and `custom_data.value` must be a string rather than a number.

Tell us what's broken. We'll fix your tracking — free.

Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.

Setting up the Pinterest Conversions API means POSTing conversion events to https://api.pinterest.com/v5/ad_accounts/{ad_account_id}/events with a conversion token as a Bearer credential. The mechanics resemble the Meta Conversions API closely enough that most developers assume they can port their Meta payload and change the URL.

They can’t, and the differences produce a 422 rather than a helpful error. Pinterest accepts exactly nine event names, its hashed user-data fields must be arrays where Meta takes scalars, and custom_data.value must be a string where Meta takes a number.


What do you need before you start?

Three things, and one of them is not an API credential.

A Pinterest ad account ID. It goes in the URL path, not in the payload. You’ll find it in Ads Manager.

A conversion token. This is generated in the Pinterest developer portal and scoped to a specific ad account. It is not an OAuth access token — there’s no authorization flow, no refresh, no app install. You paste a string and send it as Authorization: Bearer <token>. An OAuth access token also works, but the purpose-scoped conversion token is simpler and is what most setups use.

The Pinterest tag still installed on your site. This is the one people skip, and it’s the one that costs the most. Pinterest’s server-side matching leans on click_id — the epik token — more heavily than Meta’s does, because Pinterest’s own identity graph is weaker. The tag is what captures that token. Remove it and your server events still arrive; they just match far fewer people.


What events does Pinterest accept?

Exactly nine, and it rejects anything else with a 422.

Event name Typical use
page_visit A page or product view
view_category A collection or category page
search An on-site search
add_to_cart Item added to basket
checkout A completed purchase
signup Account registration
lead A form submission or enquiry
watch_video Video engagement
custom Anything that doesn’t fit the eight above

Two traps live in this table.

checkout means completed purchase, not checkout started. Pinterest has no begin-checkout event. The instinct is to fire checkout when someone reaches the payment page and again when they pay — which counts every real purchase two or three times and inflates reported conversions. Fire checkout only on a confirmed order.

custom is a literal event name, not a placeholder. If you have an event that isn’t one of the standard eight, the event_name you send is the literal string custom. Your own label for it goes in your own records, not on the wire. Sending your label as the event name gets a 422 for an unknown event, which is the single most common first-attempt failure.


What are the v5 formatting quirks that break implementations?

Three, and every one of them is a silent divergence from the Meta payload people port from.

1. Hashed user data must be arrays. Meta accepts "em": "<hash>". Pinterest’s v5 schema expects "em": ["<hash>"]. This applies to every hashed field — email, phone, first and last name, city, state, zip, country, external ID. One-element arrays are conventional.

The exceptions are the fields that aren’t hashed: click_id, client_ip_address and client_user_agent are plain single strings, exactly as in Meta.

2. custom_data.value must be a string. Send "value": "72.39", not "value": 72.39. This one is especially nasty because a numeric value doesn’t always fail loudly — it can be accepted and mishandled.

3. State must be the abbreviation, hashed. Pinterest’s match index expects the hash of ca, not the hash of california. Normalise to the two-letter code before hashing, or the field matches nothing while looking perfectly well-formed.

A minimal, correctly-shaped payload:

{
  "data": [
    {
      "event_name": "checkout",
      "action_source": "web",
      "event_time": 1757500000,
      "event_id": "a3f1c8...",
      "event_source_url": "https://yoursite.com/thank-you",
      "user_data": {
        "em": ["2a4b6c..."],
        "ph": ["9d1e3f..."],
        "ct": ["7b2a91..."],
        "st": ["4c8de0..."],
        "click_id": "dj0yJnU9...",
        "client_ip_address": "203.0.113.9",
        "client_user_agent": "Mozilla/5.0 ..."
      },
      "custom_data": {
        "currency": "USD",
        "value": "72.39",
        "order_id": "10482"
      }
    }
  ]
}

Pinterest also requires at least one of: a hashed email, or the combination of IP address and user agent. An event carrying neither is rejected rather than accepted-and-unmatched.

The same user-data field sent as a plain scalar for one platform and as a one-element array for Pinterest, with the unhashed fields called out separately


How does the epik click ID work?

epik is Pinterest’s click identifier, and it arrives two ways.

On a fresh ad click it’s in the URL as ?epik=.... On a later visit it’s in the _epik cookie, which Pinterest’s tag sets and which persists for roughly 30 days.

The correct resolution order is URL first, cookie second. The URL parameter describes the click that is happening right now, which is authoritative; the cookie is a snapshot of some earlier click. Take the cookie only when the URL has nothing.

Send it as click_id inside user_data, unhashed. It’s an opaque token, not personal data, and hashing it destroys it.

This is worth more attention on Pinterest than on other platforms. Pinterest’s ability to resolve a server event to a user without a click ID is weaker than Meta’s, so the cookie fallback on returning-visitor traffic is a real attribution lift rather than a nicety.


How do you deduplicate against the Pinterest tag?

With a shared event_id on both the browser event and the server event, exactly as on Meta — and with your own dedup as the real defence.

Derive event_id deterministically from something stable about the conversion: an order ID, a lead ID, hashed together with the event name and account. Two things follow. A retried webhook produces the same ID and therefore the same event, so it can’t double-count. And the browser tag and your server, seeing the same conversion, produce the same ID, so Pinterest can collapse them.

The part worth being blunt about: don’t rely on the platform’s deduplication as your only protection. If your automatic path and a manual re-send derive the ID differently, they are two different events as far as Pinterest is concerned, and both will count. A uniqueness constraint on your own side — one row per configuration and event ID — is what actually prevents double-attribution.


How do you know a Pinterest event actually landed?

Read the response body. An HTTP 200 does not mean the event was processed.

Pinterest returns a JSON envelope containing num_events_received, num_events_processed, and a per-event status. Two failure shapes hide behind a 200:

  • Envelope rejectionnum_events_received comes back as 0. The request was well-formed HTTP and the event inside it was not accepted.
  • Per-event rejection — the event is received but its status is something other than processed.

Neither raises an HTTP error. Monitoring that checks only the status line will report a perfectly healthy integration that is delivering nothing.

Pinterest also doesn’t provide a per-event error code — only top-level error responses carry numeric codes — so the per-event status and message are the whole diagnostic surface. There’s a test mode for this: append ?test=true to the endpoint and the event is validated without polluting real attribution.

Two operational limits worth knowing while you’re building. Events older than seven days are outside the accepted window, so historical backfills need their timestamps clamped rather than sent raw. And the rate limit is 5,000 calls per minute per ad account — generous for most accounts, but relevant if you’re replaying history.

A Pinterest response returning HTTP 200 while the envelope reports zero events received and the per-event status is not processed


How does PartialLeads send Pinterest conversions?

As one of five server-side conversion integrations, sharing the same identity and purchase pipeline rather than bolting on a separate Pinterest tool.

The epik token is captured at first touch and resolved correctly. The tag reads ?epik= from the URL and snapshots the _epik cookie, and dispatch prefers the URL value over the cookie. For a purchase, the click ID is drawn from the buyer’s session cluster — the first session carrying one wins — so a click that happened weeks before the order still supplies the identifier.

The payload is built to Pinterest’s schema, not Meta’s. Hashed fields go out as one-element arrays, click_id and IP and user agent stay plain, value is serialised as a string, and state is normalised to its two-letter form before hashing. These are handled in one canonical builder per platform rather than per call site.

Event names are constrained to the nine. A configuration labelled as custom sends the literal custom on the wire while keeping your own label in your records, so a descriptive internal name can’t cause a 422.

Pre-purchase steps aren’t mapped to checkout. Page and product views map to page_visit, collections to view_category, on-site search to search, add-to-cart to add_to_cart — and cart views, checkout-started and payment-info events map to nothing at all, deliberately, because Pinterest has no begin-checkout event and mapping them to checkout would multiply every real purchase.

A 200 is not treated as success. The response envelope is parsed; zero events received, or a per-event status other than processed, is recorded as a failure rather than a delivery. Failures are classified — authentication, permanent, rate-limited, transient, configuration — and only the ones that are genuinely final are written to the deduplication ledger, so a transient failure retries instead of being silently swallowed as a completed send.

Deduplication is enforced on our side. Event IDs are derived deterministically and a uniqueness constraint on configuration plus event ID means a redelivered webhook physically cannot double-fire.

A health strip and a test send. Seven-day sends, a per-day sparkline, delivery rate and failed count, with a test-send that always runs in Pinterest’s test mode so it can’t touch real attribution. Generic page-view rows with no product context are excluded from the volume figures, because counting navigation pixels as conversions would flatter the number.

Honest boundaries. Three. Match quality still depends on the _epik cookie being captured client-side — keep the Pinterest tag installed, because a server event without a click ID is an anonymous server event. Events are sent one per request rather than batched, which is fine at normal volume and is a real constraint during a large historical replay. And the seven-day event window is a platform-side limit: older conversions deliver with a clamped timestamp rather than their original one.

Problem PartialLeads mechanism Where you see it working
422 from an unknown event name Wire event constrained to Pinterest’s nine, custom label kept off the wire Accepted events in the activity log
Meta-shaped payload rejected Per-platform payload builder — arrays, string value, abbreviated state Retained request body per send
Purchases counted two or three times Pre-purchase steps deliberately unmapped; checkout only on a confirmed order Purchase count matching your order count
Click ID missing on returning visitors URL epik preferred, _epik cookie fallback, resolved across the session cluster Click ID present on cluster-matched purchases
HTTP 200 recorded as a delivery Envelope and per-event status parsed before success is declared Delivery rate on the health strip
Retried webhook double-fires Deterministic event ID plus a uniqueness constraint One row per conversion
No idea whether it works at all Health strip plus a test send in Pinterest’s test mode Sends, sparkline, delivery rate, failures

Tell us what's broken. We'll fix your tracking — free.

Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.

Sources

  1. Pinterest Developers — Track conversions in the API: https://developers.pinterest.com/docs/track-conversions/track-conversions-in-the-api/
  2. Pinterest Developers — API v5 introduction: https://developers.pinterest.com/docs/api/v5/introduction/
  3. Pinterest Business Help — Pinterest tag: https://help.pinterest.com/en/business/article/track-conversions-with-pinterest-tag
  4. Wikipedia — SHA-2 hash family: https://en.wikipedia.org/wiki/SHA-2

Frequently asked questions

QDo I still need the Pinterest tag if I'm using the Conversions API?
Yes, more than you do on Meta. The tag captures the `epik` click identifier, and Pinterest's server-side matching depends on that click ID more heavily than Meta's does because its own identity graph is weaker. Remove the tag and your events still arrive — they just match far fewer people. Run both and deduplicate with a shared `event_id`.
QWhy am I getting a 422 from the Pinterest Conversions API?
Most often an event name outside Pinterest's nine. If you have a custom event, the `event_name` on the wire must be the literal string `custom` — your own descriptive label is not a valid event name. The other common cause is user-data fields sent as scalars rather than one-element arrays, which is what happens when a Meta payload is ported directly.
QWhat's the difference between Pinterest's and Meta's Conversions API?
Structurally similar, three concrete differences. Pinterest accepts exactly nine event names and rejects the rest; its hashed user-data fields are arrays where Meta takes scalars; and `custom_data.value` is a string where Meta takes a number. Pinterest also has no begin-checkout event, so `checkout` means a completed purchase only.
QShould I fire `checkout` when someone starts checking out?
No. Pinterest has no begin-checkout event, and `checkout` is the completed-purchase event. Firing it at the payment page and again on confirmation counts every real sale at least twice, which inflates reported conversions and teaches the bidding algorithm to optimise toward a number that doesn't exist. Leave pre-purchase steps unmapped rather than approximating them.
QDoes a 200 response mean my Pinterest event was accepted?
No. Pinterest can return HTTP 200 while the envelope reports zero events received, or while the individual event's status is something other than processed. Neither raises an HTTP error. Parse `num_events_received` and the per-event `status` before recording a send as successful, or your delivery rate will be a measure of network connectivity rather than of tracking.
QHow far back can I send historical Pinterest conversions?
Seven days. Events with an older timestamp fall outside the accepted window. In practice you either clamp the timestamp to the edge of that window — the event delivers, but attributed to the clamp date rather than the true one — or you accept that older conversions can't be backfilled. Treat it as recovery within a recent window, not unlimited replay.
QWhere do I get the Pinterest conversion token?
From the Pinterest developer portal, scoped to a specific ad account. It's a purpose-built conversion token rather than an OAuth access token, so there's no app install or authorization flow — you generate it and paste it. An OAuth access token works too, but the conversion token is simpler to rotate and scoped more narrowly, which is the safer default.

Find the qualified leads your forms are currently throwing away.

Install PartialLeads on one landing page, send traffic, and compare what your CRM captured against what PartialLeads recovered and qualified.