Install barua on your site.

One tag on every page, high in the head. Nothing to configure after it: no events to name, no goals to define. This page is the whole integration: the tag, plus the two calls it exposes.

The tag

<script async src="https://barua.ai/b.js" data-key="pk_…"></script>

Your key is on the Setup screen in the app, shown once when it's issued. Paste the tag high in the <head> on every page. Capture starts immediately, and the first profiles fill within a couple of hours.

The key is public by design

The key ships in your HTML, so treat it as public; it's built for that. All it can do is post visitor activity into your workspace; it can never read anything back. Browser posts are checked against your site's origin once it's set in the app (a best-effort abuse check, never an authentication boundary), and the key is stored hashed on our side.

Want a fresh one? Rotate it from Setup in one click. The old key is refused everywhere within a few minutes, and that includes the copy still sitting in your deployed HTML: paste the new key into the tag and redeploy right after rotating, or capture stops until you do.

What it captures

A masked replay of each visit, plus a stream of plain events read from the markup your site already publishes. Masking happens in the browser: every input value and every piece of rendered text is replaced before anything is sent, with one narrow stylesheet edge described on the privacy page. An iframe's contents are never recorded, and an element you mark with data-cg-block keeps only an empty placeholder in the replay. The full stance is on the privacy page.

The events:

Pages pageview page_title page_leave tab_visible attention Interaction click rage_click exit_intent form_abandon form_state site_search custom Commerce product_view add_to_cart_click cart_state datalayer Context attribution device geo js_error Identity identify

One detail worth knowing: on a shop whose own cart endpoint doesn't answer, barua infers the cart from add-to-cart clicks and marks it as inferred, so downstream decisions know the difference.

Cart events distinguish a confirmed cart from an estimate or an unavailable reading. A confirmed empty cart clears the previous contents. Product details can refresh when your page updates its structured markup.

Form events distinguish an attempt from a confirmed completion. They include field types and browser validation categories. A submitted form with an unknown result stays unresolved. Clicks include the destination link when one exists, and attention is saved when a tab moves into the background.

Tag attributes

AttributeWhat it does
data-keyRequired. Your workspace key: pk_ followed by 48 hex characters.
data-consentSet to "required" to capture nothing, and write nothing to the device, until your consent banner calls barua.consent(true). A refusal stores only the refusal marker. The mode for an EU posture.
data-auto-identifySet to "off" to turn off automatic email pickup. Explicit barua.identify() calls still work.
data-endpointOverrides where event batches post, for proxying through your own domain. The handed-out snippet doesn't need it, and the once-per-session city lookup still calls barua.ai directly.
data-cg-blockThis one goes on any element of your page. Its contents are never recorded; the replay keeps an empty placeholder carrying the element's tag, class, width, and height.

Serve it from your own domain

Everything on this page works without this section: the tag as handed out captures today, and as of August 2026 the common blockers' filter lists do not carry barua, so their domain rules have nothing to match. This section is for the day some list adds it. Most ad blockers block by domain. Serve the tag and its events from your own domain and there is no third-party domain left for a filter list to match. Consent is a separate promise, and the proxy changes nothing about it. The rewrite routes live in your hosting configuration, with no DNS changes. Your sending domain setup stays exactly as it is. The one exception is the Cloudflare Worker path, which assumes your site already runs through Cloudflare; if it does not, use the rewrite route your own host provides instead. The script itself sends event batches with credentials omitted, leave-time flushes included, so proxied batches carry no site cookies on any host; the Worker and Nginx recipes also strip the header outright. What remains on plain-rewrite hosts is the script file request, plus the rare leave-time flush too large for a credential-free send, whose cookies travel in transit only: barua's event intake ignores cookies and stores none.

Your setup page shows the routes ready to paste. Netlify and Vercel take two rewrite lines each. Cloudflare takes a small Worker on the same two paths, and any proxy that can forward two paths works the same way: one serves the script from a path on your site, the other forwards event batches. The tag then uses data-endpoint with that path. Pick short, bland paths. Words like analytics, stats, track, or pixel get matched by filter patterns, and blockers running in stricter modes can still match paths, which is exactly why the names matter. The proxy changes nothing about consent: the script still captures nothing before barua.consent(true).

One optional line of server middleware goes further: when a request URL carries the b= email token, report it, and the click is captured even in a browser where the script never ran. The exact call is on your setup page. This runs in your server, outside the script's consent gate, and it stores one scrubbed URL of a barua-tagged click. The row names no person and no session, and the token in the address links it to the email it came from. If your consent posture requires it, fire it only when your own consent cookie grants. It is your middleware, and that one line is yours to gate.

The JavaScript API

The tag exposes window.barua with four calls.

barua.identify(email, externalId?, traits?)

Usually you never call it: when a visitor gives your site their email through a popup or a checkout, barua picks it up. For a logged-in app, call it when each page restores a signed-in session or changes account:

barua.identify('them@theiremail.com')

The sign-in event itself lands after the async tag has loaded in practice. A call that can run at page boot instead (a restored session) gets the same guard as consent: window.barua?.identify(…), repeated when your session state settles. On a consent-required site, an identify fired before the visitor opts in is held in memory and delivered when barua.consent(true) arrives.

traits carries what no form captured, an OAuth signup's name for instance. Recognized keys: first_name, last_name, name, phone. Values are capped at 80 characters, and any other key is dropped.

externalId is your own user id, and barua holds it as a claim rather than trusting it: a browser can claim to be anyone. To make ids stick, assert them from your backend through a bearer-authed endpoint; write chris@barua.ai and we'll set you up.

A different identity starts a fresh browser context. Activity already queued keeps its original owner. A SHA-256 marker allows the tag to notice identity changes across page loads without storing the email address in that marker. A page that has identified someone keeps that account until its own next identify or reset call. Unidentified tabs can adopt the latest shared browser context. If the browser cannot coordinate the first identity claim safely, earlier anonymous activity may remain separate from the identified profile.

barua.reset()

Call this when the page learns that someone has signed out. It starts fresh anonymous and session IDs. Other unidentified tabs on the same site adopt the change; pages that identified their own account follow their own sign-in state. Already queued activity keeps its original IDs. Use barua.consent(false) to stop collection and clear pending activity.

barua.track(name, properties?)

Use this when your app knows a meaningful action happened. For example: barua.track('feature_used', { content_type: 'editor', count: 1 }). Recognized properties accept short codes or numbers; unknown properties are dropped. Do not pass form answers or free text. Call once for each action. The tag reuses its generated event ID when it retries delivery.

Supported short-code fields are form_id, attempt_id, step, step_id, status, result, method, plan, plan_id, content_id, content_type, item_id, and variant_id. Use the same step_id and item ID when different event names describe one operation's progress and outcome. Supported numeric fields are step_index, count, duration_ms, and value; currency accepts a three-letter code.

barua.consent(ok)

For sites running data-consent="required". Wire it to your consent banner: barua.consent(true) starts capture, and barua.consent(false) stops the recorder, clearing both the queue and the pixel's stored values; the only thing a refusal keeps is the refusal marker itself. Calling it repeatedly is safe.

The pixel loads async, so a banner that can fire before it's ready should guard the call (window.barua?.consent(true)) and repeat it on the consent tool's change event. Once a choice is stored, later pages need no call at all: the pixel reads it itself on load.

If your visitors are in Europe, read this one

By default the tag captures from the first pageview and writes its identifiers to the browser's localStorage. Unconfirmed activity batches expire after 24 hours, within a 1 MiB limit, and are removed after the server confirms them. Expired batches are deleted the next time the tag is allowed to run. Replay is excluded from this saved queue. That default is right for plenty of sites and wrong for some, and which one you are is a legal question rather than a technical one.

Across the EEA and the UK, putting anything on a visitor's device or reading it back needs their consent first, unless it is strictly necessary for something they asked for. Local storage counts, the same as a cookie: European regulators settled that question explicitly, and the fact that barua sets no cookies does not exempt it. So if your visitors are there, run consent mode:

<script async src="https://barua.ai/b.js" data-key="pk_…" data-consent="required"></script>

Nothing is captured and nothing is written to the device until your banner calls barua.consent(true). Wire that call to whatever consent tool you already run, and call barua.consent(false) when someone withdraws, which clears the stored values and keeps only the refusal.

The second switch is about contact details. barua picks up an email or a name at the moment a form or a popup actually sends, by default. That is a real collection made by you, and your own notice has to cover it. If it does not, or if you would rather hand identities over from your own code, turn the automatic pickup off:

<script async src="https://barua.ai/b.js" data-key="pk_…" data-auto-identify="off"></script>

One more thing worth a minute of thought. barua masks every input value and every piece of rendered text in the browser, but it deliberately keeps the context it needs to be useful: page titles, product names and prices, and what someone searched for on your site. On most sites that is harmless. If a page title would say that a person was reading about a medical condition, a debt, or a faith, that is a different category of data with a much higher bar.

Blocking the visible element is not enough for a title. The pixel drops a page title only when the <title> element itself, or something above it, carries the attribute, so a page that hides its body but leaves its title alone still sends the title. For a route like that, mark the title:

<title data-cg-block>Your order</title>

Or keep the tag off the route entirely, which is the safer answer when a whole section of the site is sensitive. data-cg-block on a body element still does its own job: the contents of that element are never recorded. The acceptable use policy is where this is spelled out, and the privacy page has the full detail of what is masked.

Single-page apps

Nothing extra to do. The pixel follows pushState and popstate navigations, and corrects the page title after your router settles. Call identify when a page restores or changes its signed-in account, and reset when it learns of a sign-out.

Getting access

Onboarding is a conversation today. Write chris@barua.ai, and a person answers.