Track behavior on your own site.
Drop one first-party beacon on any marketing or e-commerce site and its page views flow into the same member behavior timeline as email, the loyalty portal and your app. No key, no dependency — a few lines of vanilla JS posting to a public endpoint. Flash parses device, geo and bot detection server-side.
One hosted line, or a self-contained snippet
Add the hosted tracker with a single script tag, or paste the self-contained snippet if you prefer no external dependency — either way it generates the identifiers, respects privacy signals, and POSTs a page-view to one endpoint:
You send the client bits
URL, title, referrer, ids, screen, language, timezone, UTM — that is all.
Flash enriches server-side
Device, OS and browser are parsed from the User-Agent; country from the IP; bots are detected and flagged. The client never has to be trusted for these.
It always returns 204
Bot, GPC, malformed, or accepted — the endpoint answers silently. Tracking never throws an error into your page.
On your own domain, behavior is anonymous. There is no Flash session cookie on your site, so page views are keyed to the anonymous_id you generate — not to a known member. Member attribution happens on Flash-owned surfaces (the loyalty portal and the in-app member center), where the session resolves the member. Use a consistent anonymous_idso a visitor's on-site behavior is at least unified across their own sessions.
Two ways in. Set YOUR_TEAM_IDand you're live.
Option A · Hosted — one line
Drop one script tag before </body>. It auto-tracks single-page-app route changes and exposes window.flashTrack() — nothing else to wire up.
<!-- SocialHub web tracking — one line, before </body> -->
<script src="https://flash.socialhub.ai/sdk/flash-tracker.js"
data-team-id="YOUR_TEAM_ID" defer></script>Option B · Inline — no external script
Want zero external requests (strict CSP, or the code inside your own bundle)? Paste this self-contained snippet instead. It exposes window.shTrack() — call it after each route change in a single-page app:
<!-- SocialHub web tracking — paste before </body> on every page -->
<script>
(function () {
var TEAM_ID = "YOUR_TEAM_ID"; // Settings → API
var ENDPOINT = "https://flash.socialhub.ai/api/v1/tracking/pageview";
// Honor Global Privacy Control / Do Not Track — no signal sent.
if (navigator.globalPrivacyControl === true || navigator.doNotTrack === "1") return;
function uid() {
return (window.crypto && crypto.randomUUID)
? crypto.randomUUID()
: (Date.now() + "-" + Math.random().toString(16).slice(2));
}
function persist(store, key) {
try { var v = store.getItem(key); if (!v) { v = uid(); store.setItem(key, v); } return v; }
catch (e) { return uid(); }
}
function utm() {
var p = new URLSearchParams(location.search), o = {};
["source", "medium", "campaign", "term", "content"].forEach(function (n) {
var val = p.get("utm_" + n); if (val) o[n] = val;
});
return Object.keys(o).length ? o : undefined;
}
function track() {
var body = JSON.stringify({
v: 1,
page_url: location.href,
page_title: document.title,
referrer: document.referrer || undefined,
anonymous_id: persist(localStorage, "sh_anon"), // stable per browser
session_id: persist(sessionStorage, "sh_session"), // resets per tab session
team_id: TEAM_ID,
utm: utm(),
screen: screen.width + "x" + screen.height,
language: navigator.language,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
timestamp: Date.now()
});
try {
if (navigator.sendBeacon) navigator.sendBeacon(ENDPOINT, body);
else fetch(ENDPOINT, { method: "POST", body: body, keepalive: true }).catch(function () {});
} catch (e) { /* never break the page */ }
}
window.shTrack = track; // for SPAs: call window.shTrack() after each route change
track(); // initial page view
})();
</script>// Single-page apps: after each client-side route change
router.afterEach(function () { window.shTrack && window.shTrack(); });The beacon body
Send application/json (or a plain string via sendBeacon). Anything over 4 KB or failing validation is dropped silently.
| Field | Type | Notes |
|---|---|---|
| v | number | Schema version. Always 1. |
| page_url | string | Full URL of the page (≤ 2048). Required. |
| page_title | string? | Document title (≤ 500). |
| referrer | string? | document.referrer (≤ 2048). |
| anonymous_id | string | Stable per-browser id you generate (≤ 36). Required. |
| session_id | string | Per-session id you generate (≤ 36). Required. |
| team_id | string | Your SocialHub team id (≤ 36) — identifies the brand. Required. |
| utm | object? | { source, medium, campaign, term, content } parsed from the query string. |
| screen | string? | "1920x1080". |
| language | string? | navigator.language. |
| timezone | string? | IANA tz, e.g. America/Los_Angeles. |
| timestamp | number | Client epoch ms. Required. |
Respect the visitor by construction
GPC & Do-Not-Track
The snippet checks navigator.globalPrivacyControl and navigator.doNotTrack and sends nothing when either is set. Keep that guard.
No raw PII
Send identifiers and page context only — never names, emails or form contents in the beacon. Member identity is resolved on Flash surfaces, not on your site.
Your consent banner first
If your site uses a consent manager, gate the snippet behind consent — only call the tracker once the visitor has accepted analytics.
Bot-filtered
Server-side bot detection flags non-human traffic so it does not pollute your member timeline or stats.
Capture the rest of the journey
Web is one of four surfaces. See how email, the loyalty portal and your app feed the same member timeline.