Skip to main content
GUIDE · APPLE SEARCH ADS9 min read

Apple Search Ads Campaign Management + conversion upload

A practitioner guide for app teams using TrackLayer to connect Apple Search Ads campaign management, AdServices attribution, and server-side conversion delivery without turning the mobile app into the only source of truth.

TL;DR

The setup in one pass

  • Apple Search Ads matters when your app installs and post-install events need to be tied back to backend truth instead of only in-app SDK timing.
  • The production setup depends on five credentials working together: Apple Ads Advanced access, the uploaded public key, team_id, key_id, and the SEARCHADS client_id used to mint an ES256 client secret.
  • Treat attribution token capture, OAuth access token refresh, and conversion payload validation as separate layers so you can isolate auth failures from mapping mistakes quickly.
Context

Why ASA matters for mobile apps

Apple Search Ads sits unusually close to intent. A person searches in the App Store, taps an ad, lands on an install flow, and the campaign gets judged on whether that user becomes a subscriber, purchaser, or retained customer. That makes ASA valuable, but it also makes shallow tracking expensive. If install or post-install attribution depends only on a front-end screen event or a late analytics export, campaign automation learns from partial truth. The result is slower optimization, weaker bid signals, and a team that debates reporting instead of changing bids or creative.

TrackLayer helps because it lets the app and the backend play different roles. The app can capture AdServices attribution context and first-session identity signals, while the backend confirms the business outcome that actually matters. That split is especially useful for subscriptions, delayed purchases, call center closes, or anti-fraud review flows where the final conversion should be uploaded only after your own systems trust it. In practice, ASA matters most when you want Apple to optimize toward durable business events rather than only raw installs.

Requirements

Prerequisites

01

An Apple Search Ads Advanced account with Campaign Management API access and permission to inspect campaigns, org settings, and API clients.

02

The private key that matches the public key uploaded for your Apple Ads API client, stored server-side and never bundled into mobile or web code.

03

Your Apple Developer team_id, because Apple expects the JWT issuer to identify the developer team that owns the key material.

04

The key_id returned when you upload the public key, used as the JWT header kid for ES256 signing.

05

The SEARCHADS client_id created for the API client, which becomes the JWT subject and the OAuth client identifier in the token exchange.

Build

Setup

Step 1

Store the Apple Ads credentials as server secrets

Separate auth configuration from event routing first. Teams usually keep team_id, key_id, client_id, org_id, campaign_id, and the PEM private key in a secret manager so workers can refresh access tokens without touching app code. Keep org and campaign values next to the credentials because they are part of routing, not just reporting.

APPLE_ADS_TEAM_ID=TEAM123ABC
APPLE_ADS_KEY_ID=XYZ9876543
APPLE_ADS_CLIENT_ID=SEARCHADS.27478e71-3bb0-4588-998c-182e2b405577
APPLE_ADS_ORG_ID=org-123
APPLE_ADS_CAMPAIGN_ID=cmp-456
APPLE_ADS_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg...
-----END PRIVATE KEY-----"
Step 2

Construct the ES256 client secret JWT

Apple Ads uses OAuth 2 client credentials, but the client secret is not a random string. It is a signed JWT. The header needs alg ES256 and kid key_id. The payload should use iss for team_id, sub for client_id, aud for Apple identity, plus iat and exp. Keep the expiry short enough that key rotation is manageable, and regenerate server-side instead of checking a static token into deployment config.

import jwt from "jsonwebtoken";

const now = Math.floor(Date.now() / 1000);

const clientSecret = jwt.sign(
  {
    iss: process.env.APPLE_ADS_TEAM_ID,
    sub: process.env.APPLE_ADS_CLIENT_ID,
    aud: "https://appleid.apple.com",
    iat: now,
    exp: now + 60 * 60 * 24 * 30,
  },
  process.env.APPLE_ADS_PRIVATE_KEY!,
  {
    algorithm: "ES256",
    keyid: process.env.APPLE_ADS_KEY_ID,
  }
);
Step 3

Exchange the client secret for an access token

Once the JWT is signed, use the OAuth client_credentials flow against Apple identity. This step should live in a token service or worker utility, not inside the event handler itself. Cache the access token until shortly before expiry, and log response status separately from conversion delivery so 401 auth failures are visible immediately.

POST https://appleid.apple.com/auth/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&
client_id=SEARCHADS.27478e71-3bb0-4588-998c-182e2b405577&
client_secret=eyJhbGciOiJFUzI1NiIsImtpZCI6IlhZWjk4NzY1NDMifQ...&
scope=searchadsorg
Step 4

Normalize TrackLayer events into ASA conversion types

Keep TrackLayer canonical events destination-neutral, then map them into the Apple Search Ads conversion names your account expects. In this repo the Apple Search Ads adapter already follows that shape: purchase becomes Purchase, sign_up becomes CompleteRegistration, begin_checkout becomes InitiateCheckout, and lead becomes Lead. Version the mapping because campaign teams will ask for more revenue or lifecycle granularity later.

const appleEventMap = {
  view_item: "ViewContent",
  add_to_cart: "AddToCart",
  begin_checkout: "InitiateCheckout",
  purchase: "Purchase",
  sign_up: "CompleteRegistration",
  lead: "Lead",
  contact: "Contact",
  search: "Search",
} as const;
Step 5

Build the conversion payload with attribution and identifiers

The payload should come from backend-confirmed events, not only from app UI actions. Include the attribution token when you have AdServices data, the original conversion timestamp, value and currency when relevant, and stable user identifiers such as hashed email, hashed phone, IDFA, or IDFV if your consent model allows them. Make the payload builder deterministic so retries reproduce the exact same record.

{
  "orgId": "org-123",
  "campaignId": "cmp-456",
  "conversionEvents": [
    {
      "eventName": "Purchase",
      "attributionToken": "eyJ0eXAiOiJKV1QiLCJraWQiOiJ...",
      "conversionTime": "2026-04-24T09:42:13.000Z",
      "conversionValue": 129.99,
      "currency": "USD",
      "userIdentifiers": {
        "hashedEmail": "b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514",
        "hashedPhone": "8a59780bb8cd2ba022bfa5ba2ea3b6e07af17a7d8b30c1f9b3390e36f69019e4",
        "idfv": "AEBE52E7-03EE-455A-B3C4-E57283966239"
      }
    }
  ]
}
Step 6

POST the conversion to Apple Ads and make retries idempotent

Send the access token as Bearer authorization and keep the request body stable across retries. A common production split is: token worker refreshes OAuth, event worker builds the payload, delivery worker posts to Apple. That way rate limiting, invalid JWTs, and payload-shape errors can each be retried or quarantined independently without resending everything blindly.

POST https://api.searchads.apple.com/api/v5/conversions
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json
X-AP-Context: orgId=org-123

{
  "orgId": "org-123",
  "campaignId": "cmp-456",
  "conversionEvents": [
    {
      "eventName": "Purchase",
      "attributionToken": "attr-token-123",
      "conversionTime": "2026-04-24T09:42:13.000Z",
      "conversionValue": 129.99,
      "currency": "USD",
      "userIdentifiers": {
        "hashedEmail": "b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514"
      }
    }
  ]
}
Schema

Event mapping

Keep the left side canonical and the right side Apple-specific. That lets TrackLayer stay consistent across Meta, Google, and app analytics while still giving Apple the event names its conversion model expects. If your team changes optimization strategy, update only the mapping layer rather than rewriting the product event contract.

TrackLayer canonicalASA conversion typeWhen to use it
view_itemViewContentUse when the app or landing flow confirms a key product detail view.
add_to_cartAddToCartBest for intent signals that happen before checkout starts.
begin_checkoutInitiateCheckoutTie this to the first durable checkout state, not a button hover or sheet open.
purchasePurchaseThe core revenue event. Include value and currency whenever available.
sign_upCompleteRegistrationUseful for account creation, onboarding completion, or a registration milestone.
leadLeadWorks for qualified lead capture when purchase is too late in the funnel.
contactContactA fit for high-intent support or sales contact flows.
searchSearchOptional. Usually more valuable for product behavior than for bid optimization.
Attribution

AdServices framework attribution

On iOS 14.3 and later, Apple's AdServices framework gives app developers a privacy-preserving way to ask for Apple Search Ads attribution context from within the app. Instead of assuming open web click parameters will survive the App Store install boundary, the app requests an attribution token from the device and hands that token to the backend. That token becomes the bridge between the install-side context and the server-side conversion upload.

The important operational point is that AdServices attribution is not the same thing as your business conversion event. The app can collect the attribution token close to first open or onboarding, but the backend should decide when a real business milestone has occurred. For many app teams that means install and trial start happen in the app, while paid subscribe or purchase gets confirmed by billing, anti-fraud review, or subscription state from the server. TrackLayer sits between those layers so the attribution token and the business outcome travel together.

This also reduces confusion around privacy-era measurement. If a team relies only on device identifiers, attribution quality will drift as ATT choices, app reinstalls, or identity resets change the available signals. AdServices lets you use the Apple-provided attribution mechanism for ASA while still enriching the final event with consent-aware identifiers, canonical event names, and backend-confirmed timing. In practice, that gives cleaner upload inputs than trying to reconstruct mobile attribution after the fact from warehouse joins alone.

Validation

Testing with Campaign Management API

1

Validate auth before sending a real conversion

Use the Campaign Management API with a lightweight GET call such as campaigns or ACL to confirm the access token, org context, and account permissions are all correct before you troubleshoot conversion uploads.

2

Replay one known event end-to-end

Pick a controlled test install or purchase, capture the attribution token, build one payload, and verify that the exact same event data appears in your delivery logs, queue logs, and Apple API response.

3

Check timing and mapping together

When acceptance looks inconsistent, compare the eventName, campaignId, conversionTime, and attribution token from the outbound payload with the original app event. ASA setup problems often hide in drift between those fields, not in transport alone.

Diagnostics

Troubleshooting

401 invalid JWT

Confirm the JWT uses ES256, the kid matches the uploaded key, sub matches the SEARCHADS client_id, iss matches the developer team_id, and the server clock is not skewed.

403 insufficient scope

The OAuth client or user access is valid but not authorized for the target org or API action. Recheck Apple Ads API permissions, org access, and any scope restrictions attached to the client.

400 payload shape

The JSON body is malformed or a required field is missing. Compare the outbound body against the expected orgId, campaignId, conversionEvents array, eventName, conversionTime, and identifier field names exactly.

429 rate limit

Back off by destination and org, queue retries with jitter, and avoid concurrent token refresh storms. Do not turn one failing conversion into many slightly different retries.

502 conversion time drift

This usually shows up when workers upload delayed events with queue time instead of true event time, or when clock drift between mobile, backend, and delivery workers pushes timestamps outside what Apple expects.

FAQ

Common questions

Do I need Apple Search Ads Basic or Advanced?

Advanced. The Campaign Management API and production credential workflow belong to Apple Search Ads Advanced, not the simplified Basic product.

Should TrackLayer upload only purchases?

Usually start with one revenue event and one upper-funnel event such as CompleteRegistration or Lead. Too many early mappings create noise before you know which conversion Apple should optimize toward.

Where should the attribution token be collected?

Inside the app or trusted mobile backend flow that receives AdServices attribution data. Treat it as event context that gets attached to a canonical TrackLayer event before routing to Apple.

Can I rely on IDFA alone?

No. Modern iOS attribution should assume limited device identifiers. AdServices attribution, consent-aware first-party identifiers, and stable backend timestamps are safer foundations.

Why separate token refresh from conversion upload?

Because auth failures and event-shape failures are different incidents. Splitting them lets you rotate keys, refresh access tokens, and retry deliveries without rewriting event logic.

Next reads

Related implementation guides

We use essential cookies to keep the site secure and functional. Analytics and third-party tags run only with your consent. See our Cookie Policy.

We use essential cookies to keep the site secure and functional. Analytics and third-party tags run only with your consent. See our Cookie Policy.