Actions
Everything your site tells Qinto is an action. identify and alias say who the visitor is, and
User data covers them. track says what the visitor did, and this page covers it.
A track call carries three things:
- The event name. Any non-empty string. The eleven names on Standard events are the ones every app understands without configuration.
- The properties. A JSON object with the payload of the event. Standard events have a documented shape; a custom event has whatever shape you give it.
- The options. Your own event id, or the apps and app installs that receive the event. Most calls need neither.
Qinto fans the event out to every installed app that is eligible for it, and each app transforms it for its own destination. Adding or removing a destination is a change in the Blotout platform, never in this call.
Send an event
Section titled “Send an event”Every example on this page has a tab per surface. Pick one tab and the whole page follows.
The snippet from your site’s Snippet page in the Blotout platform binds window.qinto. Call qinto.track from any
script on the page. A call made before the tag has loaded is queued and sent in order once the connection is open.
<script> qinto.track('AddToCart', { currency: 'USD', value: 10.5, contents: [{ id: 'sku-8831', quantity: 1, value: 10.5 }] })</script>The call returns a promise that resolves to { eventId } once the edge has accepted the event. The tag records
PageView for you. See Browser for the snippet and its options.
@blotout/qinto-sdk-browser is the same client as the
tag, as an npm package for a bundled site. Call init once where your app starts, then import track anywhere.
import { init, track } from '@blotout/qinto-sdk-browser'
init({ destination: 'https://q.example.com' })
const { eventId } = await track('AddToCart', { currency: 'USD', value: 10.5, contents: [{ id: 'sku-8831', quantity: 1, value: 10.5 }]})The promise settles when the edge accepted the event, so you can await a Purchase before you navigate away.
Unlike the tag, the package does not record the page load. track('PageView') is yours to call. See
Headless for init and its options.
@blotout/qinto-sdk-server sends the same events from a
backend, a payment webhook or a scheduled job. The first argument names the person by an anon_… id or any
identifier of your own, such as an email, a phone number or a customer id. It needs an edge token with the
Track events permission from your site’s Developer tools → Edge tokens page.
import { createClient } from '@blotout/qinto-sdk-server'
const qinto = createClient({ destination: 'https://q.example.com', token: process.env.QINTO_EDGE_TOKEN })
const { eventId } = await qinto.track('crm-user-8412', 'AddToCart', { currency: 'USD', value: 10.5, contents: [{ id: 'sku-8831', quantity: 1, value: 10.5 }]})Tracking never creates a person. An identifier the site has never seen fails with HTTP 404, so bind it first with
identify or alias, or send the anon_… id. An event that happened earlier, or outside the browser, carries its own
time and network in options.observed.
Any language can call POST /v1/track on your site’s Qinto host, with the edge token as a bearer token and a JSON
body. This is what the server SDK sends.
curl -X POST https://q.example.com/v1/track \ -H 'Authorization: Bearer qwat_…' \ -H 'Content-Type: application/json' \ -d '{ "identifier": "crm-user-8412", "event": "AddToCart", "eventId": "3f2a9c1e-7b64-4d0e-9a51-2c8e6f0d4b17", "properties": { "currency": "USD", "value": 10.5, "contents": [{ "id": "sku-8831", "quantity": 1, "value": 10.5 }] } }'| Field | Required | What it is |
|---|---|---|
identifier |
Yes | The person: an anon_… id or an identifier of your own. Internal gid_… ids are rejected. |
event |
Yes | The event name. |
eventId |
Yes | The id destinations deduplicate on. The SDKs mint one for you; over HTTP you supply it. For Purchase, use the order id. |
properties |
No | The event payload. |
observed |
No | occurredAt, offline, network, page, session and replay for an event that did not happen just now, or that Qinto could not observe. |
apps |
No | { "mode": "only" | "except", "slugs": [] } to pick the apps that receive the event. See Send an event to one app. |
installs |
No | { "mode": "only" | "except", "installIds": [] } to pick the app installs that receive the event. See Send an event to one app. |
The response is { "ok": true, "id": "act_…" }, where id is the id of the accepted action. An unknown identifier
returns 404 with { "error": "unknown_user" }, and an invalid body returns 400 with invalid_body and the failing
fields. See HTTP for the other endpoints.
Standard events
Section titled “Standard events”Prefer a standard event whenever one fits. The eleven names are PageView, ViewContent, AddToCart,
RemoveFromCart, InitiateCheckout, AddShippingInfo, AddPaymentInfo, Purchase, Subscribe, Search and
Lead. An app that handles a destination maps each of them to that destination’s own event without any setup on your
side, so one AddToCart reaches every ad platform, CRM and warehouse you install, in the shape each one expects.
Standard events documents every name with its parameters and an example per surface. Two rules from that page matter for every call:
- Every parameter is optional unless its table marks it required, and you can add properties of your own next to the standard ones. An app ignores a property it does not know.
- The payload decides what the graph records, not the name. The identity graph stores a conversion when the
payload carries a finite numeric
valueand a non-emptyorderId. Of the eleven standard payloads onlyPurchasehas both. The order id is also the event id, so destinations deduplicate a purchase sent from both the page and your backend.
Custom events
Section titled “Custom events”The event name is not validated against a list, so track sends any event you name. Use a custom event for something
the standard vocabulary has no word for: a video that played to the end, a filter that was applied, a feature that was
switched on, a form step that was completed. The properties are yours as well, any JSON object, nested as deep as you
need.
<script> qinto.track('VideoCompleted', { videoId: 'onboarding-tour', durationSeconds: 94, quality: '1080p', chapters: [{ id: 'intro', watched: true }, { id: 'pricing', watched: false }] })</script>import { track } from '@blotout/qinto-sdk-browser'
await track('VideoCompleted', { videoId: 'onboarding-tour', durationSeconds: 94, quality: '1080p', chapters: [{ id: 'intro', watched: true }, { id: 'pricing', watched: false }]})import { createClient } from '@blotout/qinto-sdk-server'
const qinto = createClient({ destination: 'https://q.example.com', token: process.env.QINTO_EDGE_TOKEN })
await qinto.track('crm-user-8412', 'TrialConverted', { plan: 'pro', seats: 12, billing: 'yearly' })curl -X POST https://q.example.com/v1/track \ -H 'Authorization: Bearer qwat_…' \ -H 'Content-Type: application/json' \ -d '{ "identifier": "crm-user-8412", "event": "TrialConverted", "eventId": "9c4e2b70-1f3a-4a8d-b6e5-0d7c2a9f1e34", "properties": { "plan": "pro", "seats": 12, "billing": "yearly" } }'What happens to a custom event depends on the apps you installed:
- Delivery follows the app’s manifest. An app can list the event names it handles. An app with a list never receives a name that is not on it, so a custom event reaches such an app only after its developer adds the name. An app without a list receives every event. Consent and the install’s geo filter apply on top, as for a standard event.
- Each app decides what to do with it. A destination app usually forwards an unknown name as a custom event of its destination, with your properties as-is, or ignores it. Read the app’s listing in the App store to see which. Your own app can handle any name you choose.
- The graph does not record it unless the payload carries both a numeric
valueand a non-emptyorderId. A custom event with both is a conversion, exactly likePurchase.
Pick names the way the standard events are named: PascalCase, short, and the same spelling in every call. Changing
a name later means changing every app configuration that matches on it.
Send an event to one app
Section titled “Send an event to one app”By default every eligible app install receives every event. Two options narrow that for one call:
appsnames apps by slug. It reaches every install of each listed app, including an install added later, so a second Meta Pixel install needs no change in your code.installsnames single installs by id, when one install of an app must receive the event and another must not.
Both take the same two modes:
| Mode | Who receives the event |
|---|---|
only |
The listed apps or installs, and nobody else. |
except |
Every eligible app or install except the listed ones. |
Use them when one event belongs to one destination: a server-side Purchase that only your warehouse should see
because the page already sent it to the ad platforms, or a Lead that only one of two CRM installs should receive.
<script> qinto.track('Purchase', { value: 129, currency: 'EUR' }, { apps: { mode: 'only', slugs: ['facebook'] } })</script>import { track } from '@blotout/qinto-sdk-browser'
await track('Purchase', { value: 129, currency: 'EUR' }, { apps: { mode: 'only', slugs: ['facebook'] } })import { createClient } from '@blotout/qinto-sdk-server'
const qinto = createClient({ destination: 'https://q.example.com', token: process.env.QINTO_EDGE_TOKEN })
await qinto.track('crm-user-8412', 'Purchase', { value: 129, currency: 'EUR' }, { apps: { mode: 'only', slugs: ['facebook'] } })curl -X POST https://q.example.com/v1/track \ -H 'Authorization: Bearer qwat_…' \ -H 'Content-Type: application/json' \ -d '{ "identifier": "crm-user-8412", "event": "Purchase", "eventId": "6a0d3e8f-2c1b-4e9a-8f7d-5b4c3a2e1d09", "properties": { "value": 129, "currency": "EUR" }, "apps": { "mode": "only", "slugs": ["facebook"] } }'installs takes the same place in the call, with install ids instead of slugs:
{ installs: { mode: 'only', installIds: ['0c1f6b7e-4a0a-4f0c-9d8e-2b6a1f3e5c70'] } }The rules can only narrow. When you pass both, an install receives the event only if both rules allow it:
{ apps: { mode: 'only', slugs: ['facebook'] }, installs: { mode: 'except', installIds: [...] } } reaches every
Meta Pixel install but the listed ones. An app or install that consent, Global Privacy Control or its geo filter
already excludes stays excluded when you list it under only, and an app never receives an event it emitted itself.
A slug or id that names nothing on the site is ignored, and an empty only list reaches no app while Qinto still
stores the event.
Find an app slug
Section titled “Find an app slug”A slug is the fixed name of an app, such as facebook. It is the same on every site and never changes. Two places
list the slugs of the apps on your site:
- The Platform API. List a site’s apps returns every app on a site with its
slug. It takes a team API token from Team → API tokens; see API tokens. - Your agent. The Qinto MCP server’s
qinto_list_site_appstool returns the same list. See Getting started with an agent.
Find an install id
Section titled “Find an install id”An install id is the UUID of one installation of one app on your site. An app installed once has one; an app installed several times, for example one CRM install per region, has one per install. Two places list them:
- The Platform API. List an app’s installs returns every install of an app
on a site with its
appInstallIdand label. It takes a team API token from Team → API tokens; see API tokens. - Your agent. The Qinto MCP server’s
qinto_list_app_installstool returns the same list. See Getting started with an agent.
An install id does not change for the life of the install, so it is safe to keep in your configuration. Removing the install and adding it again creates a new id.
Options
Section titled “Options”The third argument of track in the browser, and the fourth in the server SDK, is an options object. Over HTTP the
same keys sit at the top level of the body.
| Option | Surface | What it does |
|---|---|---|
eventId |
All | Your own event id, when the id has to match a record in your system. A supplied id always wins over the one the SDK would mint or take from orderId. |
apps |
All | The app rule described above. |
installs |
All | The install rule described above. |
observed |
Server, HTTP | occurredAt, offline, network, page, session and replay for an event that did not happen just now, or that Qinto could not observe. The browser fills these from the page. |
Both SDKs return the event id, so you can log it next to your own record. Destinations deduplicate on it, and an app
that stores the event keeps it. The HTTP response carries only the action id, so keep the eventId you sent.