Websites API
Create and manage websites for visitor identification and tracking.
Required Headers
| Header | Description |
|---|---|
x-access-key | Your Warm AI API key |
x-user-id | Your Warm AI user ID |
x-idempotency-key | Unique key to prevent duplicate requests |
Content-Type | application/json |
Recommended: Link ICPs to Your Website
For better prospect matching and visitor scoring, link your Ideal Customer Profiles (ICPs) to your website when creating it. This ensures visitors are automatically scored against your target profiles.
{
"action": "create_website",
"domain": "example.com",
"name": "My Website",
"icp_ids": ["icp_abc123", "icp_def456"]
}Installing the Tracking Script
After creating a website, add the following script to the <head> tag of your site:
<script src="https://assets.warmai.uk/warm.js" data-id="YOUR_TRACKING_ID" async></script>Replace YOUR_TRACKING_ID with the tracking_id returned when you create a website.
For details on what warm.js captures and how to configure it, see Tracking. The public compliance disclosure (lawful basis, sub-processors, visitor opt-out) is at getwarmai.com/tracker — share with your legal/security team or auditors.
Using WordPress or a site builder? Rich-text editors, optimization plugins, and consent managers can silently break the snippet. See the troubleshooting guide below if you don’t see sessions landing within a few minutes of a page view.
Troubleshooting install issues
If tracking isn’t working after you’ve installed the snippet, one of the following is usually the cause.
Smart quotes (WordPress, Elementor, Gutenberg rich-text)
Rich-text editors in WordPress, Elementor, and the Gutenberg visual editor convert straight quotes (" ") into curly “smart quotes” (“ ”) when you paste code. Browsers can’t parse curly quotes as attribute delimiters, so the script silently fails to load.
Fix: Paste the snippet into a plain-text block — for example:
- Gutenberg’s Custom HTML block
- A header/footer injection plugin like WPCode or Insert Headers and Footers
- Directly in your theme’s
header.php
After saving, view the page source (view-source:yoursite.com) and confirm the src and data-id attributes use straight quotes.
WP Rocket (script lazy-loading)
WP Rocket wraps third-party scripts in type="text/rocketlazyloadscript", deferring execution until the first user interaction (scroll, click, hover, etc.).
For visitor identification, this is usually acceptable — engaged visitors naturally trigger an interaction, while pure bounces go uncaptured. If that trade-off is fine for your use case, no action needed.
If you want every page load tracked regardless, in WP Rocket → File Optimization → JavaScript → Excluded JavaScript Files, add:
assets.warmai.uk/warm.jsSave and clear cache.
Autoptimize, WP-Optimize, and similar minifiers
JS combine/minify plugins can break async loading. Exclude assets.warmai.uk/warm.js from JavaScript optimization in the plugin’s settings.
Cloudflare Rocket Loader
Rocket Loader automatically defers third-party scripts via async injection. Either:
- Add
data-cfasync="false"to the script tag, or - In the Cloudflare dashboard → Speed → Optimization → Rocket Loader, add
assets.warmai.ukto the ignored URLs.
Consent managers (CookieYes, Cookiebot, OneTrust)
Consent managers block scripts categorised as analytics or tracking until a visitor accepts cookies — and warm.js falls into that category. warm.js sets a first-party warm_device cookie (a 1-year UUID used for cross-session visitor identification) and captures form-engagement signals, so it must be treated as an analytics / statistics script, not as a necessary or functional one.
To keep tracking working after consent is granted, add assets.warmai.uk/warm.js to your consent manager as an analytics script and ensure it fires only after the visitor accepts. Most platforms (Cookiebot, OneTrust, Termly) handle this automatically once the script is tagged correctly.
See the Cookies & Consent guide for a full walkthrough including a sample Cookiebot configuration.
Content Security Policy (CSP)
If your site serves a Content-Security-Policy header, allow:
script-src:https://assets.warmai.ukconnect-src:https://track.getwarmai.comandhttps://muagykdazutcjpapkcer.supabase.co
Single-page apps (React, Next.js, Vue)
warm.js hooks into the History API (pushState, replaceState, popstate), so client-side navigation is tracked automatically. No extra work needed. If you’re loading the script inside a component instead of in <head>, make sure it runs exactly once per page load (not on every mount).
Verifying your install
- Open your site in a new tab with DevTools → Network open.
- Filter for
warm.js— the request should return 200 OK. A 404 means thesrcURL is wrong (it’swarm.js, nottracker.js). - Interact with the page (scroll, click a link).
- Filter for
/api/trackortracking-event— you should see POST 200 responses. - Check your Warm AI dashboard — sessions appear within ~30 seconds.
If the Network tab shows no warm.js request at all, the snippet isn’t executing (check smart quotes, WP Rocket, or Rocket Loader). If warm.js loads but no /api/track POST fires, check CSP or a consent manager blocking the connect-src.
Endpoints
Create Website
POST https://api.warmai.uk/api-website-action
Creates a new website for visitor identification.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | Must be "create_website" |
domain | string | Yes | The domain of your website (e.g. example.com) |
name | string | No | A display name for the website |
icp_ids | string[] | No | Array of ICP IDs to link for visitor scoring |
Example Request
{
"action": "create_website",
"domain": "example.com",
"name": "My Marketing Site",
"icp_ids": ["icp_abc123"]
}Response Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique website ID |
domain | string | The website domain |
name | string | Display name |
tracking_id | string | ID used in the tracking script |
status | string | Website status |
created_at | string | ISO 8601 timestamp |
linked_icp_ids | string[] | ICP IDs linked to this website |
Example Response
{
"id": "ws_a1b2c3d4",
"domain": "example.com",
"name": "My Marketing Site",
"tracking_id": "trk_x9y8z7",
"status": "active",
"created_at": "2026-03-19T12:00:00Z",
"linked_icp_ids": ["icp_abc123"]
}List Websites
POST https://api.warmai.uk/api-website-action
Retrieve all websites associated with your account.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | Must be "list_websites" |
user_id | string | No | Filter websites by a specific user ID |
Example Request
{
"action": "list_websites"
}Response Fields
| Field | Type | Description |
|---|---|---|
websites | array | List of website objects |
websites[].id | string | Unique website ID |
websites[].domain | string | The website domain |
websites[].name | string | Display name |
websites[].tracking_script_id | string | ID used in the tracking script |
websites[].status | string | Website status (active, paused, archived) |
websites[].created_at | string | ISO 8601 timestamp |
websites[].linked_icp_ids | string[] | ICP IDs linked to this website |
Example Response
{
"websites": [
{
"id": "ws_a1b2c3d4",
"domain": "example.com",
"name": "My Marketing Site",
"tracking_script_id": "trk_x9y8z7",
"status": "active",
"created_at": "2026-03-19T12:00:00Z",
"linked_icp_ids": ["icp_abc123"]
},
{
"id": "ws_e5f6g7h8",
"domain": "blog.example.com",
"name": "Company Blog",
"tracking_script_id": "trk_w6v5u4",
"status": "active",
"created_at": "2026-03-18T09:30:00Z",
"linked_icp_ids": []
}
]
}How Tracking Works
When a visitor browses your website:
- warm.js captures page views, scroll depth, and session duration
- On session end (tab close or 30 min inactivity), identification runs automatically
- The visitor’s IP is checked against our databases and multiple paid providers
- VPN, hosting, and ISP traffic is filtered at zero cost
- Business matches are POSTed to your webhook URL as a signed payload
warm.js sets a first-party warm_device cookie for cross-session identification. UK and EU sites must obtain consent before the script runs — see Cookies & Consent.
Webhook Delivery
Configure your webhook URL in your API key settings. Every visitor session triggers a webhook to your endpoint.
Headers
| Header | Description |
|---|---|
X-Warm-Signature | HMAC-SHA256 signature for payload verification |
X-Warm-Timestamp | Unix timestamp of the event |
X-Warm-Event | visitor_identified or visitor_not_identified |
Content-Type | application/json |
Visitor Identified
Sent when a business visitor is successfully identified.
{
"identified": true,
"ip_address": "144.9.12.81",
"session_id": "uuid",
"identification_id": "uuid",
"tracking_domain": "yoursite.com",
"tracking_website_id": "uuid",
"session": {
"started_at": "2026-04-14T10:00:00Z",
"ended_at": "2026-04-14T10:05:30Z",
"duration_seconds": 330,
"page_count": 4,
"referrer": "https://google.com",
"pages": [
{ "url": "https://yoursite.com/", "path": "/", "title": "Home", "scroll_depth": 80, "duration_seconds": 45 },
{ "url": "https://yoursite.com/pricing", "path": "/pricing", "title": "Pricing", "scroll_depth": 100, "duration_seconds": 120 },
{ "url": "https://yoursite.com/features", "path": "/features", "title": "Features", "scroll_depth": 60, "duration_seconds": 90 },
{ "url": "https://yoursite.com/contact", "path": "/contact", "title": "Contact", "scroll_depth": 40, "duration_seconds": 75 }
]
},
"identification_type": "individual",
"identified_at": "2026-04-14T10:05:30Z",
"company": "Goldman Sachs",
"domain": "goldmansachs.com",
"traffic_type": "business",
"confidence": 0.91,
"confidence_level": "confirmed",
"individual": {
"linkedin_name": "Sarah Chen",
"linkedin_title": "Managing Director",
"linkedin_company": "Goldman Sachs",
"linkedin_url": "https://linkedin.com/in/sarachen"
},
"company_data": {
"name": "Goldman Sachs",
"domain": "goldmansachs.com",
"industry": "Financial Services",
"employee_count": "10001+",
"location": "New York, US",
"linkedin_url": "https://linkedin.com/company/goldman-sachs"
},
"decision_makers": [
{
"name": "Jane Doe",
"title": "CTO",
"linkedin_url": "https://linkedin.com/in/janedoe",
"seniority": "c_suite"
}
]
}Visitor Not Identified
Sent when a visitor could not be matched to a business (residential ISP, VPN, etc.).
{
"identified": false,
"ip_address": "81.246.16.178",
"session_id": "uuid",
"tracking_domain": "yoursite.com",
"tracking_website_id": "uuid",
"reason": "Residential ISP",
"session": {
"started_at": "2026-04-14T09:45:00Z",
"ended_at": "2026-04-14T09:46:20Z",
"duration_seconds": 80,
"page_count": 2,
"referrer": "https://linkedin.com",
"pages": [
{ "url": "https://yoursite.com/", "path": "/", "title": "Home", "scroll_depth": 40, "duration_seconds": 50 },
{ "url": "https://yoursite.com/about", "path": "/about", "title": "About", "scroll_depth": 20, "duration_seconds": 30 }
]
}
}Only business identifications are charged credits. VPN, hosting, and ISP traffic is detected and never billed.
Verifying Webhook Signatures
Verify the HMAC-SHA256 signature to ensure the webhook is from Warm AI:
const crypto = require('crypto');
function verifyWebhook(payload, signature, timestamp, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${JSON.stringify(payload)}`)
.digest('hex');
return signature === expected;
}