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.
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.
Prerequisites
An Apple Search Ads Advanced account with Campaign Management API access and permission to inspect campaigns, org settings, and API clients.
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.
Your Apple Developer team_id, because Apple expects the JWT issuer to identify the developer team that owns the key material.
The key_id returned when you upload the public key, used as the JWT header kid for ES256 signing.
The SEARCHADS client_id created for the API client, which becomes the JWT subject and the OAuth client identifier in the token exchange.
Setup
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-----"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,
}
);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=searchadsorgNormalize 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;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"
}
}
]
}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"
}
}
]
}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 canonical | ASA conversion type | When to use it |
|---|---|---|
| view_item | ViewContent | Use when the app or landing flow confirms a key product detail view. |
| add_to_cart | AddToCart | Best for intent signals that happen before checkout starts. |
| begin_checkout | InitiateCheckout | Tie this to the first durable checkout state, not a button hover or sheet open. |
| purchase | Purchase | The core revenue event. Include value and currency whenever available. |
| sign_up | CompleteRegistration | Useful for account creation, onboarding completion, or a registration milestone. |
| lead | Lead | Works for qualified lead capture when purchase is too late in the funnel. |
| contact | Contact | A fit for high-intent support or sales contact flows. |
| search | Search | Optional. Usually more valuable for product behavior than for bid optimization. |
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.
Testing with Campaign Management API
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.
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.
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.
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.
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.
Related implementation guides
Mobile measurement partners
How TrackLayer fits next to MMP workflows, post-install events, and paid media attribution for app teams.
Read guide →SKAdNetwork for mobile teams
A practical complement to ASA when privacy-preserving install attribution and postbacks shape your reporting stack.
Read guide →Identity resolution
Design the first-party IDs, hashing rules, and event contracts that make mobile conversion uploads less fragile.
Read guide →