Kai Personalize Icon

Kai Personalize

1.2.12

August 21st, 2026

  • Fix The tracking rate limiter never expired its counters, so an IP stayed on 429 forever - ThrottleTracking incremented 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 to put($key, 1, 0), and expiration(0) means 9999999999, not "now"), after which Cache::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, because DatabaseStore::increment() returns false on a missing key. Both windows now run through Laravel's RateLimiter, which writes the key with its TTL before incrementing, and whose :timer key 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() read X-Forwarded-For, CF-Connecting-IP and X-Real-IP straight 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, BlacklistService and TrackingController all 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) and tracking.rate_limit.per_hour (KAI_TRACKING_RATE_LIMIT_PER_HOUR, default 1000, up from a hard-coded 500). Set either to 0 to 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 with X-RateLimit-Limit and X-RateLimit-Remaining. The JSON body is unchanged
  • [changed] The cache keys were renamed from kai:tracking:{ip}:minute|hourly to kai-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. Run php artisan cache:clear after 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 carries defer. 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/js and linked from there. Run php artisan vendor:publish --tag=kai-personalize-assets --force after 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 now public, max-age=3600
  • [changed] Dropped an unused Visitor import from KaiTrack

1.2.9

August 19th, 2026

  • Fix Tracking endpoint returned 419 Page Expired - the tracker POSTs to /!/kai-personalize/track, which runs in the web middleware group and therefore through CSRF verification, while neither tracker.js nor sendBeacon sends a token. No tracking event has ever arrived on a site that did not work around this. The route now exempts itself from ValidateCsrfToken, so the host application needs no setup at all
  • [breaking] The HMAC signature layer is gone - KAI_TRACKING_SECRET, tracking.signature_secret and tracking.signature_ttl are no longer read, and TrackingSignatureService is removed. The layer was never finished on the client side: the controller demanded signature, timestamp and nonce, but no tracker version ever sent them, so a filled KAI_TRACKING_SECRET silently rejected every event with a 403. It was also a weaker reimplementation of CSRF - no session binding, a 300s TTL, and unusable from sendBeacon on page unload. Requests are guarded by the origin/referer check and the rate limits instead. Remove KAI_TRACKING_SECRET from your .env - it is a dead key
  • [breaking] {{ kai:tracking }} no longer returns signature data - it now returns url and enabled. {{ kai:tracking:signature }} is removed
  • [changed] The README's CSRF instructions are obsolete - earlier versions told you to add a validateCsrfTokens(except: …) rule to bootstrap/app.php. That rule can be removed. It never worked as written either: the documented pattern kai-personalize/track misses Statamic's action prefix, so the real path !/kai-personalize/track never matched it
  • [changed] Dropped the version field from package.json - it had drifted to 1.2.1 and served no purpose (the package is private and never published), leaving ServiceProvider::VERSION and the git tag as the only version sources. TRACKER_VERSION in tracker.js stays 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) produced Column '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 to external so an out-of-enum type (such as crm) 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 blacklist config key - the key was defined twice in config/kai-personalize.php and the second definition silently won, leaving the bot filter off. The default of blacklist.enabled is now true - set KAI_BLACKLIST_ENABLED=false to keep the old behaviour, and republish the config with php 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_week and google_maps_link are 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 / BlacklistService are singletons, removing repeated queries and three .mmdb reader instantiations per request
  • New {{ kai:visitor }} now exposes latitude, longitude, google_maps_link, time_of_day and day_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 of mergeConfigFrom() 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.enabled and blacklist.logging configuration 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/ to src/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/ to src/Database/Seeders/ for proper PSR-4 autoloading
  • New Added php artisan kai:seed-blacklist command 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_JS env 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-js or npm 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() to Edition::isLite()
    • Updated composer.json editions array
    • Updated translations (en/nl) with "Lite tier" references
    • Updated documentation (CLAUDE.md, LICENSE, README)

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