1.2.12
August 21st, 2026
- Fix The tracking rate limiter never expired its counters, so an IP stayed on 429 forever -
ThrottleTrackingincremented first and set a TTL after:Cache::increment($key)on a key that does not exist yet makes the key itself on a file cache store (FileStore::increment()falls back toput($key, 1, 0), andexpiration(0)means9999999999, not "now"), after whichCache::remember()finds a filled key and sets no TTL at all. The counter then only ever went up. After 120 requests that IP was blocked permanently and every event it sent was dropped, until someone cleared the cache - which reset it rather than fixing it, so the 429 came straight back. A database cache store happened to escape this, becauseDatabaseStore::increment()returnsfalseon a missing key. Both windows now run through Laravel'sRateLimiter, which writes the key with its TTL before incrementing, and whose:timerkey lets a counter without a window reset itself - Fix The rate limit was trivial to sidestep, and could be spent on someone else's behalf -
getClientIp()readX-Forwarded-For,CF-Connecting-IPandX-Real-IPstraight off the request without any trusted-proxy check, so a client could hand itself a fresh bucket per request, or fill the bucket of an IP it does not own. It also disagreed with the IP the tracking itself records:TrackVisitor,BlacklistServiceandTrackingControllerall use$request->ip(). The middleware now does too. Behind a reverse proxy (Cloudflare, a load balancer), configure trusted proxies - see Cloudflare Configuration in the README; without it every visitor arrives on the proxy's IP and shares one bucket - [changed] The limits are configurable and the hourly one is higher -
tracking.rate_limit.per_minute(KAI_TRACKING_RATE_LIMIT_PER_MINUTE, default 120) andtracking.rate_limit.per_hour(KAI_TRACKING_RATE_LIMIT_PER_HOUR, default 1000, up from a hard-coded 500). Set either to0to disable that window. The tracker batches events, so one visit costs 5-15 requests: 500 an hour is tight for an address that carries more than one visitor, such as office NAT or a mobile carrier, while the per-minute window still catches an actual flood - [changed] A 429 now carries
Retry-After, along withX-RateLimit-LimitandX-RateLimit-Remaining. The JSON body is unchanged - [changed] The cache keys were renamed from
kai:tracking:{ip}:minute|hourlytokai-personalize:track:{ip}:minute|hour. This is deliberate: the old counters sit in the cache with an expiry in the year 2286, and reusing the names would inherit them. Runphp artisan cache:clearafter upgrading to clear out those dead entries
1.2.11
November 29th, -0001
- N/A Changelog not available.
1.2.10
August 19th, 2026
- Fix The tracker script blocked page rendering -
{{ kai:track }}wrote a bare<script src>, so the parser stopped until the script had been fetched and run. It now carriesdefer. Tracking does not start any later for it:init()already waits for DOM-ready either way - Fix The tracker was served by PHP instead of the webserver - every visitor paid a full framework boot for an 8 KB static file, and occupied a PHP worker while doing so. Measured on a local machine: ~390 ms to first byte, against ~12 ms for the same kind of file served off disk. The script is now published to
public/vendor/kai-personalize/jsand linked from there. Runphp artisan vendor:publish --tag=kai-personalize-assets --forceafter upgrading; without it the addon falls back to the old route and keeps working, just slowly - [changed] The published tracker URL carries a
?v=version query, so an upgrade reaches returning visitors immediately - [changed] The fallback route no longer sends
immutable- it sat on a URL with no version in it, which left returning visitors on a stale tracker for a day after every upgrade. It is nowpublic, max-age=3600 - [changed] Dropped an unused
Visitorimport fromKaiTrack
1.2.9
August 19th, 2026
- Fix Tracking endpoint returned 419 Page Expired - the tracker POSTs to
/!/kai-personalize/track, which runs in thewebmiddleware group and therefore through CSRF verification, while neithertracker.jsnorsendBeaconsends a token. No tracking event has ever arrived on a site that did not work around this. The route now exempts itself fromValidateCsrfToken, so the host application needs no setup at all - [breaking] The HMAC signature layer is gone -
KAI_TRACKING_SECRET,tracking.signature_secretandtracking.signature_ttlare no longer read, andTrackingSignatureServiceis removed. The layer was never finished on the client side: the controller demandedsignature,timestampandnonce, but no tracker version ever sent them, so a filledKAI_TRACKING_SECRETsilently rejected every event with a 403. It was also a weaker reimplementation of CSRF - no session binding, a 300s TTL, and unusable fromsendBeaconon page unload. Requests are guarded by the origin/referer check and the rate limits instead. RemoveKAI_TRACKING_SECRETfrom your.env- it is a dead key - [breaking]
{{ kai:tracking }}no longer returns signature data - it now returnsurlandenabled.{{ kai:tracking:signature }}is removed - [changed] The README's CSRF instructions are obsolete - earlier versions told you to add a
validateCsrfTokens(except: …)rule tobootstrap/app.php. That rule can be removed. It never worked as written either: the documented patternkai-personalize/trackmisses Statamic's action prefix, so the real path!/kai-personalize/tracknever matched it - [changed] Dropped the
versionfield frompackage.json- it had drifted to 1.2.1 and served no purpose (the package is private and never published), leavingServiceProvider::VERSIONand the git tag as the only version sources.TRACKER_VERSIONintracker.jsstays at 1.2.5; the script itself is unchanged in this release
1.2.8
August 15th, 2026
- Fix Tracking crashed on empty UTM parameters -
?utm_term=(as Google Ads appends) producedColumn 'attribute_value' cannot be null, which aborted the rest of the request's tracking: language, device attributes, geolocation and the page view were all silently lost. Empty and non-string values are now skipped - Fix Attribute writes no longer accept empty values -
Visitor::setVisitorAttribute()rejects null, empty strings and empty arrays, and maps unknown attribute types toexternalso an out-of-enum type (such ascrm) can no longer truncate the column - Fix One failing collector no longer wipes the rest - page views are recorded before attributes, and each collector (campaign, language, agent, geolocation, ActiveCampaign) is isolated so a failure in one is logged without losing the others
- Fix Duplicate
blacklistconfig key - the key was defined twice inconfig/kai-personalize.phpand the second definition silently won, leaving the bot filter off. The default ofblacklist.enabledis nowtrue- setKAI_BLACKLIST_ENABLED=falseto keep the old behaviour, and republish the config withphp artisan vendor:publish --tag=kai-personalize-config --force - New
blacklist.skip_known_bots- skips visitors the user agent parser recognises as a bot, without relying on hand-maintained patterns. The SEO whitelist still takes precedence - [changed] Bot check runs before entry resolution - blacklisted traffic no longer pays for the expensive Statamic entry lookup
- [changed] Derived attributes are no longer stored -
time_of_day,day_of_weekandgoogle_maps_linkare computed on read. The first two were already computed live by the tags, and were being written on every single page view - [changed] Blacklist patterns are cached and
MaxMindService/BlacklistServiceare singletons, removing repeated queries and three.mmdbreader instantiations per request - New
{{ kai:visitor }}now exposeslatitude,longitude,google_maps_link,time_of_dayandday_of_week
1.2.7
June 8th, 2026
- Fix Git tags for marketplace releases - Added v1.2.x tags with proper "v" prefix for Statamic marketplace compatibility
- Fix CHANGELOG format - Updated to use New, Fix, [changed] badges for better marketplace display
1.2.6
November 29th, -0001
- N/A Changelog not available.
1.2.6
June 8th, 2026
- Fix Config deep merge - ServiceProvider now uses
array_replace_recursive()instead ofmergeConfigFrom()so missing nested config keys are always filled with addon defaults
1.2.5
November 29th, -0001
- N/A Changelog not available.
1.2.5
May 5th, 2026
- Fix Removed deprecated ScriptProcessorNode - Removed audio fingerprinting to fix browser deprecation warning
- Fix Fingerprinting now uses Canvas + WebGL only (more reliable, no warnings)
- New Extended screen resolution data - Added devicePixelRatio, orientation, and available screen size to device capabilities tracking
- New Server-side user agent tracking - Full browser user agent string now captured server-side for reliability
- New Tracker version in payload - Each tracking request now includes tracker version for debugging
- New Google Maps link - Added google_maps_link attribute when latitude/longitude is available
1.2.4
November 29th, -0001
- N/A Changelog not available.
1.2.4
May 5th, 2026
- New Blacklist settings to config - Added
blacklist.enabledandblacklist.loggingconfiguration options - New Settings page badges - Added visual indicators for Blacklist and Blacklist Logging features
1.2.3
November 29th, -0001
- N/A Changelog not available.
1.2.3
May 5th, 2026
- Fix PSR-4 autoloading - Renamed
src/database/tosrc/Database/for proper PSR-4 compliance
1.2.2
November 29th, -0001
- N/A Changelog not available.
1.2.2
May 5th, 2026
- Fix BlacklistSeeder autoloading - Moved from
database/seeders/tosrc/Database/Seeders/for proper PSR-4 autoloading - New Added
php artisan kai:seed-blacklistcommand for easy database seeding
1.2.1
November 29th, -0001
- N/A Changelog not available.
1.2.1
May 5th, 2026
- New Config option for tracker.js minification -
KAI_USE_MINIFIED_JSenv var to control minified vs regular tracker - [changed] Updated blacklist CP views to use Statamic form layout conventions
- Fix Fixed BlacklistController to extend Statamic CpController
1.2.0
November 29th, -0001
- N/A Changelog not available.
1.2.0
May 5th, 2026
-
New Bot Blacklist Feature
- Database-driven blacklist management via Control Panel
- Block by bot name (e.g., Semrush, Ahrefs) or user agent pattern
- Whitelist for essential SEO bots (Googlebot, Bingbot, etc.)
- Automatic logging of blocked requests with hit counts
- Pre-seeded with common bots, monitoring tools, and AI scrapers
- Configuration:
KAI_BLACKLIST_ENABLED=false(default off for safety)
-
New Tracker.js Minification
- Automated build system using Terser
- File size reduction: 23KB → 8.7KB (~62% smaller)
- Automatic serving of minified version when available
- Build command:
composer run build-jsornpm run build
-
[changed] Updated README.md with Cloudflare configuration (TRUSTED_PROXIES)
1.1.2
March 22nd, 2026
Small bug fixes and documentation and version updates.
1.1.1
March 20th, 2026
Changed
- Edition Rename: "Free" edition renamed to "Lite" edition
- Updated
Edition::isFree()toEdition::isLite() - Updated composer.json editions array
- Updated translations (en/nl) with "Lite tier" references
- Updated documentation (CLAUDE.md, LICENSE, README)
- Updated
Added
- Separate CHANGELOG.md file (moved from README.md)
1.1.0
March 20th, 2026
Added
-
Core Features
- Visitor tracking with fingerprint identification
- Session management with browse history
- Browser & device detection (mobile/desktop/tablet/bot)
- GeoIP2 location detection (local database, no API calls)
- Campaign parameter tracking (UTM)
- Referrer-based personalization
- Cookie consent support
-
Personalization Engine
- Rule-based content delivery with condition builder
- Dynamic visitor segments with criteria-based assignment
- Antlers tags:
{{ kai:visitor }},{{ kai:condition }},{{ kai:content }},{{ kai:segment }} - Session data management:
{{ kai:session:get }},{{ kai:session:set }}
-
Analytics & Engagement
- Page-level analytics (views, unique visitors, scroll depth, reading time)
- Engagement scoring (0-100) based on visits, page views, reading time, scroll depth
- Behavioral event tracking (scroll depth, clicks, reading time, custom events)
- Top engaged visitors ranking
- Visitor page history with pagination
-
External API Integration
- Built-in providers: Weather, Geolocation, News, Exchange rates
- Custom API connections with flexible authentication
- API caching with configurable TTL
- Test connection functionality
- Rate limiting and error handling
-
ActiveCampaign Integration
- Automatic email campaign visitor tracking
- CRM data sync (contact info, tags, lists, custom fields)
- Cookie-based email identification (multiple encoding formats)
-
Control Panel
- Dashboard with real-time statistics
- Analytics pages with engagement metrics
- Rules management (CRUD with condition builder)
- Visitors management (profiles, sessions, page history)
- Segments management (CRUD with refresh functionality)
- API Connections management (CRUD with testing)
- Settings page with configuration overview
-
Security & Privacy
- HMAC SHA-256 signature validation for tracking endpoints
- Rate limiting (60/minute, 500/hour per IP)
- Timestamp validation for replay attack prevention
- IP encryption and DNT respect
- GDPR compliance features
- Data anonymization and retention controls
-
Developer Features
- Tracker queue with localStorage persistence
- Configurable event threshold and send interval
- Artisan commands for testing and maintenance
- MaxMind database download automation
- Statamic 6 compatible (Vue 3)
-
Localization
- Full English and Dutch support