Kntnt Ad Attribution WP Registry grade A. No security issues foundWP RegistryNo security issues foundOpen the reportA
WordPress plugin that provides internal lead attribution for ad campaigns using first-party cookies and fractional, time-weighted attribution.
Install
Upload it under Plugins → Add New Plugin → Upload Plugin, then activate.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/kntnt/kntnt-ad-attribution/releases/download/v1.11.0/kntnt-ad-attribution.zipA privacy-friendly WordPress plugin for lead attribution that keeps all data on your own server — no personal data is sent to Google or any other third party. Works with any ad platform.
Description
Ad platforms like Google Ads offer conversion tracking features — such as Google's Enhanced Conversions — that improve attribution by sending hashed personal data (email addresses, phone numbers, names) from your website to the ad platform's servers. This raises significant privacy concerns: personal data leaves your domain, is transferred to servers that may be located outside the EU, and is used by the ad platform for its own purposes beyond your control. With the EU-US Data Privacy Framework under ongoing legal challenge and the possibility that it may be invalidated (as its predecessors Safe Harbor and Privacy Shield were), relying on such mechanisms creates regulatory risk for EU-based businesses.
Kntnt Ad Attribution takes a different approach. It gives you the same core benefit — knowing which ads actually generate leads — but keeps all data on your own server. No personal data is sent to Google, Meta, or any other third party.
Each ad gets a unique tracking URL (e.g. example.com/ad/a1b2c3…). When a visitor clicks an ad, your server records the click, stores the ad hash in a first-party cookie, and redirects the visitor to the landing page. When the visitor later submits a lead form, the server reads the cookie and attributes the conversion to the most recently clicked ad (filterable to support multi-touch models). The entire process happens on your infrastructure.
The plugin is platform-agnostic and works with any ad platform — Google Ads, Meta Ads, LinkedIn Ads, Microsoft Ads, or any other source that can link to a custom URL. This means you get a single, consistent attribution method across all your advertising channels.
The plugin does not hardcode integrations with any specific consent management or form plugin. Instead, it exposes hooks that you connect to your preferred plugins via your theme's functions.php, a mu-plugin, or a code snippet plugin.
Key Features
- Hash-based tracking URLs — each ad gets a unique
/ad/<hash>URL (prefix configurable via filter), independent of ad platform. - First-party cookie tracking — stores clicked ad hashes in a single
HttpOnly,Secure,SameSite=Laxcookie (_ad_clicks), with a configurable lifetime (default: 90 days). - Filterable last-click attribution — by default, the most recently clicked ad receives full conversion credit. The attribution model is filterable via the
kntnt_ad_attr_attributionhook, enabling multi-touch models (e.g. time-weighted, linear, position-based). - Deduplication — per-hash deduplication prevents the same tracking URL from generating duplicate conversions within a configurable window. Disabled by default (
kntnt_ad_attr_dedup_seconds= 0); when enabled, each hash is independently checked against its last conversion timestamp. - Cookie size management — stores a maximum of 50 ad hashes per visitor, pruning the oldest when the limit is reached.
- Campaign dashboard — view clicks, conversions, and fractional attribution per campaign for any date range, with CSV export. The date filter defaults to the two most recent complete calendar weeks (based on the WordPress "Week Starts On" setting). Individual click records include per-click Content, Term, Id, and Group fields.
- Settings page — configure cookie lifetime, deduplication, log file size limits, and queue retry parameters under Settings > Ad Attribution. Logging toggle and log file management (download, clear) are on the Tools > Ad Attribution page.
- Shared diagnostic logging — timestamped log file at
wp-content/uploads/kntnt-ad-attribution/kntnt-ad-attribution.log, shared by core and add-on plugins. Controlled via the settings page. Sensitive values are masked. - Queue management UI — view, retry, and delete individual queue jobs from the Report Queue table on the admin page.
- Three-state consent model — integrates with any cookie consent plugin via a filter hook, supporting yes, no, and undefined consent states with a transport mechanism for deferred consent.
- Platform-agnostic form support — integrates with any form plugin via an action hook.
- Bot detection — filters out known bots via User-Agent matching and
robots.txtrules. - Two redirect methods — 302 redirect (default) or JavaScript redirect via filter, providing flexibility for different ITP mitigation strategies.
- Companion plugin hooks — fires
kntnt_ad_attr_clickon every non-bot click with hash, target URL, campaign data, and access to URL parameters (gclid, fbclid, etc.). Companion plugins can capture platform-specific data and implement server-side API integrations without modifying the core plugin. - Query parameter forwarding — ad platform parameters (gclid, fbclid, msclkid, etc.) appended to the tracking URL are automatically forwarded to the target page through the redirect. Target URL parameters take precedence, and the merged set is filterable.
- Adapter infrastructure for add-ons — a built-in adapter system lets add-on plugins or code snippets register click-ID capturers (for
gclid,fbclid,msclkid, etc.) and conversion reporters (for Google Ads, Meta, Matomo, GA4, etc.). The core captures click IDs and processes a report queue; adapters define what to capture and where to report. If no adapters are registered, the plugin behaves identically to previous versions.
The Problem
Ad platforms tell you how many clicks an ad received, but not how many of those clicks turned into actual leads on your website. The standard solution is client-side conversion tracking — a JavaScript tag that fires when a form is submitted — but this approach has two categories of problems.
It is increasingly unreliable:
- Ad blockers prevent the tracking script from loading at all.
- Safari's Intelligent Tracking Prevention (ITP) limits the lifetime of cookies set in a cross-site context, causing conversions to go unrecorded if the visitor returns after a few days.
- Privacy-focused browsers (Firefox Strict Mode, Brave, etc.) strip or block tracking parameters like
gclidbefore they reach your server.
It raises privacy concerns:
Ad platforms have responded to the decline in client-side tracking with features like Google's Enhanced Conversions and Meta's Advanced Matching, which improve attribution by sending hashed first-party data (email addresses, phone numbers, names) to the ad platform's servers. While the data is hashed before transmission, this approach has significant implications:
- Hashing is not anonymization. Regulatory bodies including the US Federal Trade Commission (FTC) have noted that hashed data can still enable identification of individuals. Under GDPR, hashed personal data remains personal data.
- Personal data leaves your domain and is transferred to the ad platform's servers, which may be located outside the EU.
- The ad platform uses the data for its own purposes, including automated bidding optimization and audience modelling, beyond the advertiser's direct control.
- The legal basis for EU-US data transfers is uncertain. The current EU-US Data Privacy Framework (DPF) is under active legal challenge. Its two predecessors — Safe Harbor and Privacy Shield — were both invalidated by the EU Court of Justice. A "Schrems III" ruling or political changes to the underlying US executive order could leave businesses without a valid transfer mechanism.
The result is a dilemma: you need attribution to spend your ad budget wisely, but the available solutions are either unreliable, privacy-invasive, or both.
How This Plugin Helps
Kntnt Ad Attribution solves both problems. It moves the tracking to the server side, eliminating the dependency on client-side JavaScript, and it keeps all data on your own server, eliminating the need to share personal data with third parties.
- Each ad gets a unique server-controlled tracking URL (
/ad/<hash>). - When a visitor clicks the ad, your server — not a JavaScript tag — records the click and stores the ad hash in a first-party cookie.
- When the visitor later submits a lead form, the server reads the cookie and attributes the conversion.
Because the click recording and attribution happen on your server, they are immune to ad blockers and JavaScript-blocking browser features. The cookie is a standard first-party HTTP cookie set by your own domain, which makes it more resilient than third-party or JavaScript-set cookies (though Safari's ITP may still limit its lifetime — see Limitations).
Unlike Enhanced Conversions and similar features, the plugin never sends personal data — hashed or otherwise — to any external server. No email addresses, no phone numbers, no names. The only data stored is a list of opaque hashes in a cookie on the visitor's browser and aggregated click/conversion counts in your WordPress database. This means there is no third-country transfer issue and no dependency on the EU-US Data Privacy Framework or any other international data transfer mechanism.
Limitations
- ITP still applies. Safari may cap the cookie lifetime to 7 days when the visitor arrives from a classified tracking domain (such as Google). Conversions after that window are lost. A JavaScript redirect method (configurable via filter) may improve this in some cases, but there are no guarantees.
- No built-in feedback to ad platforms. The core plugin provides internal statistics only and does not include integrations with any external API. However, the adapter system allows add-on plugins to register click-ID capturers and conversion reporters for any ad platform or analytics tool. See Adapter System for details.
- Cookies can be cleared. If the visitor clears their cookies or uses a private/incognito window for the return visit, the attribution link is broken.
- Cross-device tracking is not supported. A visitor who clicks an ad on their phone but converts on their laptop will not be attributed.
- Impressions are not measured. Ad views occur on the ad platform and never reach the server.
- No multisite support. Version 1 does not support WordPress multisite.
Despite these limitations, server-side first-party cookie tracking captures significantly more conversions than pure client-side tracking, and gives you a reliable internal baseline for comparing ad performance.
Privacy and GDPR
This plugin is designed with data minimization and data locality as core principles. Here is how it relates to common privacy concerns:
No personal data leaves your server. Unlike Google's Enhanced Conversions or Meta's Advanced Matching, this plugin never transmits personal data — hashed or otherwise — to any third party. All attribution data stays in your WordPress database on your own infrastructure. This is the plugin's fundamental privacy advantage. Note: if you install add-on plugins that report conversions to external services (such as Google Ads or Matomo), those add-ons will transmit data to external servers. The core plugin itself never does — it only provides the infrastructure that add-ons use.
No third-country transfer problem. Because no data is sent to external servers, there is no dependency on the EU-US Data Privacy Framework, Standard Contractual Clauses, or any other international data transfer mechanism. If the Data Privacy Framework is invalidated by a future court ruling (as Safe Harbor and Privacy Shield were before it), this plugin is unaffected.
The cookie constitutes personal data under GDPR. The _ad_clicks cookie links a visitor's ad clicks to their subsequent form submissions, which makes it personal data processing. The plugin therefore requires consent for the cookie, and implements a three-state consent model (yes, no, undefined) that integrates with any cookie consent plugin. See Cookie Consent Configuration for details.
Click counting does not require consent. The plugin always logs that a click occurred on a tracking URL (recording a click record), regardless of consent status. This is analogous to server access logs and does not constitute personal data processing, since no individual is identified or identifiable from the click record alone.
Hashing is used for URL generation, not for pseudonymization of personal data. The SHA-256 hashes in this plugin are generated from random bytes (random_bytes(32)) and serve as opaque identifiers for tracking URLs. They are not derived from any personal data or UTM parameters — they are purely random identifiers. This is a fundamental difference from Enhanced Conversions, where personal data is hashed and sent to Google.
The _aah_pending cookie is a borderline case. This temporary cookie (maximum 60 seconds) contains only an ad hash and serves as a technical transport mechanism for deferred consent scenarios. It contains no personal data in itself. Whether it should be classified as "necessary" or "marketing" is a judgment call that depends on your interpretation; the plugin's consent configuration section presents both options. See Cookie Consent Configuration.
Data minimization. The plugin stores the minimum data needed for attribution: opaque hashes in a cookie and individual click/conversion records in the database. No names, email addresses, IP addresses, or other directly identifying information is stored by the plugin.
For Users
This section is for marketers, website owners, WordPress administrators, and agency staff who want to install, configure, and use the plugin.
Installation
- Download the latest release ZIP file.
- In your WordPress admin panel, go to Plugins → Add New.
- Click Upload Plugin and select the downloaded ZIP file.
- Activate the plugin.
The plugin is distributed via GitHub Releases and updated through the standard WordPress plugin update UI. When a new version is available, you will see it on the WordPress Updates page.
System Requirements
| Requirement | Minimum |
|---|---|
| PHP | 8.3 |
| WordPress | 6.9 |
| HTTPS | Required (cookies are set with the Secure flag) |
| MySQL/MariaDB | 5.7 / 10.3 |
The plugin checks the PHP version on activation and aborts with a clear error message if the requirement is not met.
Permissions
On activation, the plugin registers a custom capability: kntnt_ad_attr. This capability is automatically granted to the Administrator and Editor roles. Only users with this capability can access the Ad Attribution page and manage tracking URLs.
To grant access to other roles (e.g. Author), use a role management plugin such as Members or User Role Editor to assign the kntnt_ad_attr capability.
Cookie Consent Configuration
If you use a cookie consent plugin (such as Real Cookie Banner, which is an excellent and recommended choice), you need to register the plugin's cookies as a marketing service. Here is the information you need:
| Field | Value |
|---|---|
| Service name | Ad Attribution |
| Service identifier | kntnt-ad-attribution |
| Provider | Website owner (first-party service) |
| Category | Marketing |
| Purpose | Stores information about which ads the visitor has clicked in order to measure which ads lead to inquiries via the website's forms. No data is shared with third parties. |
| Legal basis | Consent (opt-in) |
| Data processing countries | The country (or countries) where the site is hosted, and potentially other countries (consult your legal advisor). |
Register the following cookies:
| Property | Cookie 1 | Cookie 2 | Cookie 3 |
|---|---|---|---|
| Name | _ad_clicks |
_ad_last_conv |
_aah_pending |
| Type | HTTP Cookie | HTTP Cookie | HTTP Cookie |
| Host | .yourdomain.com |
.yourdomain.com |
.yourdomain.com |
| Duration | 90 days* | Controlled by kntnt_ad_attr_dedup_seconds** |
60 seconds |
| Purpose | Stores which ads the visitor has clicked in order to attribute form inquiries to the ads that generated them. | Stores per-hash last conversion timestamps to prevent duplicate counting. Only exists when deduplication is enabled. | Temporary transport of ad hash to the landing page while the visitor has not yet made a cookie consent decision. Automatically deleted after 60 seconds. |
| Category | Marketing | Marketing | Necessary or Marketing*** |
Replace .yourdomain.com with your actual domain.
* Configurable via the kntnt_ad_attr_cookie_lifetime filter (default: 90 days).
** This cookie only exists when the kntnt_ad_attr_dedup_seconds filter returns a non-zero value (default: 0, meaning no dedup and no cookie). When enabled, the cookie lifetime equals the dedup window in seconds, capped to the cookie lifetime.
*** _aah_pending contains no personal data, lives at most 60 seconds, and serves only as a technical transport mechanism for deferred consent scenarios. Whether to classify it as "Necessary" or "Marketing" is a judgment call — see Privacy and GDPR for a discussion.
Usage
Admin Interface
The plugin adds Ad Attribution under Tools in the WordPress admin menu. The page presents a single merged view for both managing tracking URLs and viewing attribution results.
- Create Tracking URL: Click the "Create Tracking URL" button to open the form. Select a target page via a searchable dropdown and fill in the required parameter fields: source, medium, and campaign. Source and medium offer predefined options (configurable via the
kntnt_ad_attr_utm_optionsfilter) but also accept custom values. Content, Term, Id, and Group are not set at creation time — they vary per click and are captured automatically from incoming UTM or MTM parameters (see Click-Time Parameter Population). The plugin generates a SHA-256 hash and produces a tracking URL:https://yourdomain.com/ad/<hash>. - Campaign list: Shows all tracking URLs with their target URL, source, medium, campaign, click count, and fractional conversion count. Click a tracking URL to copy it to the clipboard. The list can be filtered by date range, source, medium, and campaign. A search box allows searching by tracking URL or hash.
- Row actions: Trash (or Restore / Delete Permanently for trashed URLs).
- Bulk actions: Move to Trash (for published URLs), Restore / Delete Permanently (for trashed URLs).
- Summary: Shows total clicks and total (fractional) conversions for the selected filters.
- Export: Export the filtered results as a CSV file (UTF-8 with BOM; semicolon delimiter when the locale uses comma as decimal separator). The CSV includes all fields including per-click Content, Term, Id, and Group from the clicks table.
Note: The plugin tracks clicks (each request to /ad/<hash>) and conversions. Ad impressions are not available since they occur on the ad platform and never reach your server.
Click-Time Parameter Population
Source, medium, and campaign are set when creating the tracking URL. If any are left empty (e.g. from pre-v1.5.0 URLs), the click handler populates them at click time from incoming query parameters. Content, Term, Id, and Group are always captured per click — they vary between clicks on the same tracking URL and are stored in the clicks table, not in postmeta.
Both UTM and MTM (Matomo Tag Manager) parameter formats are supported:
| Field | Storage | UTM param | MTM param |
|---|---|---|---|
| Source | Postmeta (fixed per URL) | utm_source |
mtm_source |
| Medium | Postmeta (fixed per URL) | utm_medium |
mtm_medium |
| Campaign | Postmeta (fixed per URL) | utm_campaign |
mtm_campaign |
| Content | Clicks table (per click) | utm_content |
mtm_content |
| Term | Clicks table (per click) | utm_term |
mtm_keyword |
| Id | Clicks table (per click) | utm_id |
mtm_cid |
| Group | Clicks table (per click) | utm_source_platform |
mtm_group |
For Source/Medium/Campaign, the priority order (highest first) is:
- Stored value (set by admin when creating the tracking URL) — never overwritten.
- Incoming UTM parameter from query string.
- Incoming MTM parameter from query string.
How Attribution Works
When a visitor clicks a tracking URL, the plugin logs the click, stores the ad hash in a first-party cookie (if consent is given), and redirects the visitor to the landing page.
If consent is undefined (the visitor hasn't decided yet), the hash is transported to the landing page via a temporary cookie (_aah_pending, 60 seconds) or a URL fragment. A client-side script picks up the hash and stores it in sessionStorage until consent is resolved.
When a conversion is triggered (see Connecting a Form Plugin in the Builders section), the plugin:
- Reads the
_ad_clickscookie and extracts all ad hashes. - Filters out hashes that no longer exist as registered tracking URLs.
- If no valid hashes remain, exits without recording anything.
- Per-hash deduplication (only when
kntnt_ad_attr_dedup_seconds> 0): removes hashes that were converted recently. - Applies the attribution model (default: last-click — the most recent click receives
1.0, all others receive0.0). The model is filterable viakntnt_ad_attr_attribution. - Looks up the matching click records and stores conversion rows in the database within a transaction.
- Writes per-hash dedup cookie (only when dedup is enabled).
User FAQ
What problem does this plugin solve?
Ad platforms report clicks but not which clicks became leads on your website. Standard client-side tracking (JavaScript tags) is increasingly blocked by ad blockers, Safari's ITP, and privacy-focused browsers. This plugin moves the tracking to the server side, where it is immune to ad blockers and more resilient to browser restrictions. It gives you an internal, independent view of which ads actually generate leads. See The Problem and Limitations for details.
Which ad platforms does this plugin support (Google Ads, Meta Ads, …)?
All of them. The plugin is platform-agnostic. It works with any ad platform that lets you set a custom destination URL — Google Ads, Meta Ads, LinkedIn Ads, Microsoft Ads, and any other platform.
How does this compare to Google's Enhanced Conversions?
Both this plugin and Enhanced Conversions aim to improve ad attribution beyond what basic client-side tracking provides. The key difference is where the data goes. Enhanced Conversions sends hashed personal data (email, phone, name) to Google's servers, where it is matched against Google accounts. This plugin keeps all data on your own server and never sends personal data to any third party. The trade-off is that this plugin cannot feed conversion data back to the ad platform's bidding algorithms — it provides internal attribution statistics only. See Privacy and GDPR for a detailed comparison.
What about cookie consent / GDPR?
The plugin is designed to keep all data on your own server — no personal data is sent to any third party. This eliminates the third-country transfer issues that affect solutions like Google's Enhanced Conversions. However, the _ad_clicks cookie constitutes personal data processing under GDPR and requires consent. The plugin supports a three-state consent model (yes, no, undefined) and integrates with any cookie consent plugin. See Privacy and GDPR for a full discussion and Cookie Consent Configuration for the cookie details to register.
What happens with Safari's Intelligent Tracking Prevention (ITP)?
Safari's ITP may limit the cookie lifetime to 7 days (or less) when the visitor arrives from a classified cross-site source such as a Google Ads click. This means conversions that happen more than 7 days after the ad click may not be attributed on Safari/iOS. A JavaScript redirect method is available as a filter option that may improve this in some cases, but there are no guarantees.
What if a visitor clicks more than 50 ads?
The cookie stores a maximum of 50 ad hashes. When the limit is reached, the oldest hash is removed to make room for the new one.
What if the same visitor submits the form twice?
By default, all conversions are recorded (deduplication is disabled). When deduplication is enabled via the kntnt_ad_attr_dedup_seconds filter, each tracking URL hash is independently checked — a conversion for one ad does not block conversions for other ads. Only conversions for the same hash within the configured window are skipped.
Does the plugin send conversion data back to the ad platform?
Not by itself. The core plugin provides internal attribution statistics only and never communicates with external APIs. However, the adapter system lets add-on plugins register conversion reporters that report to any ad platform or analytics tool. See the Adapter System section under Builders.
How can I get help or report a bug?
Please visit the plugin's issue tracker on GitHub to ask questions, report bugs, or view existing discussions.
For Builders
This section is for developers who want to integrate the plugin with form plugins, consent plugins, or other systems using WordPress hooks. It assumes familiarity with PHP and WordPress development.
Connecting a Form Plugin
The plugin does not integrate with any specific form plugin. Instead, you trigger a conversion by calling the kntnt_ad_attr_conversion action hook from your form plugin's submission handler.
Example with WS Form:
In WS Form, open the specific lead form you want to track, go to the Actions tab, and add a WordPress action hook that fires on successful submission. Set the hook name to kntnt_ad_attr_conversion. No additional code is needed — WS Form will fire the action and the plugin will handle the rest. Only add this action to the forms you want to count as lead conversions.
Example with a custom form or another plugin:
If your form plugin doesn't have a UI for firing action hooks, add a snippet to your theme's functions.php, a mu-plugin, or a code snippet plugin. Make sure to target only the specific lead form(s) you want to track — not every form on the site.
// Example: trigger conversion for a specific Contact Form 7 form.
// Replace 42 with the ID of your lead form (found in the CF7 admin).
add_action( 'wpcf7_mail_sent', function ( $contact_form ) {
if ( $contact_form->id() === 42 ) {
do_action( 'kntnt_ad_attr_conversion' );
}
} );
// Example: trigger conversion for specific Gravity Forms forms.
// Replace 5 and 12 with the IDs of your lead forms.
add_action( 'gform_after_submission', function ( $entry, $form ) {
if ( in_array( $form['id'], [ 5, 12 ], true ) ) {
do_action( 'kntnt_ad_attr_conversion' );
}
}, 10, 2 );
Connecting a Cookie Consent Plugin
The plugin uses a three-state consent model:
- Yes (
true) — the visitor has accepted marketing cookies. The_ad_clickscookie is set normally. - No (
false) — the visitor has declined. No cookie is set. Attribution is lost (accepted trade-off). - Undefined (
null) — the visitor hasn't decided yet. The click is logged, the hash is transported to the client via a temporary mechanism, and the client-side script waits for a consent decision.
Consent integration requires two parts: a PHP filter for server-side consent checks (at click time and when setting cookies via REST), and a JavaScript override for client-side deferred consent handling (when the visitor hasn't decided yet at the time of the click).
When no filter callback is registered on kntnt_ad_attr_has_consent, the default null (undetermined) activates the deferred consent transport mechanism. Sites without consent requirements should register a callback that returns true.
PHP: Server-side consent filter
The kntnt_ad_attr_has_consent filter must return true, false, or null. It is critical to distinguish between "the visitor has not made a decision yet" (null) and "the visitor has actively denied consent" (false). Returning false when the visitor simply hasn't decided yet will prevent the deferred transport mechanism from activating, and the attribution will be silently lost.
Example with Real Cookie Banner:
First, create a service in Real Cookie Banner with the unique identifier kntnt-ad-attribution and the cookie information from the Cookie Consent Configuration section above.
Then add the following snippet to your theme's functions.php, a mu-plugin, or a code snippet plugin:
add_filter( 'kntnt_ad_attr_has_consent', function (): ?bool {
if ( ! function_exists( 'wp_rcb_consent_given' ) ) {
return null; // RCB not active.
}
$consent = wp_rcb_consent_given( 'kntnt-ad-attribution' );
if ( empty( $consent['cookie'] ) ) {
return null; // Service not configured in RCB.
}
if ( ! $consent['consentGiven'] ) {
return null; // Visitor has not made a decision yet.
}
return $consent['cookieOptIn'] === true;
} );
Note the consentGiven check: wp_rcb_consent_given() returns cookieOptIn: false both when the visitor has actively denied consent and when they simply haven't interacted with the banner yet. The consentGiven field distinguishes between these two cases — it is true only after the visitor has made an explicit choice.
PHP: Server-side cookie deletion on opt-out
The _ad_clicks and _ad_last_conv cookies are set with the HttpOnly flag for security, which prevents consent plugins from deleting them via client-side JavaScript. The plugin provides a global function kntnt_ad_attribution_delete_cookies() that expires all plugin cookies with the correct attributes. Call it from your consent plugin's server-side opt-out hook.
Example with Real Cookie Banner:
add_action( 'RCB/OptOut/ByHttpCookie', function ( string $name, string $host ): void {
if ( $name === '_ad_clicks' || $name === '_ad_last_conv' ) {
kntnt_ad_attribution_delete_cookies();
}
}, 10, 2 );
The function expires _ad_clicks and _ad_last_conv (and any add-on cookies registered via the kntnt_ad_attr_delete_cookies filter). This hook fires during the REST request that Real Cookie Banner makes when the visitor revokes consent. For other consent plugins, consult their documentation for an equivalent server-side opt-out hook or event.
JavaScript: Client-side deferred consent
For deferred consent scenarios, the plugin defines a default window.kntntAdAttributionGetConsent function that the client-side script (pending-consent.js) calls to determine consent status. The default implementation calls the callback with 'unknown', which keeps the hashes in sessionStorage indefinitely. Override this function before DOMContentLoaded to connect to your consent plugin's JavaScript API.
The callback accepts 'yes', 'no', or 'unknown'. The script handles multiple invocations via an internal handled flag — only the first 'yes' or 'no' takes effect.
Example with Real Cookie Banner:
( function() {
'use strict';
window.kntntAdAttributionGetConsent = function( callback ) {
var api = window.consentApi;
if ( ! api || typeof api.consent !== 'function' ) {
return; // RCB not loaded; fall back to plugin default.
}
var handled = false;
function respond( answer ) {
if ( handled ) {
return;
}
handled = true;
callback( answer );
}
// consent() returns a Promise that resolves when consent IS given
// and rejects when consent is denied.
api.consent( 'kntnt-ad-attribution' ).then( function() {
respond( 'yes' );
} ).catch( function() {
respond( 'no' );
} );
};
} )();
Real Cookie Banner's consentApi.consent() method returns a Promise — it is a function, not a property object. The Promise resolves when the visitor grants consent (immediately if already consented) and rejects when the visitor denies consent. If the visitor hasn't decided yet, the Promise stays pending until a decision is made.
Developer Hooks
Filters
kntnt_ad_attr_has_consent
Controls whether the plugin has consent to set cookies for the current visitor. Return true to allow cookies, false to block them, or null for undefined (triggers deferred consent transport). Default: null.
[!IMPORTANT] Returning
falsewhen the visitor simply hasn't decided yet will silently prevent the deferred transport mechanism from activating. Make sure your implementation distinguishes "no decision yet" (null) from "actively denied" (false). See Connecting a Cookie Consent Plugin for a detailed example.
add_filter( 'kntnt_ad_attr_has_consent', function (): ?bool {
// Your consent logic here — return true, false, or null
return true;
} );
kntnt_ad_attr_redirect_method
Controls the redirect method used after an ad click. Default: '302'.
// Switch to JavaScript redirect for better ITP handling
add_filter( 'kntnt_ad_attr_redirect_method', function () {
return 'js';
} );
Accepted values: '302' (server-side redirect) or 'js' (JavaScript redirect via an intermediate page).
kntnt_ad_attr_url_prefix
Filters the URL prefix used for tracking URLs. Default: 'ad' (resulting in /ad/<hash>).
// Change tracking URLs to /go/<hash>
add_filter( 'kntnt_ad_attr_url_prefix', function () {
return 'go';
} );
kntnt_ad_attr_cookie_lifetime
Filters the cookie lifetime in days. Default: 90. Also affects the attribution formula's N value.
add_filter( 'kntnt_ad_attr_cookie_lifetime', function () {
return 30;
} );
kntnt_ad_attr_dedup_seconds
Per-hash deduplication window in seconds. Default: 0 (deduplication disabled). When non-zero, each hash is independently checked against its last conversion timestamp in the _ad_last_conv cookie. Automatically capped to cookie_lifetime × DAY_IN_SECONDS.
// Enable dedup with a 14-day window.
add_filter( 'kntnt_ad_attr_dedup_seconds', function () {
return 14 * DAY_IN_SECONDS;
} );
kntnt_ad_attr_pending_transport
Controls the transport mechanism used when consent is undefined. Default: 'cookie'.
// Use URL fragment instead of temporary cookie
add_filter( 'kntnt_ad_attr_pending_transport', function () {
return 'fragment';
} );
Accepted values: 'cookie' (temporary _aah_pending cookie, 60 seconds) or 'fragment' (URL fragment #_aah=<hash>).
kntnt_ad_attr_is_bot
Controls bot detection. Default: false. The plugin registers its own callback that checks the User-Agent (case-insensitive substring match) against these signatures: bot (catches Googlebot, Bingbot, LinkedInBot, AdsBot-Google, etc.), crawl, spider, slurp, facebookexternalhit, Mediapartners-Google, Yahoo, curl, wget, python-requests, HeadlessChrome, Lighthouse, GTmetrix. Empty User-Agents are also treated as bots. Bots are redirected to the target page without logging the click or setting a cookie. The plugin also adds Disallow: /<prefix>/ to the virtual robots.txt.
// Add custom bot detection logic
add_filter( 'kntnt_ad_attr_is_bot', function ( bool $is_bot ): bool {
if ( $is_bot ) {
return true; // Already detected as bot
}
// Your additional detection logic
return str_contains( $_SERVER['HTTP_USER_AGENT'] ?? '', 'MyCustomBot' );
} );
kntnt_ad_attr_click_id_capturers
Registers platform-specific GET parameters to capture at ad click time. Return an associative array mapping platform identifiers to GET parameter names. Default: [].
add_filter( 'kntnt_ad_attr_click_id_capturers', function ( array $capturers ): array {
$capturers['google_ads'] = 'gclid';
$capturers['meta'] = 'fbclid';
return $capturers;
} );
kntnt_ad_attr_conversion_reporters
Registers conversion reporters for async processing. Return an associative array mapping reporter IDs to reporter definitions. Default: [].
Reporter definition:
| Key | Type | Description |
|---|---|---|
label |
string |
Name for logging and admin UI. |
enqueue |
callable |
Called at conversion time. Signature: ( array $attributions, array $click_ids, array $campaigns, array $context ) → array of items. Each item is either a structured array with payload, optional label, optional retry_params, and optional not_before keys, or a raw payload array (legacy format). |
process |
callable |
Called by queue processor. Signature: ( array $payload ) → bool. |
Structured item keys:
| Key | Type | Required | Description |
|---|---|---|---|
payload |
array |
Yes | The data to be processed by the process callback. |
label |
string |
No | Human-readable description shown in the queue management UI. |
retry_params |
array |
No | Per-job retry overrides: attempts_per_round, retry_delay, max_rounds, round_delay. |
not_before |
int |
No | Unix timestamp before which the job should not be processed. Used for deferred uploads (e.g. Google Ads 6h delay). |
See Adapter System for full examples and documentation.
kntnt_ad_attr_admin_tabs
Filters the admin page tab list. The core no longer uses tabs (the admin page is a single merged view), but add-on plugins can still register custom views by appending slug → label entries. When ?tab=<slug> is passed, the kntnt_ad_attr_admin_tab_{$tab} action fires for rendering.
add_filter( 'kntnt_ad_attr_admin_tabs', function ( array $tabs ): array {
$tabs['settings'] = __( 'Settings', 'my-addon' );
return $tabs;
} );
kntnt_ad_attr_redirect_query_params
Filters the merged query parameters before building the redirect URL. When a visitor clicks a tracking URL with extra query parameters (e.g. /ad/<hash>?gclid=abc), these are forwarded to the target page. Target URL parameters take precedence over incoming ones.
add_filter( 'kntnt_ad_attr_redirect_query_params', function ( array $merged, array $target, array $incoming ): array {
// Prevent internal tracking parameters from leaking to the landing page.
unset( $merged['fbclid'], $merged['gclid'] );
return $merged;
}, 10, 3 );
kntnt_ad_attr_attribution
Filters the attribution weights for a conversion. Receives an associative array of hash => fractional value (default: 1.0 for the most recent click, 0.0 for all others) and an array of click data with timestamps. Must return an array where values sum to 1.
This README is longer than the copy stored here. Read the rest on GitHub →
Releases
15 releases. Each count is every asset in that release; expand a row for the breakdown.