Mobile attribution
A web session carries its campaign in the landing URL; a mobile install doesn't. If you run an MMP (Singular), it holds the verdict of which network and campaign drove the install, and it delivers that verdict asynchronously: seconds after launch, or server-side later. This page covers the two ways to hand that verdict to AgentHog so it lands on the session's utm_* columns, which is what campaign reports, Top sources, and ah campaigns actually read. identify() traits do not feed those reports; these routes do.
Pick a route
Implement one; you don't need both. If you do run both, they converge: the server applies whichever arrives first, and repeats are ignored per session.
| Route | Where it runs | What you get |
|---|---|---|
| setAttribution (client) | Your app, in the MMP SDK's attribution callback | Simplest wiring, lowest latency. Singular's device callback carries network / campaign name / campaign id, but no adset or creative (Singular doesn't expose them client-side) |
| Singular postback (server) | Singular's servers → AgentHog, no app code beyond one id | The full field set (adset, creative, re-engagement and view-through flags), and it works retroactively for users whose app version predates your SDK wiring |
Client route: setAttribution
Every SDK ships the same call: pass your MMP's verdict whenever its callback fires, and it rides the next batch on the normal cadence. Only provider is required; utm_* map to the same-named session columns, and params holds provider extras verbatim.
// wherever Singular's attribution callback hands you data
const ah = useAgentHog()
ah.setAttribution({
provider: 'singular',
utm_source: network, // "organic" is safe to pass; it never erases anything
utm_campaign: campaignName,
params: { campaign_id: campaignId, click_timestamp: clickTs },
})Singular's device attribution callback is a BETA feature of their SDK; enable it at Singular init. There is no web tab here on purpose: the web tracker has no setAttribution, because a web session's attribution comes from the URL it actually landed on. The call is safe to make exactly as the callback hands you data:
- Call it any time. Before the session's first flush it rides the first batch; after, it rides the next one. No forced network request.
- Delivered once per distinct payload. MMP callbacks re-fire their cached verdict on every launch; repeats are no-ops. A different payload (a re-engagement verdict) is a new delivery and stamps whichever session is live then.
- Crash and offline safe. An undelivered payload persists and rides the next launch's first flush.
reset()clears it with the rest of the identity;setLandingParamsis unchanged and stays for deep-link params.
Server route: the Singular postback
Singular's Internal-BI postback POSTs the full attribution record to a URL you configure. Three steps:
- On your project's Settings page, generate a postback secret. The page composes the exact URL to paste into Singular:
POST http://agenthog.io/postbacks/singular?project=<project key>&ah_secret=<postback secret>
- In your app, tell Singular who the user is:
SetCustomUserId(...)with either your ownuser_id(as long as you also pass it to AgentHog viaidentify(email, { user_id })) or the AgentHog anon id (AgentHog.anonIdafter init). Not the Singular Device ID or a raw device id: AgentHog has no mapping for those. - In Singular (Attribution → Partner Configuration → Internal BI), paste that URL as the postback URL. Singular sends its standard JSON payload in full —
user_idis included automatically once your app callsSetCustomUserId. Make sure to enable the install postback (and re-engagement, if you run re-engagement campaigns).
AgentHog maps the payload onto the session's utm_* columns and keeps the rest on its attribution record — the full field mapping is on the server-to-server page. Install postbacks stamp the person's earliest session; re-engagement postbacks stamp the session covering the event timestamp. A postback arriving before any session exists is parked and retried for 7 days, so ordering races resolve themselves. Responses: 401 means a bad or missing secret (the only auth failure); every accepted outcome is a 200 with { "status": "applied" | "parked" | "dropped" } for debugging. Do not put your write token in the URL; the postback secret is the whole auth.
Singular's UI takes a single Internal-BI postback URL per app. Pointing it straight at AgentHog is the standard hookup; if yours already feeds your own backend — or you prefer your server in the path (the conservative choice when Meta AMM user-level data is in play) — point Singular at your server and relay a copy to the URL above. Auth is the secret alone, so a relay works without any signature or IP-allowlist gymnastics. The direct-vs-relay trade-offs are on the server-to-server page.
How the data lands
Per column, the strongest source wins: real deep-link param > MMP verdict > install-referrer stamp. A URL your app was actually opened with is never overwritten. The install-referrer stamp is the attribution AgentHog derives on its own on Android, from the Play Store's install referrer on first launch; the MMP verdict overwrites it, because Singular's cross-network arbitration sees clicks the referrer can't. An organic answer (including Singular masking a Meta install as organic) writes nothing, so it never erases a real verdict. One attach applies per session; the raw payload is always kept on the session even when precedence leaves the columns alone.
From there it's ordinary campaign data: Top sources and ah campaigns, ah sessions list --utm-source X, and person-level first/latest touch in ah user and ah users --source. There is no separate “attribution report” to learn.