Ad attribution in your app
What the SDKs record about where people came from, what Voidhash sends to Meta, and how to automate ads.
Voidhash credits each signup, and everything the person pays later, to the ad or campaign that brought them, from data your SDKs record. This page covers the code; Ad measurement in the Help center covers the reports in Studio.
What your app needs
- The web SDK on your landing pages and web app, and a mobile SDK in your apps.
identifywith the same user ID wherever people sign up or sign in. That ID links a web visit, an app install and purchases on any store. See People and identity.- Campaign parameters on your ad links, such as the Meta URL parameters.
Where touches come from
A touch records where a person came from, with an ad network when it came from an ad. Each SDK
writes touches to the person as $initial_… and $latest_… traits.
| Source | What the SDK reads | Details |
|---|---|---|
| Web pages | utm_* parameters, campaign_id, adset_id, ad_id, click IDs such as fbclid, gclid and ttclid, and Meta's _fbc and _fbp cookies. | Campaign attribution |
| iOS | The Apple Ads attribution token, once per install. | Credit installs to Apple Ads |
| Android | The Google Play install referrer, and with metaAppId, Meta's install referrer for installs from Meta ads. | Credit installs to ad campaigns |
| Web to app | A website visit followed by a sign-in in your app with the same user ID. | Web to app |
React Native runs the iOS and Android behavior; see Install attribution. Touches need analytics consent. Only ad spend needs a connected ad account; attribution works for every network.
Share purchases with Meta
Voidhash can send production purchases to Meta's Conversions API, so Meta counts renewals and
purchases its pixel and SDK miss. Sending a person's purchases needs their agreement, which is
separate from analytics consent. Record it with setAdDataSharing:
try await voidhash.setAdDataSharing(true)voidhash.setAdDataSharing(true)await voidhash.client.setAdDataSharing(true);await voidhash.setAdDataSharing(true);The call sets the $ad_data_sharing person attribute:
- Only the Boolean
truecounts. The text"true", set throughsetPersonAttributes, doesn't. - Agreeing needs analytics consent.
setAdDataSharing(false)always goes through, and the SDK retries it for up to 48 hours. Declining analytics doesn't revoke the agreement, so when someone rejects everything, call both. - When two profiles of a person merge, a
falseon either one wins. - A
trueset within 5 minutes after afalseis ignored. Ask again later. falsestops sending from then on. What Meta already received stays with Meta, including after you delete the person's data.
On iOS, sending someone's purchases to Meta can count as tracking under Apple's rules. Check
whether you need App Tracking Transparency permission before you call setAdDataSharing(true).
What Meta receives
Each purchase is a Purchase event with its time, amount and currency, and:
| Field | Value |
|---|---|
event_id | transaction:<id>, so Meta counts a purchase once even if it arrives twice. |
action_source | system_generated for renewals, other otherwise. |
em | A SHA-256 hash of the person's email, when known. |
external_id | A SHA-256 hash of the user ID you identified them with. Anonymous IDs aren't sent. |
fbc, fbp | The person's $fbc and $fbp, or an fbc built from the fbclid of the credited Meta ad click. |
Voidhash doesn't send purchases without an email, fbc or fbp; free trials; refunded or revoked
purchases; purchases of erased people; or purchases older than 7 days. If you also send purchases to
Meta from your own code, they have other event IDs and count twice, so send them one way.
Delivery runs every 15 minutes and leaves each purchase 5 minutes to settle, so a purchase usually reaches Meta within 20 minutes. Each run sends up to 5,000 purchases in up to 64 requests. A purchase reported before its price is known waits up to 1 hour for the store's server notification, then is skipped. Set up the dataset and token in Studio; see Send purchases to Meta.
Automate from the CLI and API
| Task | CLI | API or MCP |
|---|---|---|
| Connect a Meta ad account | voidhash ads connect act_1234567890 --token-stdin --backfill-days 365 | ad_accounts.create with platform, externalId, accessToken and backfillDays |
| Set up the Conversions API | voidhash ads measurement edit --dataset 1234567890 --token --enable | meta_measurement.updateSettings, set_meta_conversions |
| Store the install referrer decryption key | voidhash ads measurement edit --install-referrer-key | meta_measurement.updateSettings with installReferrerKey |
| Set the attribution window (1–90 days) | voidhash settings set ads-attribution-window-days 14 | projects.updateSettings with ads.attributionWindowDays |
| Set the key conversion | voidhash settings set ads-key-conversion trial | projects.updateSettings with ads.keyConversion: signup, trial or payer |
| Mark landing pages as web-to-app | voidhash ads settings web-to-app example.app/start | ads.updateSettings with webToAppLandingPrefixes, set_web_to_app_prefixes |
Tokens and keys are read from a hidden prompt or standard input, never from a flag's value.
Connecting an account that is already connected replaces its token; --backfill-days imports up to
1,095 days. Ad accounts, measurement and web-to-app prefixes need project:ads:manage; the
attribution window and key conversion need permission to update the project. See
CLI: ads.
Reports
The performance, creatives and cohorts reports and ads issues are in the
API, the CLI and the
MCP tools. They take currency, timezone and environment, and need
project:ads:read; measured people and revenue also need project:analytics:read, or come back
empty.
The changes report and the ads_changes_report MCP tool
show what changed between hourly syncs over up to 7 days (the MCP tool defaults to the last 24
hours):
- Decisions: campaigns, ad sets and ads created, turned on, paused or removed, and budget and bid strategy changes, with values before and after. Name, targeting and creative edits aren't recorded.
- Delivery: what each campaign, ad set or ad spent, showed, got clicked and converted between syncs.
- Revisions: Meta's corrections to days that had already ended, mostly late conversions.
Amounts are Meta's, in minor units of each ad account's currency and time zone. historySince
says when history starts for each account.
Next steps
- Page views and attribution: the web SDK's campaign rules.
- Web to app: carry a website visit into your app.
- Ad measurement: how Studio credits signups and revenue.
- Connect Meta Ads: connect an ad account in Studio.
Something wrong or missing on this page? Tell us by email or on Discord.