Deep Linking
How mobile deep linking works π
There are two distinct scenarios. Getting both right is what βdeep linkingβ actually means in practice.
Direct deep link (app already installed) π
The user taps the ad, the app is already on the device, and it opens directly on a specific screen β for example a product page, a promo, or a saved cart β instead of the generic home screen. This is driven by a URI the OS knows how to route to the app, e.g. a custom scheme or a Universal/App Link.
myapp://product/12345
https://www.brand.com/product/12345 (Universal Link / App Link)
Deferred deep link (app NOT installed) π
The user taps the ad, does not have the app, is sent to the App Store / Google Play to install, and on first open is routed to the same intended screen. The intended destination has to βsurviveβ the install β the app store strips the original link, so something has to remember where the user was supposed to go. That βsomethingβ is the MMP.
Where the MMP (AppsFlyer) fits in π
An MMP like AppsFlyer sits in the middle of the click. Instead of pointing your creative straight at myapp://product/12345, you point it at an AppsFlyer OneLink / attribution (click) URL. OneLink is a single link that does three jobs at once:
-
Routing decision β on tap, AppsFlyer checks the device and app-install status and decides whether to open the app directly, defer through the store, or fall back to a web page.
-
Carries the destination β the intended in-app screen travels inside the link as parameters (e.g.
af_dp,deep_link_value) and is preserved through the store install so the SDK can deliver it on first open. -
Attribution β the same click is recorded for attribution, tying the install and the in-app event back to your campaign, creative and publisher. One integration, one source of truth.
So the rule of thumb for a DSP is: you pass the AppsFlyer click URL as the click-through, not the raw app URI. AppsFlyer resolves the rest. The raw URI is embedded inside that AppsFlyer link via af_dp / deep_link_value.
The AppsFlyer OneLink, decoded π
A OneLink click URL has a fixed shape: a OneLink subdomain + template ID, followed by query parameters. The parameters fall into three groups β deep-link destination, routing/fallback, and attribution/macros.
https://onelink-subdomain.onelink.me/ABcd?
pid=loopme_int // media source (partner id)
&c=summer_sale // campaign name
&af_dp=myapp%3A%2F%2Fproduct%2F12345 // deep link URI (installed users)
&deep_link_value=product_12345 // destination key (deferred / UDL)
&deep_link_sub1=US&deep_link_sub2=banner // extra context (optional)
&af_web_dp=https%3A%2F%2Fbrand.com%2Fp%2F12345 // web fallback
&clickid=CB-{AUCTION_ID} // your unique click id (dedupe / postbacks)
&idfa={IDFA}&advertising_id={GAID} // device id macros
Deep-link destination parameters π
| Parameter | Purpose | Notes |
|---|---|---|
af_dp |
App URI opened for users who ALREADY have the app installed. | Must be URL-encoded. e.g. myapp://product/12345 |
deep_link_value |
The destination βkeyβ the app reads via Unified Deep Linking (UDL). Used for both installed and deferred users. | Preferred modern field; app maps it to a screen. |
deep_link_sub1β10 |
Extra parameters passed alongside the value (e.g. coupon, category, locale). | Optional. Up to 10. |
af_web_dp |
Web URL to fall back to if routing to the app/store is not possible. | Recommended for graceful failure. |
af_ios_url / af_android_url |
Platform-specific web fallback / store override. | Optional, per-OS control. |
af_force_deeplink=true |
Force the app to open via the URI even when AppsFlyer would otherwise serve a redirect. | Use with care; test per app. |
Attribution & macro parameters π
These tie the click to your campaign. The Chartboost and LoopMe exchanges substitutes its macros into the URL at serve time; AppsFlyer substitutes its own user-level values. Always supply a unique clickid so clicks can be de-duplicated and matched to postbacks.
| Parameter | Typical value / macro | Purpose |
|---|---|---|
pid |
loopme_int (fixed) |
Media source β identifies your traffic to AppsFlyer. |
c |
{CAMPAIGN_NAME} |
Campaign name. |
af_siteid |
{APP_BUNDLE} |
Publisher app the impression ran on. |
af_c_id / af_ad_id |
{CAMPAIGN_ID} / {CREATIVE_ID} |
Campaign / creative IDs for reporting. |
clickid |
CB-{AUCTION_ID} |
Unique click identifier (dedupe + postback match). |
idfa / advertising_id |
{IDFA} / {GAID} |
Device advertising IDs (when available. |
Note: exact macro tokens (e.g. {AUCTION_ID}, {APP_BUNDLE}) are defined by the Chartboost exchange spec. Confirm the current token list with your LoopMe/Chartboost integration contact before going live.
Passing the deep link through the Chartboost SDK π
Key fact: the Chartboost OpenRTB 2.6 bid response has no dedicated deep-link field (there is no seatbid.bid.ext.deeplink). The deep link is delivered the same way as any click destination β it lives in the click-through of the ad markup (adm). The Chartboost SDK opens whatever click-through URL the creative carries; if that URL is your AppsFlyer OneLink, deep linking and deferred deep linking work automatically.
Therefore the deep link = the AppsFlyer click URL placed in the click-through of your **adm. Build the OneLink, URL-encode it, and drop it into the correct click-through slot for the creative type.
Where the click-through lives, by creative type π
| Creative type (ext.crtype) | Click-through location inside adm |
|---|---|
HTML / MRAID "HTML", "MRAID 1.0/2.0/3.0" |
The href of the clickable element, e.g. <a href="<OneLink>">...</a> or the MRAID open() call. |
MRAID Playable "MRAID playable" |
The store/click URL invoked on the end-card CTA (mraid.open with the OneLink). |
Video (VAST) "VAST 2.0/3.0" |
VideoClicks > ClickThrough (and CompanionClickThrough on the end card). |
Example β HTML / MRAID bid response π
The OneLink sits in the <a href>. Everything else is a standard Chartboost banner/MRAID response.
{
"id": "9d55...", "cur": "USD",
"seatbid": [{ "seat": "loopme_dsp", "bid": [{
"id": "rtb-9d55...",
"impid": "05f23...", // must match imp.id
"price": 15.26,
"adomain": ["brand.com"],
"bundle": "123456789", // advertised app store id / package
"crid": "loopme:dl_12345",
"mtype": 1,
"burl": "https://track.loopme.com/imp?ai=${AUCTION_ID}&p=${AUCTION_PRICE}",
"adm": "<!DOCTYPE html><html><body>
<a href=\"https://brand.onelink.me/ABcd?pid=loopme_int
&c=summer_sale&af_dp=myapp%3A%2F%2Fproduct%2F12345
&deep_link_value=product_12345
&af_web_dp=https%3A%2F%2Fbrand.com%2Fp%2F12345
&clickid=CB-${AUCTION_ID}&advertising_id=${GAID}\">
<img src=\"https://cdn.brand.com/ad.jpg\"/></a>
</body></html>",
"ext": { "crtype": "MRAID 2.0",
"imptrackers": ["https://track.loopme.com/imp?..."] }
}]}]
}
4.3 Example β VAST video bid response π
The OneLink goes in <ClickThrough> (and, if there is an end card, in <CompanionClickThrough>). URL-encode the link inside the CDATA.
<VAST version="3.0"><Ad><InLine>
<AdSystem>LoopMe</AdSystem><AdTitle>summer_sale</AdTitle>
<Impression><![CDATA[https://track.loopme.com/imp?...]]></Impression>
<Creatives><Creative><Linear>
<Duration>00:00:15</Duration>
<MediaFiles><MediaFile type="video/mp4" delivery="progressive"
width="568" height="320"><![CDATA[https://cdn/video.mp4]]>
</MediaFile></MediaFiles>
<VideoClicks>
<ClickThrough><![CDATA[https://brand.onelink.me/ABcd?pid=loopme_int
&c=summer_sale&af_dp=myapp%3A%2F%2Fproduct%2F12345
&deep_link_value=product_12345
&af_web_dp=https%3A%2F%2Fbrand.com%2Fp%2F12345
&clickid=CB-${AUCTION_ID}]]></ClickThrough>
</VideoClicks>
</Linear></Creative></Creatives>
</InLine></Ad></VAST>
Step-by-step integration checklist π
-
Get the OneLink template from the advertiser/MMP. The advertiser sets up the OneLink in AppsFlyer and shares the subdomain + template ID and the agreed
pidfor your traffic. -
Build the deep-link destination. Encode the app URI into
af_dpand setdeep_link_value(+ anydeep_link_sub*). Addaf_web_dpas a web fallback. -
Append attribution + macros. Add
pid,c,clickid, device-ID and exchange macros. -
URL-encode the whole OneLink before embedding it in the creative (double-check the encoding when it is nested inside JSON or CDATA).
-
Place it in the click-through for the creative type:
<a href>/mraid.openfor HTML-MRAID,<ClickThrough>for VAST. -
Set the required bid fields Chartboost expects:
id,impid,price,adomain,crid,bundle,mtype,ext.crtype, plusburlorext.imptrackersfor impression tracking. -
Test both paths: app installed = direct deep link; app not installed = store then deferred deep link on first open.
Testing & validation π
-
Installed-user (direct) test: with the advertised app installed, tap the creative in a Chartboost test placement β the app must open on the target screen, not the home screen.
-
Deferred test: uninstall the app, tap the creative, install from the store, open the app β first launch must land on the same target screen.
-
Attribution check: confirm the click and install appear in AppsFlyer attributed to
pid=loopme_intwith your clickid and campaign. -
Encoding check: decode the click-through URL from the live adm and confirm
af_dp/deep_link_valuesurvived JSON/CDATA nesting intact. -
Fallback check: on a device/OS where the app canβt open, confirm the user lands on
af_web_dprather than a dead screen.
Common mistakes to avoid π
| Mistake | Why it breaks | Fix |
|---|---|---|
| Passing the raw app URI as the click-through | Deferred users (no app) hit a dead link; no attribution. | Always pass the AppsFlyer OneLink; embed the URI in af_dp. |
| Double / missing URL-encoding | Parameters truncate at the first & or get mangled in JSON/CDATA. | Encode the full OneLink once; verify after nesting. |
Omitting deep_link_value |
Deferred deep linking canβt resolve the destination via UDL. | Set deep_link_value (and af_dp for installed users). |
No af_web_dp fallback |
Users with no app + failed store route hit nothing. | Always include a web fallback URL. |
| Reusing clickid across impressions | Clicks/installs canβt be de-duplicated or matched. | Use a unique id, e.g. CB-${AUCTION_ID}. |
| Wrong click-through slot for the format | SDK never fires the deep link. | <a href> for HTML/MRAID; <ClickThrough> for VAST. |