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.

RouteWhere it runsWhat you get
setAttribution (client)Your app, in the MMP SDK's attribution callbackSimplest 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 idThe 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; setLandingParams is 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:

  1. 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>
  1. In your app, tell Singular who the user is: SetCustomUserId(...) with either your own user_id (as long as you also pass it to AgentHog via identify(email, { user_id })) or the AgentHog anon id (AgentHog.anonId after init). Not the Singular Device ID or a raw device id: AgentHog has no mapping for those.
  2. In Singular (Attribution → Partner Configuration → Internal BI), paste that URL as the postback URL. Singular sends its standard JSON payload in full — user_id is included automatically once your app calls SetCustomUserId. 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.