Web SDK · v0.6.0
Attribution
How RTN classifies campaign and platform touches, and how to read local or server-confirmed attribution.
The SDK records where every visit came from and sends context.attribution = { first_touch, last_touch } whenever the server may not have it yet. No extra code is required; tag your links correctly.
The touches travel on:
- the first event of every session,
order_completed,checkout_startedandsigned_up, so conversions always carry their campaign,- every event after a new touch until the ingest has accepted one of them (a failed or retried request does not count).
Other events stay small; the server stores the touches once per anonymous ID and waitForAttribution() reads them back.
first_touchis the first recorded visit of this browser, the web equivalent of an install referrer. It is never overwritten.last_touchis the most recent non-direct visit. A direct visit never replaces it. A reload or back navigation in the same session keeps its time; returning through the same link after the session ended (30 minutes of inactivity) movesat, so attribution windows count from the latest click.
In the browser, each touch holds the raw signals: UTM values (source, medium, campaign, term, content), the ad click ID (click_id, click_id_type), referrer_domain, landing_path, and at. The ingest edge classifies them into platform and channel (and fills source and medium when absent), so classification rules can improve without an SDK upgrade.
Link tagging
| Source | Example | Edge result platform / channel |
|---|---|---|
| Google Ads | automatic gclid + ?utm_campaign=black_friday | google / paid_search |
| Google organic | referrer google.com | google / organic_search |
| Instagram ad | ?utm_source=instagram&utm_medium=paid_social&utm_campaign=winter (+ fbclid) | instagram / paid_social |
| Facebook / Instagram organic | only fbclid, referrer l.facebook.com | facebook / organic_social |
| TikTok ad | automatic ttclid | tiktok / paid_social |
| Microsoft Ads / Yandex Direct | msclkid / yclid | microsoft / yandex, paid_search |
| Newsletter | ?utm_source=newsletter&utm_medium=email&utm_campaign=october | email / email |
| RTN message | ?utm_source=rtn&utm_medium=sms&utm_campaign=cart_reminder&rtn_cid=<message_id> | rtn / sms |
| Affiliate | ?utm_source=partner&utm_medium=affiliate | partner / affiliate |
| Other site | referrer blog.example.org | blog.example.org / referral |
| Direct | no referrer or parameters | direct / direct |
Returning from a payment page (payment providers, bank 3-D Secure pages) does not overwrite the campaign. Add your own provider with ignoredReferrers:
rtn.init('WRITE_KEY', { ignoredReferrers: ['pay.example-psp.com'] });Store promotions on orders
Attach the promotion applied at checkout to orderCompleted:
rtn.orderCompleted({
order_id: 'ORD-1001',
products: [/* ... */],
value: 2339.82,
discount: 259.98,
coupon: 'WINTER10',
promotion_id: 'winter_2026',
promotion_name: 'Winter 10%',
});Read attribution in the browser
getAttribution() returns the local snapshot, or null without consent:
const local = rtn.getAttribution();
console.log(local?.last_touch.source);waitForAttribution() asks the ingest service for the server-confirmed attribution. It never blocks event collection: if no answer arrives within timeoutMs (default 2000 ms), it resolves with the local snapshot.
const attribution = await rtn.waitForAttribution({ timeoutMs: 2000 });Connect backend records to the visitor
To link a server-side record, such as an order created by your backend, to the browser visitor, send the anonymous ID with your form. getAnonymousId() returns null without consent.
rtn.ready(() => {
document.querySelector('#checkout-form input[name=rtn_anonymous_id]').value =
rtn.getAnonymousId() ?? '';
});This is an integration input only. The current platform does not verify orders or refunds on the server, and order_completed remains a client-side signal marked estimated: true.