Skip to content

Browser

The tag is the Qinto client as a <script> snippet. You paste it once into the <head> of your pages, and it binds window.qinto, opens one connection to your Qinto host and sends what visitors do. It is the quickest way to put a site on Blotout.

Use the tag when you can add a script to your pages and you do not build the site’s JavaScript yourself:

  • A content site, a store theme or a marketing site on a CMS, such as Webflow.
  • A site that loads scripts through a tag manager (which we highly do not recommend).
  • A site with several teams or vendors adding scripts, where one global window.qinto is the shared entry point.

Pick another surface when:

  • Your site runs on Shopify. The tag alone does not reach the checkout, so a store also needs a custom pixel. See Shopify.
  • You ship your own bundle. A single-page app, a framework app or a TypeScript project gets typed imports and control over load timing from Headless, the same client as an npm package.
  • The event happens on a server. A payment webhook, a refund or a scheduled job calls HTTP, with the server SDK or any HTTP client.

The tag and the package are built from one source, talk to the same edge and mint the same ids. A site can move from one to the other without losing a visitor.

Open your site in the Blotout platform and go to Settings → Capturing data. The Your snippet card shows it, and the Snippet step of onboarding shows the same code. You must deploy the site once before a snippet exists.

Until DNS for your own hostname is live, the card shows two snippets. Use one or the other, never both.

Snippet Host When to use it
First party domain Your mapped hostname, such as q.example.com Production. The visitor cookie belongs to your own domain, so browsers keep it and a returning visitor is recognised.
Qinto URL The site’s development URL on a Qinto hostname Testing before DNS is live. The cookie belongs to a Qinto hostname, which browsers treat as third party, so a visitor is not recognised between visits on Safari and in private windows.

The snippet looks like this, with your host in place of q.example.com:

<script>
(function(w,d,u,g){if(g in w){var o=w[g];
if(!(o&&o._q))console.error("window."+g+" is already in use");return}
var t={_q:1,q:[]};
Object.defineProperty(w,g,{value:new Proxy(t,{get:function(o,n){
if(typeof n!="string"||n=="then"||n=="ready"||n=="config"||n in o)return o[n];
return o[n]=function(){var a=arguments;var p=new Promise(function(res,rej){o.q.push([n,a,res,rej])});p.catch(function(){});return p}}}),writable:true,enumerable:true,configurable:true});
var s=d.createElement("script");s.async=true;s.src=u;s.dataset.qintoGlobal=g;
d.head.appendChild(s)})(window,document,"https://q.example.com/q.js","qinto");
</script>

Copy it from the platform rather than from this page. The platform fills in your host and, if you renamed the global, the name.

The inline part runs at once. It creates window.qinto as a queue: any method you call before the tag has loaded returns a promise and waits in order. Then it adds one async script, /q.js from your Qinto host.

/q.js carries three things in one response:

  1. The site’s configuration. The host, the capture settings and the list of installed apps with the consent category each one declares. The tag reads it from window.qinto.config.
  2. The client. The same code @blotout/qinto-sdk-browser publishes.
  3. The browser code of the apps installed on the site. With consent mode on, an app runs only when the visitor’s consent allows it.

When the client runs it opens a WebSocket to /open on your Qinto host, sends every queued call in order, and keeps the connection for the life of the page. Your Qinto host sets the visitor cookie on the first connection, a first-party cookie on your own domain. If the connection drops, the client reconnects with a backoff of up to 30 seconds, and a call made while the socket is down waits for the next open.

The response for /q.js is cached for five minutes in the browser and at the edge. After you install or remove an app, or change a setting on the Capturing data page, a page loaded within the next ten minutes can still run the old list. A page that is already open keeps what it started with until it reloads.

The tag has no options in the code you paste. Everything it does differently from site to site lives on the Settings → Capturing data page, so a change there needs no change to your pages.

Setting Default What it does
Page load under Automatic capture Off Records a PageView each time a visitor opens a page. Turn it on unless your page sends PageView itself, for example after a consent save settles, or when a Shopify pixel sends it. On and your own call together count each page twice.
JavaScript global under Your snippet qinto The window property the tag binds. Set it only when window.qinto is already taken on your page, or when two Blotout snippets run on the same page. The snippet changes to window.acme.track(...), and /q.js is requested with ?g=acme.
Recover visitors with your own cookie Off The name of a cookie your server sets, which Qinto reads to recognise a returning visitor for longer. See Server-side cookie.

Consent mode and Global Privacy Control are on Settings → Privacy & Security and change what the tag delivers, not how it loads. See Consent.

In a sandboxed frame, such as a Shopify custom pixel, window.location names the frame and not the page, and the site’s cookies do not reach the connection. Give the snippet’s holder a pageContext function. In the snippet you pasted, the line var t={_q:1,q:[]}; creates the holder, before the snippet adds the tag script. Add the function there:

var t={_q:1,q:[],pageContext:function(){return{url:shopPage.url,referrer:shopPage.referrer}}};

It has to be in that inline script. The tag loads as an async script and can run before any script that follows the snippet, and the client reads pageContext once, when it boots. The client calls the function on every connect and every call, so it must return at once. The function returns an object with these properties:

Property Required What it is
url Yes The absolute URL of the page the visitor sees. It names the page for the connection, the calls, the apps and query-parameter capture. If it is not an absolute URL, or the function throws, the client uses the frame’s own location and referrer instead.
referrer No The absolute URL of the page the visitor came from. Leave it off when there is none.
visitor No Continues the same visitor and visit on the next page. It goes to the connection only. Leave it off until the server has reported one in a qinto:connect event.
visitor.id Yes, when visitor is set The anonId from an earlier qinto:connect event.
visitor.session No The event’s session.id and session.startedAt joined with a dot. Without it the visitor continues, but a new visit starts.

A Shopify store uses a custom pixel built this way. Shopify has the code and the steps.

Every method returns a promise that settles when your Qinto host accepted the call. A call made before the tag loaded is queued, so you never have to wait for a load event.

Method What it does
track(event, properties?, options?) Records something the visitor did. Resolves to { eventId }. See Actions.
identify(traits?, id?) Merges traits into the visitor’s profile, and links id, an identifier of your own, to them. See User data.
alias(id) Links an identifier of your own to the visitor without touching traits.
consent(input) Saves the visitor’s consent choices. See Consent.
getConsent() The consent state as the server last sent it. Sends nothing.
getAnonId() The visitor’s anon_… id from the connection. Sends nothing.
getVisitor() { anonId, globalId, ip, geo }, asked from the server each time.
close() Stops reconnecting and rejects every call in flight. A page rarely needs it.
<script>
qinto.identify({ email: 'ada@example.com' }, 'crm-user-8412')
qinto.track('AddToCart', { currency: 'USD', value: 10.5 })
</script>

The tag also dispatches a qinto:connect event on window each time the connection opens, reconnects included. Its detail holds name, the global that connected, anonId, session with { id, isNew, startedAt }, and reconnect. It leaves out ip, geo and globalId, because every script on the page can read a window event. Use getVisitor() for those.

<script>
window.addEventListener('qinto:connect', function (event) {
var detail = event.detail
if (detail.session && detail.session.isNew) {
// the first page of a visit
}
})
</script>

A promise rejects with one of three errors. QintoTimeoutError means the call waited ten seconds with no answer. QintoProtocolError means the edge refused the call, or the socket closed while it was in flight; its message is the edge’s own. QintoError means window.qinto belongs to something else on the page. The error classes are not on window, so check error.name.

A page with a CSP must allow your Qinto host in two directives. script-src needs https://q.example.com for /q.js, and connect-src needs wss://q.example.com for /open. An https: source never matches a wss: URL, and a blocked socket leaves every call pending until it times out.

  • Paste the snippet in the <head> of every page, as high as you can. The queue exists from the moment the inline part runs, so a call further down the page never races the tag. A snippet at the end of <body> records a page view later and misses a visitor who leaves early.
  • Paste it once. A page that has the snippet ignores a second copy. Two snippets with different globals open two connections for one visitor.
  • Use the first party domain snippet in production. Finish DNS before you launch, and keep the Qinto URL snippet for testing.
  • Turn Page load on unless you send PageView yourself. If your consent banner learns the choice late and the first page view must reach browser apps, turn Page load off and call qinto.track('PageView') after qinto.consent settles. See Consent.
  • await a Purchase before you navigate. The promise settles when the edge accepted the event. A redirect before that can lose the call on a slow connection.
  • Send the order id with every Purchase. It becomes the event id, so the same purchase sent from the page and from your backend counts once. See Standard events.
  • Send consent before the first event, and save the visitor’s identifier with alias. With consent mode on, an event sent before the save reaches only necessary apps. An email sent only as a trait is stored and links nothing.
  • Do not rename the global without a reason. Every snippet, pixel and example assumes window.qinto. Rename it only when the name is taken or two Blotout sites share a page.
  • Turn on consent mode instead of wrapping the snippet in a consent check. Consent mode is off for a new site, and with it off every app receives every event. With it on, under Settings → Privacy & Security, an app outside necessary receives nothing until the visitor grants its category, and the tag can still record the choice itself. A tag that never loads cannot. See Consent.
  • Test with the browser’s network panel. A healthy page shows one /q.js response and one /open WebSocket on your Qinto host, and each qinto.* call resolves. Analytics → Real time in the Blotout platform lists the events the site received.