Web SDK
The Web SDK (@tolinku/web-sdk) provides event tracking, smart banners, in-app messages, referral management, and deferred deep link claiming for browser and Node.js environments.
Installation
Section titled “Installation”npm install @tolinku/web-sdkyarn add @tolinku/web-sdkpnpm add @tolinku/web-sdk<script src="https://cdn.jsdelivr.net/npm/@tolinku/web-sdk/dist/tolinku.min.js"></script>No build step. The script defines a global Tolinku, which is the same class
the npm package exports, so every example on this page works unchanged: replace
the import with the tag above and keep the rest.
import { Tolinku } from '@tolinku/web-sdk';
const tolinku = new Tolinku({ apiKey: 'tolk_pub_your_key', baseUrl: 'https://your-app.tolinku.com' // optional});The baseUrl defaults to https://api.tolinku.com. Set it to your Appspace’s domain if you use a custom domain.
User identification
Section titled “User identification”Set a user ID for analytics attribution and segment targeting:
tolinku.setUserId('user_123');
// Clear the user ID (e.g. on logout)tolinku.setUserId(null);Event tracking
Section titled “Event tracking”Track custom events:
// Simple eventtolinku.track('custom.page_view');
// Event with propertiestolinku.track('custom.purchase', { campaign: 'spring-sale', source: 'email', user_id: 'user_123'});Events are batched automatically (10 events or every 5 seconds) and sent via navigator.sendBeacon on page unload to avoid losing data.
You can force an immediate flush:
await tolinku.flush();Ecommerce tracking
Section titled “Ecommerce tracking”Track purchases, cart activity, and product events via tolinku.ecommerce:
// Set user ID for attributiontolinku.setUserId('user_123');
// Track a purchaseawait tolinku.ecommerce.purchase({ transaction_id: 'order_456', revenue: 49.99, currency: 'USD', items: [ { item_id: 'sku_1', item_name: 'T-Shirt', price: 24.99, quantity: 2 } ]});
// Track product viewsawait tolinku.ecommerce.viewItem({ items: [{ item_id: 'sku_1', item_name: 'T-Shirt', price: 24.99 }]});
// Track add to cartawait tolinku.ecommerce.addToCart({ items: [{ item_id: 'sku_1', quantity: 1 }]});
// Track checkout flowawait tolinku.ecommerce.beginCheckout({});await tolinku.ecommerce.addPaymentInfo();
// Search and product interactionsawait tolinku.ecommerce.search({ search_term: 'red shoes' });await tolinku.ecommerce.rate({ item_id: 'sku_1', rating: 4.5, max_rating: 5 });Ecommerce events are batched separately from custom events (10 events or 5-second timer) and sent to the ecommerce batch endpoint. The SDK automatically manages a cart ID across cart events and clears it after purchase.
Smart banners
Section titled “Smart banners”Display a smart app banner on your web page:
// Show the highest-priority active bannerawait tolinku.showBanner();
// Filter by labelawait tolinku.showBanner({ label: 'summer-promo' });
// Dismiss the current bannertolinku.dismissBanner();showBanner() accepts the same configuration as banner.js, just as a typed options object instead of data attributes:
await tolinku.showBanner({ style: 'floating', // or 'pinned' | 'stacked' animation: 'pop', theme: 'dark', position: 'top', anchor: '#site-header', // stacked mode only — explicit injection point // Banner shape bg: '#3B82F6', border: undefined, radius: 16, margin: 12, shadow: 'lg', // Title titleColor: '#ffffff', titleSize: 14, titleWeight: 600, // Body bodyColor: '#dbeafe', bodySize: 12, bodyWeight: 400, // CTA ctaBg: '#ffffff', ctaColor: '#3B82F6', ctaSize: 13, ctaWeight: 600, ctaRadius: 100, // Icon iconSize: 40, iconRadius: 10, // Toggles hideIcon: false, hideClose: false, hideBody: false, // Custom CSS hook customClass: 'my-banner', // Filter and timing label: 'summer-promo', delay: 0,});All options are optional; omit any you don’t need. See the banner script reference for value ranges, defaults, and the resolution chain.
In-app messages
Section titled “In-app messages”Display in-app messages:
// Show the highest-priority undismissed messageawait tolinku.showMessage();
// Filter by triggerawait tolinku.showMessage({ trigger: 'on_open' });
// Dismiss the current messagetolinku.dismissMessage();Messages are rendered as a DOM modal overlay.
Referrals
Section titled “Referrals”Access referral methods via tolinku.referrals:
// Create a referral codeconst { referral_code, referral_url } = await tolinku.referrals.create({ userId: 'user_123', userName: 'Jane Doe'});
// Look up a referralconst info = await tolinku.referrals.get('ABC123');
// Link a referred user (status stays pending until reward milestone is reached)await tolinku.referrals.complete({ code: 'ABC123', referredUserId: 'user_456'});
// Update milestone (completes the referral if it matches the reward milestone)await tolinku.referrals.milestone({ code: 'ABC123', milestone: 'first_purchase'});
// Claim reward (after granting it in your system)await tolinku.referrals.claimReward('ABC123');
// Get leaderboardconst { leaderboard } = await tolinku.referrals.leaderboard(10);Resolving an incoming link
Section titled “Resolving an incoming link”Mostly this does not come up on the open web, where the browser follows a link to Tolinku and lands on the page it resolves to. It comes up constantly inside a Capacitor or Cordova app, where the operating system hands the web layer the URL that was tapped and the routing happens in JavaScript.
A link arrives as the URL that was tapped, exactly as it was written. That is enough while the URL is readable, but every route also has a short link, and a short link is the same route written as a code:
https://links.example.com/s7k2p9q/4821Nothing in that URL says which route it is, and nothing on the device can work it out. Short links are what the dashboard’s copy button gives you and what a QR code carries, so your app will receive them whether or not you chose to share them.
links.resolve asks Tolinku and answers with the route, the token and the
canonical path. A readable URL resolves to itself, so resolve every incoming
link rather than trying to spot the short ones. It returns nothing rather than
throwing when it cannot reach us, so your own handling stays the fallback.
const link = await tolinku.links.resolve(url);const path = link?.deep_link_path ?? new URL(url).pathname;// path -> "/merchant/abc123", and link.token -> "abc123"token saves you working out which segment it is, which the URL alone does not
tell you when a route’s prefix places its token mid-path.
Deferred deep links
Section titled “Deferred deep links”Recover the link a visitor tapped before they had your app, once.
const link = await tolinku.deferred.claimDeferredLink({ appspaceId: '64f0a1b2c3d4e5f60718',});if (link) { // Route to link.deep_link_path}How matching works on the web
Section titled “How matching works on the web”There is no install referrer in a browser, so this is device signal matching: timezone, language, screen size and pixel ratio are compared against what the landing page recorded when the link was tapped. Matching is probabilistic and the window is short.
On Android the same call tries the Play referrer first, which is deterministic. That difference is in the SDKs, not in your code: the call is the same name with the same arguments on every platform.
Calling it once
Section titled “Calling it once”A claim is consumed the first time it succeeds. claimDeferredLink remembers
that it asked, in localStorage, so calling it again costs nothing.
Only a settled answer is remembered. “Nothing waiting for this device” counts,
because no amount of asking will change it. A dropped request, or a 403 from an
appspaceId you are about to correct, does not.
If storage is unavailable, as in a private window, the claim is attempted rather than skipped: an extra request costs less than an install nobody can attribute.
The lower-level calls
Section titled “The lower-level calls”Both are still available and unchanged. They ask every time they are called and do no remembering.
const byToken = await tolinku.deferred.claimByToken(token);const bySignals = await tolinku.deferred.claimBySignals({ appspaceId });Cleanup
Section titled “Cleanup”For single-page applications, clean up when unmounting:
tolinku.destroy();This flushes any queued events, removes DOM elements (banners, messages), and cancels pending requests.