Turns "how do we measure our app marketing" into a proposed measurement map: one governing report per decision, the platform paths that feed each report, and the SDK and event practices that keep those reports honest. It explains and specifies; it does not change SDK code, ad accounts or MMP settings.
When to use
Use this skill when:
- A team asks how app attribution works now, or what to measure before an app launch or a first paid campaign.
- Install or revenue numbers disagree between an ad network, the MMP, App Store Connect or Play Console, and the backend.
- An app is adding Firebase, an MMP SDK or a new paid network and needs the start order, consent, ATT and identity rules.
- Someone asks whether they need an MMP yet.
- iOS reporting changed after an OS release, for example Apple Ads postbacks moving to AdAttributionKit on iOS 26.2.
Use something else when:
- Conversion value schema design: skan-conversion-value-design if installed, else the minimum rules in ios-attribution.md.
- MMP vendor comparison and pricing: mmp-selection if installed, else the shortlist in mmp-decision.md.
- Pre-launch QA of one MMP integration: mmp-implementation-qa if installed, else the acceptance paths in sdk-and-event-practices.md.
- The full app event plan: mobile-app-tracking-plan if installed, else tracking-plan-builder.
- Subscription revenue modelling: mobile-subscription-revenue if installed, else revenue-and-store-fees.md.
- Channel mix or whether paid media works at all: incrementality testing or MMM. Attribution cannot answer it.
Inputs
Read these first if they exist, before asking anything:
martech-context/stack.md: apps per platform, SDKs and versions, MMP, ad networks, CMP, subscription platform, owners.martech-context/metrics.md: install, activation, payer, CAC and payback definitions.martech-context/decisions.md: ATT prompt placement, consent model, identity model, any earlier MMP decision.martech-context/tracking-plan/: app event and revenue contracts, and any existing app measurement map.
Then gather what is missing:
- The recurring decisions, who makes them and how often: daily bids, weekly network budgets, monthly channel mix.
- OS mix: share of iOS users on 17.4, 18, 18.4, 26.2 and 27.2 or later, from App Store Connect or the analytics SDK.
- Paid networks per platform, Apple Ads use, web-to-app flows, and links in email, QR codes and referrals.
- Where purchases are verified: in the app, on the backend from store server notifications, or in a subscription platform.
- Consent regions, the CMP and whether it writes TCF strings; where the ATT prompt sits in onboarding.
- When an account ID first exists, and whether anonymous use is allowed.
- Release cadence and the minimum supported app version.
Never ask for SDK dev keys, API tokens, S2S credentials or user-level records. Work from test devices and redacted samples.
Procedure
0. Route the question
- "Do we need an MMP?": steps 2 and 9. Minimum output: the outcome, the rule step that decided it, reopen triggers, a proposed
decisions.mdentry. - "Why do the numbers differ?": step 10 with step 1's date basis. Minimum output: the reconciliation write-up.
- SDK or app setup: steps 2 and 4 to 8. Minimum output: start sequence, identity, revenue and release rules, with acceptance paths.
- Full measurement map: every step, ending at step 11.
1. Write the decision map first
For each recurring decision, name one governing report and keep every other view as a diagnostic. Start from this default and adapt it:
- Apple Ads bids: Apple Ads reporting plus AdServices attribution, which covers Apple Ads only.
- iOS budget per network: choose one view per network in advance. Either the MMP's aggregated SKAdNetwork and AdAttributionKit report (comparable across networks, delayed, coarse) or the network's own modelled report (faster, self-graded). Never add the two.
- iOS creative and ad set decisions: the network's own reporting. Apple's source identifier carries 2 to 4 digits, and only 2 at lower privacy tiers.
- Android budget per network: MMP attribution built on Install Referrer and network claims, or the network's report when there is no MMP.
- Channel mix and "is paid working": incrementality tests or MMM. Agreement between attribution views does not measure lift.
- Onboarding and activation: product analytics by acquisition cohort, where consent allows, once the source reaches the analytics profile (step 5).
- Revenue, payback and LTV: the backend ledger fed by store server notifications, net of store fees and refunds. MMP revenue is attributed, not collected.
Put the denominator in every metric name: cost per attributed install, per first open and per verified payer are different metrics.
Postbacks carry no install or tap timestamp. For each aggregated iOS report, record whether it dates installs by postback-received date or by an MMP-modelled install date (with the MMP's offset), align spend to the same basis, and apply maturity rules only to modelled install-date cohorts. Set each reconciliation tolerance from at least 8 weeks of observed gaps (discrepancy-diagnosis.md). Template: measurement-map-template.md.
2. Check platform status live
Before stating any OS boundary, deadline, fee or Android status, fetch the live page and record the URL and date. As checked on 2026-10-17:
- AdAttributionKit: installs from iOS 17.4, re-engagement from 18, conversion tags from 18.4. Vendors put configurable windows, cooldowns and country code at 18.4 too.
- Apple Ads on iOS 26.2 and later, MMP-sourced (Apple's Apple Ads page is silent): AppsFlyer says postbacks are signed and sent with AdAttributionKit instead of SKAdNetwork; Branch says attribution will be interrupted without AdAttributionKit in
Info.plist. Action: configure both frameworks and both copy keys, then show a received Apple Ads AdAttributionKit copy from a released build on 26.2 or later. - ATT in the EU from iOS 27.2, per Apple's privacy page: a full-page alternative prompt (the only option in France, Germany, Italy, Poland and Romania), a Markdown usage key, an Additional Information button and a re-prompt a year after the last answer.
- Privacy Sandbox on Android: deprecated, retirement announced on 17 October 2025, phase-out under way; check status live. It is not an upcoming migration. The advertising ID remains, with the
AD_IDpermission for apps targeting Android 13 or later. - Google Play's fees changed in 2026; both stores' rates differ by region, programme and product type.
If a newer announcement exists, follow it and tell the user what changed. Details: ios-attribution.md, android-attribution.md.
3. Map the measurement paths per platform
iOS:
- ATT governs tracking and the IDFA. Without authorization the IDFA is zeros. A hashed email does not replace ATT permission, and fingerprinting is banned, including inside SDKs.
- The ATT prompt is one-shot on every OS and in every region, except the EU re-prompt a year after the last answer from iOS 27.2.
- SKAdNetwork 4 and AdAttributionKit return signed, aggregated postbacks with random delays and privacy tiers. One impression wins per conversion across both, so their totals never add.
- Read SKAdNetwork postbacks by version:
did-winexists from version 3,postback-sequence-indexfrom 4,redownloadfrom 2, and versions 1 to 3 carrycampaign-id. Requiring the newer fields drops every Apple Ads SKAdNetwork install. - Apple Ads also has AdServices: a device token, exchanged server-side within 24 hours.
- Web-to-app: SKAdNetwork for Web Ads (Safari only), App Store campaign links, or a link vendor.
Android:
- Install Referrer returns the Play link's referrer string and click and install timestamps for 90 days. Read it once on first run, persist it, and store failure codes; failures are not organic.
- No Android counterpart to SKAdNetwork remains after the Privacy Sandbox deprecation. Verify live before saying so.
- The advertising ID returns zeros after the user deletes it; store its status on the install record.
4. Set the SDK start order around consent and ATT
- Load stored consent and ATT status before any measurement SDK sends. In consent regions, start with denied defaults and pass consent to each SDK before it starts.
- If the app will show the ATT prompt, configure the MMP's ATT wait before starting it: at least the 90th percentile of time from first launch to the first ATT answer, within the vendor maximum or, where none is documented, the team's own ceiling. If that percentile exceeds it, move the prompt earlier.
- Show the prompt only when the app is active, after a value moment. Never gate features on it or reward acceptance.
- Check every SDK's privacy manifest: requests to declared tracking domains fail without tracking permission.
- Sequence, vendor APIs, consent keys and the opt-in metric: sdk-and-event-practices.md.
5. Set identity at login
- Use an opaque internal user ID as the analytics user ID and the MMP customer user ID. Never an email or phone number.
- IDs are not retroactive. Set the ID before the SDK sends where the vendor allows it.
- On logout, clear the user ID in every SDK, including messaging SDKs.
- Get the acquisition source onto the analytics profile: pass the analytics ID to the MMP's partner integration, or join MMP raw data in the warehouse. Prove it with one attributed test install on the same profile as its later events.
- For S2S events, store the install-to-user mapping, hold events sent right after install until the MMP has processed it, and alert before the MMP's late-event timestamp cut-off.
- Re-check identity APIs on every SDK major upgrade. AppsFlyer's Android SDK V7, for example, removed its wait-for-ID mode.
6. Make revenue events match money
- One sender per verified occurrence. The app reports intent; the backend or subscription platform reports the verified purchase, renewal and refund.
- iOS conversion values are set on the device. If purchases are verified on the backend, the app learns the result while the window is open, then calls the update or logs a revenue-free SDK event the MMP schema maps (for example
trial_verified). Ask the MMP whether S2S events can drive conversion values and record the answer. - Send gross price and ISO 4217 currency. Derive proceeds from store financial reports; never hardcode 15% or 30%.
- Firebase logs StoreKit 1 purchases automatically, needs a manual call for StoreKit 2, and does not deduplicate the two. GA4 deduplicates
transaction_idon web streams only. - Exclude test purchases on the stores' own markers and link refunds to the original transaction: revenue-and-store-fees.md.
7. Version releases and plan for offline
- Keep a release record: app version, build, SDK versions, conversion value schema version, consent and ATT flow version, MMP configuration changes.
- Postbacks do not carry your schema version. Cut schema changes over at a release and a date.
- SDK offline caches are finite (AppsFlyer Android: 40 events). Money comes from the backend, never a device queue.
8. Specify deep links, deferred deep links and web-to-app
- Verify Universal Links and App Links on real devices. Firebase Dynamic Links shut down on 25 August 2025; inventory old links.
- Route deferred deep links on routing values only. For new users the MMP may return route parameters with null attribution fields.
- Write a route contract for installed, signed out, new install, expired content, someone else's private item and desktop, tested from every entry context the links use.
- Record the method that attributed each install (
adservices,skan,aak,install_referrer,mmp_click,link_open,unknown). Details: deep-links-and-web-to-app.md.
9. Decide whether to buy an MMP
Count each platform separately. The first step that matches decides:
- A trigger fires: buy, unless the in-house build, costed with the reference's inventory, is cheaper than the MMP's first full year. Triggers:
- Two or more paid networks on one platform need install and in-app postbacks, or one deduplicated view.
- A network the team relies on does not report its own installs and needs an attribution partner.
- iOS paid acquisition runs beyond Apple Ads and nobody can own conversion values, postback copies and verification.
- Deferred deep links or web-to-app routing carry acquisition and no linking vendor is in place.
- The warehouse needs install-level attribution joined to revenue.
- No trigger, one expected within 6 months, and the MMP's first full year after free offers, plus internal hours, costs under 10% of annual paid spend: buy now, so history exists before the change.
- Otherwise wait, and record the reopen triggers.
Propose the decisions.md entry with the deciding step. Inventory, cost test and no-MMP setups: mmp-decision.md.
10. Explain a discrepancy
Check in order and stop at the first difference that explains the gap: date basis and time zone, attribution windows, overlapping self-reported claims, postback delay and maturity, unknown values, reinstall and re-attribution rules, consent coverage, revenue basis. Report a reconciliation with each side's definition, not "the right number". Checklist, version-aware counting rule, fixture and maturity days: discrepancy-diagnosis.md.
11. Assemble and propose
Fill the template to the depth step 0 set. Show the proposed martech-context/ changes as a diff: the map in tracking-plan/app-measurement-map.md, decisions in decisions.md, metric definitions in metrics.md. Write only after the user explicitly approves.
Common failure modes
- ATT wait shorter than the prompt path, or longer than users stay. Detect: installs fall against
first_openafter an onboarding change. - SDK starts before consent loads. Detect: the first request in debug logs carries no consent data.
- SKAdNetwork and AdAttributionKit totals, or MMP and network totals, added. Detect: attributed installs exceed store first-time downloads.
- Pre-version-4 SKAdNetwork postbacks dropped. Detect: Apple Ads near zero in direct postback counts while the MMP shows installs.
- Unknown conversion values counted as zero. Detect: conversion rate falls while the no-value share rises.
- Conversion value signal lost after purchases move to S2S. Detect: the share of first postbacks at or above the purchase value drops to near zero after the release.
- CPI moves against a spend change for a few days. Detect: compare CPI on spend date with received and modelled install dates for the same week.
- S2S events recorded as organic. Detect: organic events spike for events sent within a minute of install.
- Install Referrer failures reported as organic. Detect: organic share jumps for one device or Play Store version.
- Two revenue senders. Detect: attributed revenue near twice the ledger for a cohort.
- Store fee hardcoded. Detect: net-to-gross ratio constant across regions and across a fee change.
Worked example
Acme Fit, a fictional subscription app (70% iOS, 30% Android), runs Apple Ads, one social network and Google App campaigns, and sends web visitors from example.com to both stores. Full map: example-acme-measurement-map.md. Points worth copying:
- Apple Ads bids run on AdServices. The social network's budget runs on the MMP's aggregated iOS report on modelled install dates, with spend on the same date.
- The ATT prompt moved from after the first workout to the end of goal-setting when the p90 time to answer (410 seconds) exceeded the team's own 300-second ceiling; the wait is now 120 seconds.
- Purchases reach the MMP only from the backend. The app sends intent events and a revenue-free
trial_verifiedevent that sets the conversion value. - First-postback budget decisions wait 5 days: about 4 days for the latest first postback plus a named 1-day team buffer. Third-window reads wait about 41 days plus MMP lag.
Acceptance checks
- Decision map: each decision has one governing report, a named denominator, a date basis and a maturity rule. Pass: no decision lists two governing reports.
- Platform status: every OS boundary, deadline, fee and Android status has a URL and a check date from this run. Pass: none without.
- iOS counting: installs follow the version-aware rule, with redownloads and re-engagements on their own lines. Pass: the eleven-delivery fixture gives installs 4, redownloads 1, re-engagements 1.
- Postback copies: each key has one owner, and a received SKAdNetwork copy and AdAttributionKit copy from a released version (not TestFlight) are recorded with version and date. Pass: both dated copies exist.
- Start sequence: first launch is written for ATT authorized, denied, not determined and early exit, plus consent denied in a consent region. Pass: each path says what each SDK sends.
- Identity: user ID source, timing, logout and install mapping are written, and one attributed test install sits on the same analytics profile as its later events. Pass: no email or phone number as an ID.
- Revenue: one sender per occurrence, proceeds from store reports, refunds linked, test purchases excluded, a named device signal for verified purchases. Pass: the revenue fixture reconciles.
- Install record: stores
attribution_method,install_referrer_statusandad_id_status. Pass: all three populated on a test install. - Release record: includes SDK versions and schema version. Pass: the last release is filled in.
- Deep links: the route contract covers all six app states from each entry context in use, and no Firebase Dynamic Links remain. Pass: link inventory shows zero.
- Reconciliation: each write-up lists both definitions, the explaining check with evidence, and the residual gap against the agreed tolerance. Pass: every disputed number has one.
- MMP decision: the proposed entry names the deciding rule step and the reopen triggers. Pass: both stated.
- Approval: proposed
martech-context/changes were shown and approved before writing. Pass: nothing changed without a recorded approval.
Boundaries
- This skill explains and specifies. Changes to SDK code,
Info.plistor manifest entries, ad network settings, MMP configuration or consent banners are drafts that stop for human approval. - ATT prompt copy, consent legal basis and whether a vendor's matching method is allowed belong to the privacy owner.
- Never reverse-engineer a person or device from aggregated postbacks, and never handle user-level exports outside the user's own access rules.
- Hand-offs:
- AppsFlyer: the AppsFlyer MCP server (beta) reads data and settings; its one write,
create_onelink_link, creates a link after confirmation (10 per account per day). Stay read-only while planning; before any link creation, show the exact template, parameters and link and get explicit approval. Scope and auth: mmp-decision.md. - Adjust: the Adjust MCP server (Early Access, enabled through Adjust, OAuth) exposes aggregated data only.
- Firebase and GA4: Google's GA4 MCP server is read-only and useful for checking app stream events.
- Other MMPs: check whether the vendor ships an MCP server and its permissions before connecting it.
- AppsFlyer: the AppsFlyer MCP server (beta) reads data and settings; its one write,
Live docs
Platform settings, limits and menu paths change. Before stating any parameter name, limit, quota or UI path:
- Fetch the current page from the vendor docs MCP server or llms.txt listed in
sources.yaml, or open the canonical URL. - If the live page disagrees with this skill, follow the live page and tell the user what changed.
- Cite the URL you used.
Entries that matter most: Apple's postback parameter, AdAttributionKit and user privacy pages, the Apple Ads attribution overview, Google's Privacy Sandbox and Install Referrer pages, both stores' fee pages, and AppsFlyer's Apple Ads bulletin, S2S and MCP articles. Apple's pages render with JavaScript; fetch the /tutorials/data/documentation/....json form when a plain fetch returns only a title. Read AppsFlyer support articles through https://support.appsflyer.com/api/v2/help_center/en-us/articles/{id}.json, with the number from the article URL. Facts checked on 2026-10-17.