Branch came to attribution from deep linking, and it shows in the integration surface: attribution context rides on link analytics tags rather than free-form query macros, and conversions arrive as JSON POST webhooks with a nested payload instead of the flat GET postbacks AppsFlyer and Adjust send. Neither difference is hard to handle — but both break integrations that assume every MMP looks the same. This guide covers the Branch-specific work. For the tracker–MMP architecture generally, start with the mobile attribution guide.
How Branch Differs from Other MMPs
Three things set Branch apart for a network integration. First, the link itself is the product: a single Branch link deep-links into an installed app, routes to the correct app store otherwise, and falls back to web — which makes it unusually good for affiliate traffic that starts on web content. Second, attribution context travels in Branch's tilde-prefixed analytics tags (~campaign, ~channel, ~feature) plus Branch's own click ID, ~id, generated per click. Third, reporting is webhook-based: JSON POST requests with user, touch, and event data in separate nested objects.
Step 1 — Map the Link Analytics Tags
As with every MMP, the mapping between platform naming and your affiliate hierarchy is the core decision. The canonical Branch mapping:
| Branch tag | Tracker macro | Meaning |
|---|---|---|
~id | {click_id} | Branch click ID — the round-trip key |
~campaign | {campaign_name} | Campaign identity |
~channel | {affiliate_id} | Publisher / affiliate ID |
~feature | {source} | Traffic source |
sub_aff_id (link data) | {sub_aff1} | Sub-affiliate drill-down |
Note the inversion relative to other MMPs: Branch's ~id is generated by Branch, not by you. Your tracker does not push a click ID into the link — it receives Branch's click ID in the webhook and joins on the campaign and channel context you set on the link. Populate ~channel with the publisher ID on every link so both systems can report source-level quality; attribution context on the deep-link parameters is required, not decorative.
Step 2 — Build the Branch Link
Branch links are minted via the dashboard, the link API, or per-click by your tracker. A production-shaped example of the resulting structure:
https://yourapp.app.link/abcDEF
?~campaign={campaign_name}
&~channel={affiliate_id}
&~feature=affiliate
&sub_aff_id={sub_aff1}
// ~id is generated by Branch at click time Because the same link serves install, open, and web-fallback journeys, one affiliate link covers the full web-to-app funnel — the scenario where conventional app tracking links quietly lose attribution at the store boundary.
Step 3 — Configure the Webhooks
In the Branch dashboard, register your tracker's endpoint for the events you pay on: INSTALL at minimum, plus the commerce and custom events behind your CPA rules. Confirm the webhook template includes ~id — it is how each event reconnects to the click — and your identifiers from the link data. Unlike AppsFlyer and Adjust, where the URL template carries the macros, Branch's webhook body carries everything; the endpoint registration is just a destination plus event filters.
Step 4 — Parse the Nested Payload
A Branch webhook is a JSON document with three objects your pipeline reads from:
{
"name": "INSTALL", // or PURCHASE, custom events
"timestamp": 1752742800000, // epoch milliseconds
"user_data": { "os": "ANDROID", "aaid": "...", "idfa": null,
"ip": "...", "model": "...", "country": "US" },
"last_attributed_touch_data":
{ "~id": "1234...", "~campaign": "...",
"~channel": "aff_1042", "sub_aff_id": "..." },
"event_data": { "revenue": 24.99, "currency": "USD" }
} The joins that matter: click identity from last_attributed_touch_data.~id, publisher context from ~channel and your link data, device identity from user_data (note Android's advertising ID arrives as aaid, not gaid), and revenue from event_data for RevShare and CPA payouts. Treat INSTALL and FIRST_OPEN as the install class; everything else maps through your event rules.
Step 5 — Secure, Filter, Deduplicate
Branch signs webhooks with a shared secret as an HMAC-SHA256 over the raw body — verify with a constant-time comparison and reject mismatches before any parsing. Affclix does this automatically when the Branch Secret is configured on the integration, and flags unsigned integrations in logs. Then apply the standard payable-event filters: organic detection on Branch payloads means checking the attributed touch data — a ~channel of organic or an empty ~campaign is unattributed traffic and must not convert. Deduplicate on ~id + event name, since undelivered webhooks are retried, and run survivors through your normal fraud screening — Branch webhooks carry no fraud verdict field, so the tracker-side layers do all the work here.
AppsFlyer postbacks carry a fraud_reason field; Branch webhooks do not. Your tracker's own timing, device, and concentration analysis is the only fraud gate on Branch traffic — make sure it is on.
Reconciliation and Gotchas
- Webhooks arrive without
~id— the webhook template omits it. Re-add the field in the dashboard template; events without it cannot be joined to clicks. - Timestamps look wrong by a factor of 1,000 — Branch sends epoch milliseconds where most MMPs send seconds. Parse accordingly or every event lands in 1970 or 57,000 AD.
- Android device IDs missing — you are reading
gaid; Branch's field isaaidinsideuser_data. - Installs counted twice — both INSTALL and FIRST_OPEN webhooks enabled and treated as separate conversions. Collapse them into one install class.
- Numbers drift vs the Branch dashboard — reconcile against the daily export API rather than dashboard aggregates, and verify organic filtering matches on both sides.
Affclix's Branch integration handles all of the above out of the box — nested payload parsing, ~id mapping, millisecond timestamps, aaid normalisation, HMAC verification, organic and duplicate filtering, and daily export reconciliation. Alongside the AppsFlyer and Adjust guides, this covers the three MMPs you will meet most often — the series continues with Kochava, Singular, and Apptrove.