SDKs
PHP and Laravel server SDK
Report sign-ups and purchases from a PHP or Laravel backend with traqio/traqio-php, and tie each one to the ad click it came from. Auto-discovered in Laravel.
traqio/traqio-php is the Traqio SDK for PHP and Laravel backends — the server half of Traqio's web SDK. The conversion that counts on a website — a sign-up, a subscription, a purchase — happens on your backend, where the user id is known. This package reports it with the trackID, the ad click id that @traqio/web kept in first-party storage since the landing page, and Traqio credits the campaign that brought the user — deterministically, by that click's id. No dependency beyond ext-curl; auto-discovered in Laravel.
Not on Packagist yet — early access
traqio/traqio-php · version 0.1.0The SDK packages are not on their public registries yet. To integrate today, ask for the build: hello@traqio.appHow far it is proven: Unit-tested, and run end-to-end against a running Traqio API: sign-up with a trackID → attributed.Requirements#
- PHP 8.1 or later, with
ext-curlandext-json. Laravel 12 or 13 for the auto-discovered integration; plain PHP, Symfony or any PSR-7 framework otherwise. - A server key for the product (
tq_sk_…). Neither the app's SDK key nor a web key works here. - @traqio/web on the site, and ads pointing at tracking links with a website destination — that is how the trackID reaches your sign-up form.
Set up#
Create a server key#
In the console, open Integrations → SDK and create a server key. It is shown once: put it in
.env, never in code or in a page. Traqio keeps only a fingerprint of it; a lost key is replaced by a new one, and the old one revoked.TRAQIO_SECRET_KEY=tq_sk_...Install the package#
composer require traqio/traqio-phpUntil the package is on Packagist, add the folder we send you as a path repository:
# the folder you received, committed inside the backend's repository composer config repositories.traqio path ./vendor-src/traqio-php composer require traqio/traqio-php:@devConfigure the client#
Laravel: nothing to write — the service provider and the
Traqiofacade are discovered, and readTRAQIO_SECRET_KEY. To change the endpoint, the timeout or the retries, publish the config file:php artisan vendor:publish --tag=traqio-configPlain PHP: one client, built once.
use Traqio\Traqio; $traqio = new Traqio(getenv('TRAQIO_SECRET_KEY'));Report the sign-up with its trackID#
Call
signup()once, where the account is created. @traqio/web writes the trackID in thetraq_clickcookie on your own domain, so the browser sends it with the sign-up request;TrackId::fromRequest()reads it.use Illuminate\Http\Request; use Traqio\Laravel\Facades\Traqio; use Traqio\TraqioException; use Traqio\TrackId; public function store(Request $request) { $user = User::create($request->validated()); try { // Reads the raw Cookie header: Laravel's cookie decryption would drop traq_click. Traqio::signup((string) $user->id, TrackId::fromRequest($request)); } catch (TraqioException $e) { report($e); // never fail the sign-up itself } return redirect('/welcome'); }use Traqio\TrackId; $user = $users->create($_POST); $result = $traqio->signup((string) $user->id, TrackId::fromRequest()); // the current requestIn Laravel, to keep the call out of the request, run it after the response is sent:
dispatch(fn () => Traqio::signup((string) $user->id, $trackId))->afterResponse();If the backend is on another domain than the site, the cookie does not reach it: send
Traqio.getTrackId()from the browser with the form instead. The sign-up is the moment the user is acquired; the last click before it, inside your attribution window, gets the credit.Track the conversions#
Traqio::track((string) $user->id, 'purchase', [ 'value' => 49, 'currency' => 'EUR', 'transactionId' => $paymentIntent->id, // one payment, one row — whoever reports it 'store' => 'stripe', ]);Conversions follow the user: they are credited to the campaign that acquired them.
What the response tells you#
$result = Traqio::signup((string) $user->id, $trackId);
$result->trackId; // "matched" | "unknown" | "expired" | "claimed" | "refused" | null
$result->acquired; // true: the sign-up is recorded
$result->attribution->network; // "meta", "tiktok"… or "organic"
$result->attribution->campaignId; // the campaign that brought the user| trackId | Meaning |
|---|---|
matched | The click is now this user's. A retry answers matched again. |
unknown | No click of this product has that id — a typo, another product's link, or a value @traqio/web did not write. |
expired | The click is older than the product's attribution window. |
claimed | Another user, or an app install, already owns that click. A click credits one identity. |
refused | The click failed an anti-fraud rule (it came through an anonymising network). It is counted, never credited. |
What it does today, and what it does not#
| Today | |
|---|---|
| Tie a sign-up to its click, and say who was credited | Yes — in the response |
| Record purchases with value, currency and payment id, deduplicated | Yes |
| Leave your apps' installs, revenue and ROAS untouched | Yes — never attached to an app install |
| Show web conversions in the console's reports | Yes — per campaign, beside installs |
| Send web conversions to Meta or TikTok | Not yet |
Conversions sent here appear in Live events, marked as not matched to an install — expected for a website. A sign-up credited to a campaign is counted in the ROAS report as a web conversion of that campaign, beside its installs, and the response of each call gives you the attribution at once.
Errors and retries#
A network error, a timeout, a 429 or a 5xx is retried — twice by default, with the same body, so nothing is counted twice. A 4xx (wrong or revoked key, invalid payload) is not retried. Both end as a Traqio\TraqioException:
use Traqio\TraqioException;
try {
Traqio::signup((string) $user->id, $trackId);
} catch (TraqioException $e) {
// $e->status: 401 = wrong or revoked key, 400 = invalid payload, null = network
Log::warning($e->getMessage(), ['status' => $e->status, 'body' => $e->body]);
}Report after your own write
API reference#
// config/traqio.php
return [
'secret_key' => env('TRAQIO_SECRET_KEY'), // required
'endpoint' => env('TRAQIO_ENDPOINT', 'https://api.traqio.app'),
'timeout' => (float) env('TRAQIO_TIMEOUT', 10), // seconds per attempt
'max_retries' => (int) env('TRAQIO_MAX_RETRIES', 2), // network error, 429, 5xx
];| Method | What it does |
|---|---|
signup($userId, $trackId = null, $events = []) | Reports the sign-up — the acquisition — with its trackID. The first one is kept. |
track($userId, $name, $properties = []) | Records one conversion: value, currency, transactionId, store, eventId, trackId. |
send($input) | The raw call behind both, for several events at once. |
TrackId::fromRequest($request = null) | The trackID from a Laravel, Symfony or PSR-7 request — or the current PHP request — or null. |
In plain PHP the same options are the constructor's second argument: endpoint, timeout, max_retries, and transport to route requests through your own HTTP client.
Proof so far
matched with its campaign, a second user claiming the same click was told claimed, a wrong key was refused. Not yet run in a customer's production backend.Frequently asked questions#
- Why does $request->cookie('traq_click') return null in Laravel?
- Laravel encrypts its cookies and decrypts them on every request; a cookie written by JavaScript — the one @traqio/web sets — fails that decryption, so Laravel hands back null. TrackId::fromRequest reads the raw Cookie header instead, which the decryption never touches. You do not need to add traq_click to the EncryptCookies exceptions.
- Which Laravel versions are supported?
- Laravel 12 and 13, through package auto-discovery: the service provider, the config file and the Traqio facade register themselves. The client itself is plain PHP 8.1+ and works in Symfony, Slim or any PSR-7 application.
- What if the visitor did not come from an ad?
- TrackId::fromRequest returns null and the sign-up is still recorded, as organic: no campaign is credited.
- Will a retry count a purchase twice?
- No. Each call mints its event ids once and retries the identical body, and a purchase is also keyed on its transactionId, so the same payment reported twice is stored once.