HomeGuidesAPI Reference
ChangelogHelp CenterCommunityContact Us
Guides

Integrate an ad platform

Learn how to build a Klaviyo audience sync and ad platform integration.

Overview

This resource will help you as you build an integration between an ad platform and Klaviyo. It covers the two flows every successful ad integration is built on — syncing Klaviyo audiences to your platform, and sending ad engagement events back to Klaviyo profiles — plus authentication, certification, and an API quick reference.

Why connect your ad platform to Klaviyo

Klaviyo sits on the customer data layer for ~200,000 brands. When merchants connect their Klaviyo account to your ad platform, you get measurable wins on the metrics they actually care about:

  • Higher ROAS. Klaviyo-built segments beat platform-native targeting. Enterprise merchants run every Meta, Google, and TikTok campaign against a Klaviyo audience — when that audience comes from your integration, you get the credit.
  • More revenue per customer. Klaviyo's ad integrations have driven +10.5% profile growth (Google case study) and meaningful web traffic lift. Bigger profile pool + retargeting on high-LTV segments = more orders per acquired customer.
  • Less wasted spend. Suppression audiences — excluding recent purchasers, unsubscribers, churn risks — are the highest-conviction use case across every merchant conversation we have.
  • Stickier merchants. Klaviyo's current ad integrations have ~30% install rates and hundreds of millions in associated ARR. A live audience-sync integration is one of the strongest signals a merchant stays on your platform.

Short version: Klaviyo audiences → your ad platform = higher ROAS, lower CAC. Your ad events → Klaviyo profiles = flows like "clicked ad, didn't buy." These two flows are how every successful integration is built.

Audience sync: Klaviyo → your platform

Goal: mirror a Klaviyo list or segment as a custom audience in your ad platform. One-way push from Klaviyo to you. There's no webhook for list/segment membership changes today, so partners maintain a local snapshot and compute the daily delta themselves.

The flow

  1. Let the merchant pick an audience. Show a dropdown of their Lists and Segments. Lists are static; segments are dynamic ("placed an order in the last 30 days," "VIPs"). Most ad use cases target segments.
  2. Pull current membership. Page through GET /api/lists/{id}/profiles or GET /api/segments/{id}/profiles. Grab email/phone, hash client-side.
  3. Store a snapshot. Persist today's membership keyed by Klaviyo list/segment ID — this is the input to tomorrow's diff.
  4. Run a daily diff. Compare today's pull against yesterday's snapshot: ADD = in today, not yesterday → push to your audience's add endpoint. REMOVE = in yesterday, not today → push to your remove endpoint.
  5. Replace yesterday's snapshot with today's. Tomorrow's diff runs against this.

Example: Day 0 = {1, 2, 3}, Day 1 pull = {2, 3, 4}, diff = add 4, remove 1. Two batched calls. Initial sync is the same flow with an empty Day 0.

Endpoints

  • Get Lists (GET /api/lists) and Get Segments (GET /api/segments) — populate the dropdown. Persist the Klaviyo id, not the name.
  • Get Profiles for List (GET /api/lists/{id}/profiles) / Get Profiles for Segment (GET /api/segments/{id}/profiles) — pull current members. Paginate via the cursor in the response.
  • GET /api/segments?filter=equals(is_active,true) — recommended filter so your dropdown doesn't show empty segments.

Practical notes

  • Cadence: Daily is the right default; hourly is fine if your platform expects fresher audiences. Klaviyo's own Meta/TikTok syncs run roughly hourly.
  • Identifiers: Profiles are keyed by email or phone (hashed for the ad platform). Only the primary email syncs. Match rates against the ad platform's user graph won't be 100% — normal.
  • Rate limits: Per-account, not per-app. Daily pulls of a few segments are well under any limit. For very large merchants, batch overnight and back off on 429s.
  • 1:1 mapping: Map each Klaviyo list/segment to exactly one custom audience in your platform. Klaviyo as source of truth, your platform mirrors it. The merchant should always edit the segment in Klaviyo.
  • First sync lag: Tell merchants the initial sync can take up to 48 hours to show up in their ad account — that's the destination's processing time, not Klaviyo's.

Event sync: your platform → Klaviyo

Audience sync alone is table stakes. The integrations merchants actually rave about are bidirectional: you also send ad-engagement events back into Klaviyo, tied to the profile. Once a profile has "Clicked Ad" events on it, the merchant can use that data anywhere they use Klaviyo data — flows, segments, campaigns, suppressions.

What to send

  • Served Ad / Impression — a profile was eligible to see an ad and it fired.
  • Viewed Ad — if you can distinguish viewable from served-but-not-seen.
  • Clicked Ad — merchants care most about this one. It's the trigger for the biggest flows.
  • Ad Conversion / Purchase from Ad — if your attribution model produces one.

Properties: campaign ID, creative ID, ad group/placement, timestamp (ISO 8601 UTC), value + currency for conversions, and UTMs if you have them.

Endpoints

  • Create Event (POST /api/events) — single event, tied to a profile by email or phone. Can update profile properties inline.
  • Bulk Create Events (POST /api/event-bulk-create-jobs) — required if you push high event volume (millions/day).
  • Bulk Import Profiles (POST /api/profile-bulk-import-jobs) — for seeding new profiles (e.g., ad-generated leads), not just attaching events.

Klaviyo matches by email first, then phone, then external ID. If a profile doesn't exist yet, the events endpoint creates it — so ad-generated leads can land on first click, ready to enter the merchant's welcome flow.

How merchants actually use it

  • Clicked but didn't buy. Segment = "Clicked Ad in last 7d" AND NOT "Placed Order in last 7d" → triggers a follow-up flow. The single most-requested use case across every ad partner conversation we have.
  • Suppress recent purchasers. Segment of recent purchasers syncs back to your platform as a suppression audience. Stops paying to advertise to people who already converted.
  • Ads inside flows. Browse-abandonment flow becomes: email 1 → sync to your ad platform → wait 2 days → if no purchase, email 2 → if no purchase by day 14, stop advertising. Ads as a journey channel, not a separate budget line.
  • Lookalikes off VIPs. Merchant defines "best customer" with rich Klaviyo behavioral data (3+ orders, AOV > $200), syncs that as the seed audience. Stronger than whatever proxy your platform would infer alone.
  • Attribution. Ad events on the profile alongside email opens, SMS, browse — merchant can finally answer "what did this customer see before they bought." The strategic reason the CMO greenlights the integration.

Auth, scopes, and certification

Build as a public OAuth app. Private API keys are a non-starter for the marketplace. Start with the Create a public OAuth app guide.

Scopes: lists:read, segments:read, profiles:read for audience sync; add profiles:write if you create profiles; events:write for event sync. Don't over-scope — it gets flagged in review and slows certification.

Once functional, submit for App Marketplace review. Plan for at least one round of feedback — covers OAuth, scopes, setup UX, and that the integration delivers what your listing claims.

API quick reference

Concrete specs for AI coding assistants and engineers grounding on this doc. The narrative above is the why and the flow; this section is the what-to-actually-type.

Headers (required on every request)

Authorization: Bearer <oauth_access_token>
revision: 2026-04-15
Accept: application/json
Content-Type: application/json

The revision header is required. Pin to a specific date when you ship; check the API versioning guide for the current latest. Without it you'll get a 400.

Pagination

Klaviyo uses cursor-based pagination via JSON:API envelopes. Don't compute offsets yourself — follow the cursor.

GET /api/segments/{id}/profiles?page[size]=100
→ response.links.next contains the full next-page URL
→ loop until links.next is null

Filter DSL

Klaviyo uses a custom filter syntax on list endpoints, not SQL/MongoDB-style. Examples:

filter=equals(is_active,true)
filter=greater-than(updated,2026-01-01T00:00:00Z)
filter=any(email,["[email protected]","[email protected]"])
filter=and(equals(is_active,true),greater-than(created,2025-01-01))

Profile object shape

Returned by Profiles-for-List/Segment endpoints. The fields you care about:

{
  "data": [{
    "type": "profile",
    "id": "01HXXX...",
    "attributes": {
      "email": "[email protected]",
      "phone_number": "+15555550100",
      "external_id": "merchant_internal_id_123"
    }
  }],
  "links": { "next": "..." }
}

Hashing PII for ad-platform handoff

Every major ad platform expects SHA-256, lowercase, trimmed of whitespace, no salt:

hash = sha256(email.strip().lower()).hexdigest()
# e.g. "  [email protected] " → "[email protected]" → sha256

Event POST body

POST /api/events

{
  "data": {
    "type": "event",
    "attributes": {
      "properties": {
        "campaign_id": "abc123",
        "creative_id": "xyz789",
        "placement": "feed"
      },
      "metric": {
        "data": {
          "type": "metric",
          "attributes": { "name": "Clicked Ad" }
        }
      },
      "profile": {
        "data": {
          "type": "profile",
          "attributes": { "email": "[email protected]" }
        }
      }
    }
  }
}

Retries

  • 429 — respect the Retry-After header. Otherwise exponential backoff starting at 1s, cap at 60s, max 5 retries.
  • 5xx — same backoff.
  • 4xx (other than 429) — don't retry. Log and surface to the merchant.

Daily diff — end-to-end pseudocode

yesterday = load_snapshot(merchant_id, klaviyo_segment_id)
today = set()
cursor = None
while True:
    url = f"/api/segments/{segment_id}/profiles?page[size]=100"
    if cursor: url = cursor
    resp = klaviyo.get(url, headers=auth_headers)
    for p in resp['data']:
        email = p['attributes']['email']
        if email: today.add(sha256_lower(email))
    cursor = resp['links'].get('next')
    if not cursor: break

adds    = today - yesterday
removes = yesterday - today

ad_platform.add_to_audience(audience_id, adds)
ad_platform.remove_from_audience(audience_id, removes)
save_snapshot(merchant_id, klaviyo_segment_id, today)

Glossary

  • Klaviyo list ≈ static custom audience (membership only changes via explicit add/remove).
  • Klaviyo segment ≈ dynamic custom audience (membership computed from rules; can change as profile data changes).
  • Klaviyo profile ≈ user record. Keyed by email, phone, or external ID.
  • Klaviyo metric ≈ event type. "Clicked Ad," "Placed Order," etc. Created automatically on first event ingest.
  • Klaviyo flow ≈ automated journey. Triggered by metrics (events) or list/segment membership.
  • Klaviyo campaign ≈ one-off send (email or SMS) to a list/segment. Not relevant for ad sync but appears in shared dashboards.

Resources


Did this page help you?