Website SDK
One script tag connects your website to The AI CMO: visits, conversions, and revenue flow into your warehouse, journeys can react to what people do on your site, and website messages from Journeys render on your pages.
Install
In the app, open your account menu and choose Website SDK. Add your
site by domain and you get a snippet to paste before </head>:
<!-- The AI CMO -->
<script async src="https://theaicmo.com/sdk/v1/tracker.js"></script>
<script>
window.AICMO = window.AICMO || [];
AICMO.push(['init', { siteId: 'lpo_your_site_id' }]);
</script>
That's the whole install. The script loads asynchronously and cannot block or break your page – even if analytics is unreachable, your site is unaffected.
To confirm it works, visit your site once, then refresh the Website SDK page. Each site shows "Receiving data – last event" with a timestamp once the first event lands, and the page banner switches to "SDK installed and receiving data".
What is tracked automatically
With no further code, the SDK captures pageviews (including single-page app navigation), visitors and sessions, and campaign attribution from tracked links and UTM parameters. Everything else – your business moments, conversions, revenue, identity – you declare explicitly with the calls below, so your analytics contains what you decided matters, not noise.
Session recording, heatmaps, and automatic click capture are switched off by design: they multiply event volume without adding decisions, and they are the first thing a privacy review flags.
Everything the SDK sends lands in your warehouse with an sdk_ prefix
on the event type, attributed to the visitor and – once they identify –
to the person.
Attribution: first click or last click
The SDK keeps a touch ledger per visitor: their first-ever arrival (source, medium, campaign, referrer, landing page, timestamp) is recorded once and never overwritten, and their most recent campaign or referral arrival updates alongside it. Direct return visits never erase a known source – the same "last non-direct click" convention the big analytics suites use. Both touches ride on every conversion and revenue event, stored in your own warehouse.
Which touch your reports credit is a per-brand setting: last click (the default) or first click. Because both touches are stored raw on every event, changing the model later never loses data – reports state the active model next to the numbers, and history keeps enough to be re-read under either. Attribution starts when the SDK is installed, and journeys that span devices connect once the person identifies on both.
Included analytics volume
Visitor analytics is included with your plan: from 500,000 up to 2M events a month depending on tier on Assistant, 5M on Manager, 20M on CMO. Conversions, identity, custom events, journeys, and revenue tracking are never counted against this – it meters only the behavioral layer (pageviews and sessions).
If a site outgrows its plan's volume, nothing breaks and nothing is lost silently: visitor analytics switches to sampling (1 in 10 visitors), you get an alert, and full detail returns when volume drops or the plan grows. First-party data – conversions, revenue, journey events – is never sampled.
The API
Call methods through the queue, which is safe to use before the script has loaded:
AICMO.push(['method', ...arguments]);
| Method | What it does |
|---|---|
init({ siteId, consent? }) | Starts the SDK. Pass consent: 'pending' to wait for a consent banner. |
track(eventName, properties) | Sends a custom business event – see below. |
identify(idOrEmail) | Tells the SDK who the visitor is – an email or your own customer ID. |
trackConversion(type, { value, currency, email }) | Records a conversion with revenue. |
trackPageView() | Manual pageview, for cases the automatic tracking can't see. |
optIn() / optOut() | Grant or withdraw consent at runtime. |
consentStatus() | Returns granted, denied, or pending. |
Custom events
Custom events are how your site's own business moments – a deposit, a booking, a quote request – reach the platform:
AICMO.push(['track', 'deposit', { value: 250, currency: 'USD' }]);
Or with zero JavaScript, declaratively on any element:
<button data-aicmo-event="deposit_click" data-aicmo-value="50">Deposit</button>
Two rules to know:
- Event keys must be registered first in Data → Events, and use lowercase letters, numbers, and underscores. Events with unregistered keys are not stored – but the Events tab shows keys it has seen so you can register them with one click.
- In the warehouse the event lands as
sdk_<key>(a registereddepositbecomessdk_deposit), and a journey with the matching custom-event trigger enrolls the person the next time the engine polls – within about a minute.
You can also define no-code capture rules in Data → Events: a CSS selector or URL match that fires an event without touching your site's code, reading the value from the page or a cookie.
Identifying people
Anonymous visitors get a random visitor ID. The moment you know who they are, tell the SDK:
AICMO.push(['identify', 'jane@acme.com']); // email
AICMO.push(['identify', 'player-48213']); // or your customer ID
Identification is what connects website behavior to a real customer: their events join their profile, journeys can enroll them, and person-targeted website messages can reach them. If your site already sets a login cookie, configure it as the identity cookie in Data → Events and the SDK identifies people automatically – no code change.
Revenue
Send revenue either through a conversion or on a custom event:
AICMO.push(['trackConversion', 'signup', { value: 49, email: 'jane@acme.com' }]);
AICMO.push(['track', 'deposit', { value: 250, currency: 'USD' }]);
The value becomes the event's amount in your warehouse, and feeds
journey goals, revenue reporting, and What's Working.
Website messages
When a journey publishes a website message, the SDK renders it on your site – as a banner, card, or modal – styled to the message, isolated from your page's CSS, and shown at most once per day per message. Person-targeted messages appear only to the identified person they were written for. Impressions, clicks, and dismissals are tracked automatically and feed the journey's performance numbers.
The inbox
Messages can persist in an on-site inbox instead of – or alongside – the one-shot popup. When a visitor has inbox messages, the SDK shows a small launcher in the corner with an unread badge; opening it lists their messages, newest first, and marks them read. Visitors with no messages never see the launcher at all.
Three things put messages there: the journey Website step (choose "Popup + inbox" or "Inbox only" on the step), promos – when a promo with a customer message is credited to someone, that message lands in their inbox automatically, with its terms attached – and the Inbox page (sidebar), where you manage the whole channel.
Managing the inbox
On the Inbox page every message shows opens, clicks and click-through rate, counted per person rather than per page load, next to who it targets, where it came from, and its state – Live, Scheduled, Draft or Ended. You can compose a new message, save it as a draft, schedule it to appear from a date and time, pin it to the top of the panel, edit or duplicate it, end it early, or delete it. A message leaves the inbox on its own when the period you chose runs out.
Who a message goes to: every visitor, one of your saved audiences, one person by email, or one person by your own customer or player id – useful when people sign in with an account number rather than an email address. Person-targeted messages are checked against your suppression lists before they send, and audience and site-wide messages are checked again for each reader, so someone who has opted out or self-excluded sees no marketing. Messages marked as Service – an account, payment or security notice – still reach them, and are shown first in their inbox. Anyone on a blacklist set to "Blocks everything" sees nothing at all.
What a message can contain
Beyond a headline, message and image:
- Up to three buttons, each with its own link and treatment – solid, outline, or a plain link. A button with no link simply acknowledges the message and settles the row, which is what you want for "Got it". Each button is tracked separately, so you can see whether people clicked "Claim bonus" or "See terms".
- A code to copy – shown as a tap-to-copy chip, for a bonus or discount code.
- A label above the headline – a short word like BONUS in your accent colour.
- The deadline, shown as "Ends in 2 days" and read from when the message actually expires, so the card and the truth can never disagree.
- Terms, as text or a link.
How a message looks
The Design section sets the defaults for the whole site – what a card looks like (layout, image shape, button treatment) and what the panel looks like (accent, position, launcher, font, text size, corners, language, title). A sample card beside the controls shows the defaults as you change them.
Each message can then override any of it in its own Design tab: layout, image shape, its own accent, background and text colours, and an accent edge for something important. Anything you leave alone follows the site-wide defaults, so a message only looks different where you meant it to. Where a card carries two buttons, the second one steps down a level so two solid buttons never compete. The preview beside the form is rendered by the same code your visitors run, so what you see is what they get.
Messages can also be translated. The panel itself already speaks ten languages; the Translations tab translates the message inside it – label, headline, body and every button label – and a reader whose language you have not translated sees your original copy.
The Inbox page also documents the two public endpoints for rendering the same inbox natively inside your own logged-in UI instead of the SDK widget.
Support chat
The same messenger can carry a Support tab: visitors write to your team directly from the site, and the conversation lands in the Helpdesk bound to the customer when they're identified. Replies from your team appear in the visitor's chat within seconds, and when a conversation is resolved the visitor is asked for a 1–5 rating in their own language. Enable it from the SDK console's Support chat toggle; answering happens in Helpdesk.
Personalization
The SDK can change what your site says per visitor. Mark an element with
data-aicmo-slot="hero-title" (or give a CSS selector on the rule), then
create rules under Audience → Audiences → Personalization: what to show,
and who sees it – everyone, or one of your saved audiences.
Higher-priority rules win; identified visitors on your exclusion lists
are never personalized. Variants are plain text by default; HTML variants
exist behind a per-site switch you have to turn on deliberately.
Rules can target devices – all, mobile (phones and tablets), or desktop – so the shorter headline shows only where screens are small.
Rules apply on page load and on navigation in single-page apps. Applied
elements carry a data-aicmo-personalized attribute so your team can see
what changed and why.
Each rule reports how often it applied and, for identified visitors, the revenue those people generated after first seeing it – the rule's row under Personalization reads both, so a rule is never a guess.
A/B testing a rule
When creating a rule you can switch on A/B test this change: write a second version, choose the split (say 50/50), and set a holdout – a slice of visitors who keep seeing your original page but still count as exposed. Every visitor lands in the same arm on every visit, and the rule's row then reads revenue per arm – A vs B vs holdout – with the leader highlighted. The holdout answers the question that matters: does the change beat doing nothing?
Customers who are inside a journey with a control group are left out of rule A/B tests automatically – they see your original content and are not counted. One person is never part of two experiments at once, so journey uplift numbers and rule test numbers both stay honest.
Web push notifications
Turn browsers into a push channel – no third-party push provider needed. Browser subscriptions are off by default: nothing is offered or collected from visitors until you switch them on for the site (Website SDK page → Web push notifications). Once on, two steps:
-
Host a one-line service worker at your site root, saved as
/aicmo-sw.js(browsers require this file to live on your own domain):jsimportScripts('https://theaicmo.com/sdk/v1/push-sw.js'); -
Call
AICMO.registerPush()from your own button – for example an "enable notifications" toggle in your account area. Browsers punish permission prompts the visitor didn't ask for, so the SDK never prompts on its own.
That's it. Subscribed browsers become reachable by the Push step in
Journeys. Subscriptions are matched to the person once you identify
them – someone who subscribes anonymously and logs in later becomes
addressable automatically. AICMO.unregisterPush() removes this
browser's subscription.
This is mobile push without an app: on Android phones, notifications arrive like any app's – even with the browser closed. On iPhone they arrive once the visitor has added your site to their home screen (Apple requires your site to ship a web app manifest for that). Desktop Chrome, Edge, and Firefox work everywhere. Blacklists and self-exclusion are enforced before every send.
Mobile apps (React Native)
Your React Native app gets the same three things through
@aicmo/react-native – no dependencies, and no third-party push
provider:
- Events and identity –
identify,track, andtrackConversionland in your warehouse exactly like the website SDK's. - Push, delivered directly – upload your Firebase service account
on the Website SDK page (verified live, stored encrypted) and The AI
CMO sends notifications straight through your own Firebase project:
Android natively, iOS via Firebase's APNs integration. Your app
passes its FCM token with
registerPushToken, and the Push step in Journeys reaches the device – matched to the person afteridentify.
The package README walks through setup for bare React Native and Expo. Blacklists and self-exclusion are enforced before every send, on this route like every other.
Survey pages
Your hosted survey pages can carry your site's SDK too: in a survey's
Fill page & branding → Tracking section, pick the site and survey
visits land in your analytics, with a survey_completed event fired on
submit. GA4 and Meta Pixel IDs are supported there as well – IDs only,
never scripts. Owner previews are never tracked.
Consent and privacy
- Consent gating: start with
consent: 'pending'and calloptIn()when the visitor accepts. Until then, nothing runs. AnoptOut()is remembered and the SDK stays silent on future visits. - Do Not Track is respected by default.
- The SDK sets no cookies. Visitor state lives in localStorage on your own domain. It only reads cookies you explicitly configure for identity or value capture.
- Session recordings mask every input field by default.