Everything you need to run Dbrij Glass: the web tag and its options, privacy and masking, custom events and identify, consent, the Swift, Kotlin, Flutter and React Native trackers, the collector API, site settings and limits.
Dbrij Glass shows you what people actually do on your website and in your apps: sessions, pages, clicks, rage clicks and dead clicks, scroll depth, heatmaps, funnels, retention and session replays. A website gets one script tag. A native app gets a single file tracker for Swift, Kotlin or Flutter, or the @dbrij/glass-native package for React Native. Everything lands in the same Glass dashboard.
Every site you add under Glass, Sites gets a site key that starts with gk_. The key only lets a tracker send events to that site. It cannot read anything back, so it is safe in your page source and in an app bundle. If you ever need a new one, rotate it from the site's settings: the old key stops being accepted within a minute.
https://glass.dbrij.com/glass.jshttps://api.dbrij.com/api/v1/public/glassutm_source, utm_medium and utm_campaign of the landing address, and the screen size.No cookies and no fingerprinting. A visitor is a random id the tracker makes and keeps in the browser's local storage. A session ends after 30 minutes without activity, or after 4 hours in total, whichever comes first.
Add the site under Glass, Sites, copy its tag, and paste it before </body> on every page (or in <head>; it is deferred either way). Events arrive within seconds of the next visit.
<script src="https://glass.dbrij.com/glass.js" data-site="gk_your_site_key" defer></script>The tracker sends events in batches: every 5 seconds, or as soon as 20 are waiting, and once more when the page is hidden or closed. Replays are recorded by a second file, glass-record.js, which the tag fetches from its own origin only for a session the replay sample picked, so the tag itself stays small on every page.
The tag never breaks your page. Every part of it is wrapped, and a failure is silent. Without a data-site attribute it writes one warning to the console and does nothing else.
If your site sends a Content Security Policy, allow the Glass host in script-src (it serves both files) and the Dbrij API in connect-src.
script-src 'self' https://glass.dbrij.com;
connect-src 'self' https://api.dbrij.com;A web service you host on Dbrij needs no paste at all. The Sites page lists your hosted web services; start watching one there and the tag is added at the hosting edge.
Nothing extra to wire. The tag follows route changes made through the history API and records each new page. If your app has a signed in area you would rather not record, see glass.appMode() under Custom events and identify, and data-replay-pause under Tag options.
Options are attributes on the script tag. Each one applies to that install only; the site's own settings (Site settings) apply everywhere.
ATTRIBUTES
| data-site* | string | The site key, gk_… from Glass, Sites. |
| data-endpoint | string | Where the tracker sends. A full https address, or a path read against the script's own origin. Leave it out to use the Dbrij API. |
| data-replay | "off" | "masked" | "off" never records the screen on this install; events still arrive. "masked" records with every piece of text and every input masked, and images, pictures, video, audio, canvas, frames, objects and embeds left out: layout, navigation and clicks stay visible. |
| data-replay-pause | string | Comma separated path prefixes where replay must not run, such as "/account,/billing". Each prefix covers the path and everything under it: /mail covers /mail/inbox, not /mailing. Entries must start with /. |
| data-scrub | "ids" | Treats paths that differ only by an identifier as the same page, so moving between them records no new page view. An identifier is a UUID, a segment of 16 or more letters, digits, underscores or hyphens, or a number of 3 or more digits. Query strings are ignored for this too. |
| data-labels | "hooks" | Click labels come only from data-glass-id, data-testid or aria-label, never from the text on the page. Use it where rows show people's names. |
<script src="https://glass.dbrij.com/glass.js"
data-site="gk_your_site_key"
data-replay="masked"
data-replay-pause="/settings,/billing"
data-scrub="ids"
data-labels="hooks"
defer></script>On a single page app, moving onto a paused path ends the current recording at once, and moving off it starts a fresh one with its own snapshot.
Inputs are always masked in replays, at every masking level and on every plan. What a person types into a field never leaves the browser.
MASKING LEVELS (SITE SETTINGS)
| strict | level | Every piece of text is masked in replays, and clicks carry no label. |
| balanced | level (default) | Text is masked inside any element with the glass-mask class or the data-glass-mask attribute. |
| relaxed | level | Only inputs are masked. |
MARKUP YOU CONTROL
| class="glass-mask" / data-glass-mask | attribute | Masks the text inside the element (at the balanced level; strict masks all text anyway). |
| class="glass-block" / data-glass-block | attribute | Leaves the element and everything in it out of the recording entirely, at every level. |
| data-glass-id | attribute | A stable name for an element. Used as the click label and in the selector, so a heatmap and a funnel survive a redesign. |
<div class="glass-mask">Balance: ₦240,000</div>
<section data-glass-block>…medical history…</section>
<button data-glass-id="checkout-pay">Pay now</button>A click's label is read from the clicked element or up to three of its parents, in this order: data-glass-id, data-testid, aria-label, the element's own text when it is 60 characters or fewer, then alt, title, placeholder and name. A label never reads a field's value, and it is cut to 60 characters. With data-labels="hooks" only the first three are used; at the strict level no label is sent.
glass-block.A session records at most 60 minutes of replay in total. The recorder pauses after 3 minutes with no mouse, key, touch, wheel or scroll input, and starts a fresh segment on the next input. A tab left open records nothing.
| glass:vid | localStorage | The random visitor id. |
| glass:s:<key prefix> | localStorage | The current session: its id, when it started and when it was last active. |
| glass:cfg:<key prefix> | sessionStorage | The site's tracker settings, kept for 5 minutes. |
Once the tag has loaded, window.glass carries a small API.
WINDOW.GLASS
| track(name, props?) | function | Records a moment your code names, such as "signup". The name is cut to 120 characters. props is a plain object; when its JSON is over 4 KB the props are dropped and the event kept. Custom events show under Events and can be funnel steps. |
| identify(id, traits?) | function | Ties this visitor to your own user id (up to 200 characters). traits merge into that person's profile on the site. Ignored when the browser sends Global Privacy Control. |
| stop() | function | Sends what is already queued, then stops all tracking and recording for this page load. |
| appMode(mode?) | function | For the moment somebody signs in to a single page app. Turns on data-scrub="ids" and data-labels="hooks". "events" (the default) ends replay; "masked" ends the current recording and starts a masked one, as data-replay="masked", on the next input. |
| session() | function | The current session as { site, id, replay }, or null once stopped. |
| version | string | The tracker version. |
glass.track('signup', { plan: 'pro', source: 'pricing' });
// after your own sign in succeeds
glass.identify('user_1234', { plan: 'pro', company: 'Acme' });
// on a single page app, once the pages become personal
glass.appMode('masked');Calls can be queued before the script arrives. Put them in window.glass.q as arrays; the tag replays track and identify calls from the queue once it starts, then replaces window.glass with the API above.
<script>
function glassCall(method, a, b) {
var g = window.glass;
if (g && typeof g[method] === 'function') return g[method](a, b);
(window.glass = g || { q: [] }).q.push([method, a, b]);
}
glassCall('track', 'pricing_viewed', { plan: 'pro' });
</script>
<script src="https://glass.dbrij.com/glass.js" data-site="gk_your_site_key" defer></script>Glass sets no cookies, reads no fingerprint and records no typed input. Nothing is collected before the script runs, so if your site asks for consent before analytics, add the tag only after the visitor agrees.
function startGlass() {
var s = document.createElement('script');
s.src = 'https://glass.dbrij.com/glass.js';
s.setAttribute('data-site', 'gk_your_site_key');
s.defer = true;
document.head.appendChild(s);
}WAYS TO STOP OR NARROW TRACKING
| glass.stop() | web | Withdraws for the rest of the page load. Leave the tag out on later pages to keep it withdrawn. |
| Global Privacy Control | web | When the browser sends it, the session is never recorded and identify() is ignored. Page views, clicks and custom events still arrive, under the random visitor id. |
| data-replay="off" | web | Events only, never a recording, on that install. |
| disabled / setDisabled(true) | native | Start a native tracker disabled, or switch it off at run time. Nothing is stored or sent while disabled; the queue is cleared and identify() calls are dropped. |
| Site status: paused | site | Every tracker on the site is told to stop, and anything that still arrives is dropped. |
Native apps send sessions, screens, taps and custom events to the same site key as your website; the dashboard shows the device as iOS or Android and the browser as App <version>. Session replay is web only: native sessions appear without a recording.
Pure native apps use one file per platform with no dependencies: glass-tracker.swift, GlassTracker.kt or glass_tracker.dart. React Native uses @dbrij/glass-native from npm. The API is the same everywhere.
THE API ON EVERY PLATFORM
| screen(name) | method | A screen became visible. The name becomes a page path: "Checkout / Payment" is /checkout-payment. |
| track(name, props?) | method | A moment your code names, with optional properties. |
| identify(id, traits?) | method | Ties this device's sessions to your own user id. Dropped while disabled. |
| tap(xPct, yPct, label?) | method | A tap, as percentages of the screen from 0 to 100, for heatmaps. label names the element, like data-glass-id on the web. |
| onForeground() / onBackground() | method | Lifecycle. Background sends the queue and saves it; foreground continues the session and refreshes the site settings. |
| flush() | method | Sends everything queued now. |
| setDisabled(bool) | method | The run time privacy switch. Disabling clears the queue and stops all sending. |
npm install @dbrij/glass-nativeimport AsyncStorage from '@react-native-async-storage/async-storage';
import { AppState, Dimensions, Platform } from 'react-native';
import { createGlass } from '@dbrij/glass-native';
export const glass = createGlass({
siteKey: 'gk_your_site_key',
platform: { os: Platform.OS === 'ios' ? 'ios' : 'android', device: 'mobile', appVersion: '1.4.2' },
storage: AsyncStorage, // getItem / setItem / removeItem
screen: Dimensions.get('screen'),
// disabled: true, // start switched off until the person agrees
});
AppState.addEventListener('change', (state) => {
if (state === 'active') glass.onForeground();
else if (state === 'background') glass.onBackground();
});
// screens from your navigator
const route = navigationRef.getCurrentRoute();
if (route) glass.screen(route.name);
glass.track('signup', { plan: 'pro' });
glass.identify('user_1234', { plan: 'pro' });
glass.tap(48, 88, 'buy-button');CREATEGLASS OPTIONS
| siteKey* | string | The gk_ key. Any other value leaves the tracker disabled. |
| platform* | { os, device?, appVersion? } | os is "ios" or "android"; device is "mobile" (default) or "tablet"; appVersion is shown as App <version> (up to 20 characters). |
| storage | GlassStorage | Where the visitor id, session and offline queue persist. Memory by default; pass AsyncStorage or similar on a device. |
| endpoint | string | API base. Default https://api.dbrij.com/api/v1. |
| disabled | boolean | Start disabled. |
| screen | { width, height } | Screen size, for context. |
The React Native client also has visitorId(), the persistent visitor id (empty until storage has loaded).
let glass = GlassTracker(siteKey: "gk_your_site_key", appVersion: "1.4.2")
// optional: endpoint:, device: "tablet", disabled: true, defaults: UserDefaults
// on iOS the app lifecycle is observed for you
glass.screen("Checkout")
glass.track("signup", props: ["plan": "pro"])
glass.identify("user_1234", traits: ["plan": "pro"])
glass.tap(xPct: 48, yPct: 88, label: "buy-button")
glass.setDisabled(true)val glass = GlassTracker(application, GlassTracker.Config(
siteKey = "gk_your_site_key",
appVersion = "1.4.2",
// endpoint = "https://api.dbrij.com/api/v1", device = "tablet", disabled = true,
))
// constructed with an Application, it follows the activity lifecycle for you
glass.screen("Checkout")
glass.track("signup", mapOf("plan" to "pro"))
glass.identify("user_1234", mapOf("plan" to "pro"))
glass.tap(48.0, 88.0, "buy-button")
glass.setDisabled(true)final glass = GlassTracker(GlassConfig(
siteKey: 'gk_your_site_key',
platformOs: Platform.isIOS ? 'ios' : 'android',
appVersion: '1.4.2',
storage: PrefsStorage(prefs), // optional; memory only by default
));
// lifecycle, from a WidgetsBindingObserver
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) glass.onForeground();
if (state == AppLifecycleState.paused) glass.onBackground();
}
// screens, from a NavigatorObserver
class GlassNavObserver extends NavigatorObserver {
@override
void didPush(Route route, Route? previous) {
final name = route.settings.name;
if (name != null) glass.screen(name);
}
}GlassStorage is three async methods, getItem, setItem and removeItem. Back it with shared preferences to keep the visitor id and the offline queue across launches.
The trackers above speak this protocol, and you only need it to write a tracker of your own. It runs on other people's pages, which shapes it: no credentials, no custom headers, and bodies sent as text/plain, so a browser never sends a preflight. Every response carries Access-Control-Allow-Origin: *. Refusals are silent: the collector answers with success and drops what it will not keep.
https://api.dbrij.com/api/v1/public/glassGET/public/glass/config?k=<site key>
What the tracker should do. JSON, inside the usual { "success": true, "data": … } envelope. 30 requests a minute from one address.
{
"ok": true, // false: unknown key or paused site. Stop sending.
"sampleRate": 100, // percent of sessions to record
"maskingLevel": "balanced", // strict | balanced | relaxed
"replayEnabled": true,
"quotaExceeded": false, // true: the month's events are spent. Do not record.
"sessionTimeoutMs": 1800000
}POST/public/glass/ingest
One envelope of events as JSON in a text/plain body. Answers 202 with { "ok": true } whatever happened. 120 requests a minute from one address.
{
"k": "gk_your_site_key",
"v": "gv_3f9a0c1d2e4b5a6978c0d1e2", // visitor id
"s": "gs_8b7c6d5e4f3a2b1c0d9e8f7a", // session id
"url": "https://acme.com/pricing?utm_source=news",
"ref": "https://news.example/today",
"utm": { "s": "news", "m": "email", "c": "october" },
"sw": 1440, "sh": 900,
"events": [
{ "t": "pageview", "p": "https://acme.com/pricing", "ts": 1767225600000, "props": { "scrollPct": 64 } },
{ "t": "click", "p": "https://acme.com/pricing", "ts": 1767225603000,
"props": { "selector": "[data-id=\"plan-pro\"]", "x": 812, "y": 1420, "vw": 1440, "vh": 3200, "text": "Choose Pro" } },
{ "t": "custom", "n": "signup", "p": "https://acme.com/pricing", "ts": 1767225610000, "props": { "plan": "pro" } },
{ "t": "identify", "n": "user_1234", "ts": 1767225611000, "props": { "plan": "pro" } }
]
}ENVELOPE FIELDS
| k* | string | Site key, starting gk_, up to 40 characters. |
| v* | string | Visitor id: 10 to 64 letters, digits, underscores or hyphens. |
| s* | string | Session id: 10 to 40 letters, digits, underscores or hyphens. |
| url | string | The page address, up to 600 characters. Used for any event without its own p. |
| ref | string | Referrer, up to 300 characters. A referrer on the same host counts as navigation, not as a source. |
| utm | { s, m, c } | Campaign source, medium and name, up to 120 characters each. |
| sw, sh | number | Screen width and height. |
| pf | "ios" | "android" | Native trackers only. Any other value is a web envelope. |
| dv | "mobile" | "tablet" | Native trackers only. Anything but "tablet" reads as mobile. |
| av | string | Native app version, up to 20 characters. |
| events* | Event[] | Up to 50; anything after the 50th is ignored. |
EVENT FIELDS
| t* | "pageview" | "click" | "custom" | "identify" | Any other type is dropped. |
| n | string | The custom event name (up to 120 characters) or the identify id (up to 200). Required for custom and identify; without it the event is dropped. |
| p | string | The page address, up to 600 characters. Kept as its host and path: query and hash removed, a trailing slash trimmed, the path cut to 300 characters. |
| ts | number | Milliseconds since the epoch. More than 5 minutes from the collector's clock, it is replaced with the time of arrival. |
| props | object | Up to 4 KB of JSON. Larger, or not a plain object, and the props are dropped while the event is kept. Clicks carry selector, x, y, vw, vh, text, rage and dead; page views carry scrollPct. |
A body over 512 KB, a body that is not JSON, or a bad key, visitor id or session id drops the whole envelope. A bad event drops only that event.
Origin (or, without one, Referer) host is not one of the site's allowed domains. A request with neither header is accepted.POST/public/glass/replay?k=&s=&q=&st=&et=&c=
One chunk of recorder events as raw bytes: a JSON array, gzip compressed when c=1. The server stores it exactly as received and never inflates it. Answers 204. 240 requests a minute from one address.
QUERY
| k* | string | Site key. |
| s* | string | Session id, as in the envelope. |
| q* | integer | Chunk sequence number, 0 to 5000. A sequence number already stored for the session is ignored, so a retry is safe. |
| st, et | integer | Timestamps of the first and last event in the chunk. |
| c | "1" | "0" | 1 when the body is gzip. |
Chunks over 10 MB, chunks for a site with replay switched off, and chunks after the month's events are spent are dropped.
Each site under Glass, Sites carries the settings below. They decide what every tracker on that site does, wherever it is installed.
| Name | 1 to 120 characters | How the site reads in the dashboard. |
| Allowed domains | up to 20 | Hosts allowed to send events, such as acme.com. A domain also allows its subdomains. The scheme, www. and any path are removed as you save. Empty accepts any origin, which is fine while you get started. |
| Masking level | strict | balanced | relaxed | How much text a replay masks. Default balanced. See Privacy and masking. |
| Replay | on | off | Whether sessions record at all. Default on. |
| Replay sample rate | 0 to 100 | The percentage of sessions that record. Drawn once per session, so a reload keeps recording. Default 100. |
| Event retention | 7 to 365 days | How long events are kept. Default 90. |
| Replay retention | 1 to 90 days | How long replays stay watchable. Default 30, and never longer than the plan allows (see Plans and limits). |
| Status | active | paused | Paused tells every tracker to stop, and drops anything that still arrives. |
| Rotate key | action | Issues a new gk_ key. The old one stops being accepted; update your tag and apps. |
The collector reads site settings at most a minute old. A web tracker reads them on every page load (keeping a copy for up to 5 minutes) and again every 5 minutes; a native tracker on start and on return to the foreground, at most every 5 minutes.
Glass is sold as a small ladder. Each rung adds something rather than only counting higher, and nothing that protects your visitors is ever left to a higher rung: input masking is on every plan. Rungs marked yearly are billed once a year, at ten times the monthly price.
| Plan | Price | Billing | Sites | Events a month | Replays kept | Funnels | Retention cohorts |
|---|---|---|---|---|---|---|---|
| One site | ₦1,500 a person a month | Yearly | 1 | 100,000 | 14 days | No | No |
| Team | ₦2,250 a person a month | Yearly | 3 | 400,000 | 30 days | Yes | No |
| Business | ₦3,000 a person a month | Monthly or yearly | Up to 20 | 1,000,000 | 90 days | Yes | Yes |
The allowance counts every event across all of a company's sites in a calendar month (UTC). Past it, the collector drops new events and replay chunks until the month turns: it is a stop, never a bill. Trackers are told quotaExceeded: a web tracker stops recording, and the app trackers stop sending until a later check, when the app comes back to the foreground, finds the site open again. The account is warned as the month's usage nears the allowance.
| Events per envelope | 50 | Further events in the same envelope are ignored. |
| Envelope size | 512 KB | Larger envelopes are dropped. |
| Event props | 4 KB of JSON | Larger props are dropped; the event is kept. |
| Clock skew | 5 minutes | Times further off become the time of arrival. |
| Replay chunk | 10 MB | Up to 5,000 chunks per session. |
| Replay per session | 60 minutes | Recording pauses after 3 idle minutes. |
| Session | 30 minutes idle, 4 hours total | A new session starts after either. |
| Requests a minute, one address | 30 / 120 / 240 | Settings / events / replay chunks. |
Every SDK and package for Glass, with what it does, how to install it and the smallest working use. The whole catalogue, for every product, is on the apps page.
The Glass tracker for websites: page views, clicks, custom events and session replay from one script tag.
Script for your site · JavaScript · Served by Dbrij · Runs on Browsers
<script src="https://glass.dbrij.com/glass.js" data-site="gk_your_site_key" defer></script><script src="https://glass.dbrij.com/glass.js" data-site="gk_your_site_key" defer></script><script> // once the tag has loaded glass.track('signup', { plan: 'pro' }); glass.identify('user_1234', { plan: 'pro' });</script>Glass analytics for React Native: sessions, screens, taps and events in the same dashboard as your website.
SDK · TypeScript · npm · Runs on React Native, Any runtime with fetch
npm install @dbrij/glass-nativeimport AsyncStorage from '@react-native-async-storage/async-storage';import { AppState, Dimensions, Platform } from 'react-native';import { createGlass } from '@dbrij/glass-native';export const glass = createGlass({ siteKey: 'gk_your_site_key', platform: { os: Platform.OS === 'ios' ? 'ios' : 'android', device: 'mobile', appVersion: '1.4.2' }, storage: AsyncStorage, screen: Dimensions.get('screen'),});AppState.addEventListener('change', (state) => { if (state === 'active') glass.onForeground(); else if (state === 'background') glass.onBackground();});glass.track('signup', { plan: 'pro' });The Glass tracker for native iOS apps, as one Swift file you drop into your project.
SDK · Swift · Served by Dbrij · Runs on iOS, Other Swift platforms with Foundation
curl -O https://dbrij.com/glass-tracker.swiftlet glass = GlassTracker(siteKey: "gk_your_site_key", appVersion: "1.4.2")glass.screen("Checkout")glass.track("signup", props: ["plan": "pro"])glass.identify("user_1234", traits: ["plan": "pro"])glass.tap(xPct: 48, yPct: 88, label: "buy-button")The Glass tracker for native Android apps, as one Kotlin file you drop into your project.
SDK · Kotlin · Served by Dbrij · Runs on Android
curl -O https://dbrij.com/GlassTracker.ktval glass = GlassTracker(application, GlassTracker.Config( siteKey = "gk_your_site_key", appVersion = "1.4.2",))glass.screen("Checkout")glass.track("signup", mapOf("plan" to "pro"))glass.identify("user_1234", mapOf("plan" to "pro"))glass.tap(48.0, 88.0, "buy-button")The Glass tracker for Flutter apps, as one Dart file you drop into lib.
SDK · Dart · Served by Dbrij · Runs on Flutter, Dart with dart:io
curl -O https://dbrij.com/glass_tracker.dartfinal glass = GlassTracker(GlassConfig( siteKey: 'gk_your_site_key', platformOs: Platform.isIOS ? 'ios' : 'android', appVersion: '1.4.2',));glass.screen('Checkout');glass.track('signup', {'plan': 'pro'});glass.identify('user_1234', {'plan': 'pro'});Dbrij is Nigeria’s company operating system: the first Nigerian built platform to put team chat, video meetings, email, HR, tax compliant payroll, customer support, marketing, analytics and cloud hosting in one product on one subscription.