clicktrail / php-sdk
ClickTrail deterministic attribution core and PHP SDK - parse, classify and merge first/last-touch attribution; event ingestion contracts.
Requires
- php: >=8.1
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/log: ^3.0
Requires (Dev)
- guzzlehttp/guzzle: ^7.8
- guzzlehttp/psr7: ^2.6
- phpunit/phpunit: ^10.5
Suggests
- guzzlehttp/guzzle: PSR-18 client implementation for the batch transport
- symfony/http-client: Alternative PSR-18 client implementation
README
English | Português | Deutsch | 中文
clicktrail/php-sdk
Deterministic campaign attribution for PHP — the same engine contract behind ClickTrail's WordPress, GTM and CMS integrations.
Index
Why
Most tracking packages store what a page showed. ClickTrail proves which campaign created the lead or sale: deterministic first-touch / last-touch merge laws, validated field-by-field against the same golden fixtures that govern our WordPress plugin and GTM templates.
Installation
composer require clicktrail/php-sdk
Attribution capture
use ClickTrail\Core\{AttributionInput, StoredState, TouchMerger}; // A paid-search landing... $merged = TouchMerger::observe( StoredState::fromJson($session['ct'] ?? null), new AttributionInput( query: $_GET, host: 'example.com', landingPage: 'https://example.com/promo', touchTimestamp: '2026-08-24T10:00:00.000Z', ), ); $session['ct'] = $merged->toJson(); // first->source === 'google', first->clickIds['gclid'] set, // and this exact result replays identically in WordPress, GTM and Shopware.
A direct visit afterwards changes nothing — first touch stays, stored last touch persists. That is the merge law, tested, not promised.
Consent gating
use ClickTrail\Consent\{ConsentBehavior, ConsentSnapshot}; if (! ConsentBehavior::can($snapshot, 'ad_user_data')) { // enhanced conversion suppressed; reason recorded: // "adUserData was unknown at lead capture (source: cookieyes)" }
Unknown consent is denied by default. Snapshots travel with the lead, so months later the conversion worker knows exactly which permissions existed at capture.
Delivery
$client->track(new Sale(eventId: 'order-8241-paid', orderId: '8241', value: 199.0, currency: 'EUR')); $client->flush(); // batched POST, idempotency keys, backoff retries
Failed batches are captured by your adapter's persistence layer via pending() / restore() and replayed after diagnosis.
Fixture parity
bin/replay-fixtures.php replays the clicktrail-js golden fixtures: currently 12/12 fixtures, 57/57 fields MATCH. Ledger: docs/FIXTURE-PARITY-LEDGER.md. Classifier changes are MAJOR releases, by definition.
Testing
vendor/bin/phpunit --testdox # full suite php bin/replay-fixtures.php <dir> # parity ledger
License
MIT.