Skip to content

Navigation Menu

Sign in
Sign up

Latest commit

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Decisa PHP SDK

The official PHP SDK for the Decisa server-side ingest API. Record conversions (sales, signups, leads, refunds...) and events (PageView, Purchase...) from your backend with a typed, framework-agnostic client.

  • Framework-agnostic — built on PSR-18 / PSR-17, auto-discovers your HTTP client. Works in plain PHP, Laravel, Symfony, Slim, anywhere.
  • Money guardvalue_cents is integer-only; passing a decimal is rejected before it ever hits the wire (the #1 install footgun).
  • Typed errors — every API failure maps to a specific exception you can catch.
  • Safe retries — automatic exponential backoff with jitter on network errors, 5xx, and 429 (honoring Retry-After). Duplicate sends are idempotent, so retries can never double-count.
  • Secret-safe — your API key is kept private and never appears in an exception message, log line, or __toString().

Requirements

  • PHP 8.2+
  • A Decisa API key (starts with dcs_ak_) with the conversions:write and/or events:write scope.

Install

composer require decisa/decisa-php

The SDK depends only on PSR HTTP abstractions and auto-discovers a PSR-18 client. If your project doesn't already ship one, add Guzzle (or any PSR-18 client):

composer require guzzlehttp/guzzle

Quickstart — record a sale

use Decisa\Client;
$decisa = new Client(getenv('DECISA_API_KEY')); // dcs_ak_...
$result = $decisa->conversions()->record([
 'type' => 'sale',
 'externalId' => 'order-1001', // your idempotency key — required
 'valueCents' => 1999, // R19ドル.99 as INTEGER cents
 'currency' => 'BRL',
 'visitorId' => $_COOKIE['dcs_vid'] ?? null,
 'customerEmail' => 'buyer@example.com', // hashed server-side, raw never stored
]);
$result->id(); // "conv_abc123"
$result->externalId(); // "order-1001"
$result->valueCents(); // 1999
$result->attributed(); // true if Decisa matched it to a click/campaign
$result->raw(); // full decoded `data` payload

Use the cents() helper for major-unit amounts

value_cents must always be an integer. To convert a decimal price safely, use Money::cents() (rounds half-up):

use Decisa\Money;
Money::cents(19.99); // 1999
Money::cents('49.50'); // 4950
$decisa->conversions()->record([
 'type' => 'sale',
 'externalId' => 'order-1002',
 'valueCents' => Money::cents($order->total), // never pass the float directly
 'currency' => 'USD',
]);

Passing a float straight into valueCents is rejected before any network call:

$decisa->conversions()->record([
 'type' => 'sale',
 'externalId' => 'order-1003',
 'valueCents' => 19.99, // throws Decisa\Exception\InvalidArgumentException
]);

Typed input objects (optional)

Arrays are ergonomic, but you can build fully typed input with enums:

use Decisa\Input\ConversionInput;
use Decisa\Enum\ConversionType;
use Decisa\Money;
$input = new ConversionInput(
 type: ConversionType::Sale,
 externalId: 'order-1004',
 valueCents: Money::cents(19.99),
 currency: 'BRL',
);
$decisa->conversions()->record($input);

Recording events

use Decisa\Client;
$decisa = new Client(getenv('DECISA_API_KEY'));
$decisa->events()->record([
 'eventId' => 'evt-9f3a1c20', // your idempotency key — required, 8..64 chars
 'eventName' => 'Purchase', // PageView, ViewContent, AddToCart, Lead, Purchase, ...
 'valueCents'=> 4999,
 'currency' => 'USD',
 'visitorId' => $_COOKIE['dcs_vid'] ?? null,
 'url' => 'https://shop.example.com/thank-you',
]);

Events accept multiple pixel keys. Pass an array and the SDK joins it into the comma-separated string this endpoint expects:

$decisa->events()->record([
 'eventId' => 'evt-aa11bb22',
 'eventName' => 'PageView',
 'pixelKeys' => ['pk_main', 'pk_remarketing'], // sent as "pk_main,pk_remarketing"
]);

Note: /v1/conversions takes pixel_keys as a JSON array, while /v1/events takes it as a comma-separated string. The SDK handles each correctly for you — always pass an array.

Error handling

Every failure maps to a typed exception. All extend Decisa\Exception\DecisaException and carry ->httpStatus(), ->code(), ->message, and ->requestId() (when the server provides one).

use Decisa\Exception\DecisaValidationException;
use Decisa\Exception\DecisaAuthException;
use Decisa\Exception\DecisaForbiddenException;
use Decisa\Exception\DecisaRateLimitException;
use Decisa\Exception\DecisaServerException;
use Decisa\Exception\DecisaException;
try {
 $decisa->conversions()->record([
 'type' => 'sale',
 'externalId' => 'order-1005',
 'valueCents' => 1999,
 'currency' => 'BRL',
 ]);
} catch (DecisaValidationException $e) {
 // 422 — show the per-field messages back to the operator.
 foreach ($e->fields() as $field => $messages) {
 echo "$field: " . implode(', ', $messages) . PHP_EOL;
 }
} catch (DecisaAuthException $e) {
 // 401 — the API key is missing, malformed, unknown, revoked, or expired.
} catch (DecisaForbiddenException $e) {
 // 403 — valid key, but not allowed. Use ->code() to tell which:
 if ($e->code() === 'INSUFFICIENT_SCOPE') {
 // the key lacks conversions:write / events:write
 } elseif ($e->code() === 'ENTITLEMENT_MISSING') {
 // the workspace hasn't activated attribution
 // $e->details()['capability'] === 'attribution'
 }
} catch (DecisaRateLimitException $e) {
 // 429 — retries were exhausted. Back off and retry later.
 $waitSeconds = $e->retryAfter(); // ?int, from the Retry-After header
} catch (DecisaServerException $e) {
 // 5xx — already retried; treat as transient and queue for later.
} catch (DecisaException $e) {
 // catch-all (e.g. a network error after retries).
}
Exception HTTP Notes
DecisaValidationException 422 ->fields() returns ['<field>' => ['msg', ...]]
DecisaAuthException 401 key missing / invalid / revoked / expired
DecisaForbiddenException 403 ->code() is INSUFFICIENT_SCOPE or ENTITLEMENT_MISSING
DecisaRateLimitException 429 ->retryAfter() returns ?int seconds
DecisaServerException 5xx transient
InvalidArgumentException client-side guard (e.g. a float value_cents); thrown before the wire

Retries & idempotency

The SDK automatically retries on network errors, 5xx, and 429 with exponential backoff plus jitter (honoring the Retry-After header on 429). It never retries other 4xx responses — those are deterministic client errors.

This is safe because the API is idempotent:

  • Conversions dedupe on external_id. Re-sending the same external_id returns the stored record and still responds 201 — a duplicate is not an error.
  • Events dedupe on event_id. Re-sending the same event_id returns the stored record and still responds 202.

So you can safely retry a request whose response you never received without double-counting. Always reuse a stable external_id / event_id per logical event.

Configuration

use Decisa\Client;
$decisa = new Client('dcs_ak_...', [
 'base_url' => 'https://api.decisa.ai', // default
 'timeout' => 10.0, // seconds, default 10
 'max_retries' => 2, // default 2
 // Optional: inject your own PSR-18 client / PSR-17 factories.
 // 'http_client' => $psr18Client,
 // 'request_factory' => $psr17RequestFactory,
 // 'stream_factory' => $psr17StreamFactory,
]);

Testing against the SDK

Inject a PSR-18 client (for example Guzzle's MockHandler) so no real network calls are made:

use Decisa\Client;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
$mock = new MockHandler([
 new Response(201, [], json_encode([
 'data' => ['conversion' => ['id' => 'conv_test', 'external_id' => 'order-1', 'attributed' => false]],
 'meta' => null,
 'error' => null,
 ])),
]);
$guzzle = new GuzzleClient(['handler' => HandlerStack::create($mock), 'http_errors' => false]);
$decisa = new Client('dcs_ak_test', ['http_client' => $guzzle]);
$result = $decisa->conversions()->record(['type' => 'sale', 'externalId' => 'order-1']);

Laravel bridge (planned)

A Laravel bridge (service provider + facade for auto-wired config and a Decisa::conversions() facade) is a planned fast-follow. The core SDK here stays framework-agnostic; the bridge will be a thin, optional package on top of it.

License

MIT. See LICENSE.

About

Official PHP SDK for the Decisa ingest API (conversions + events), PSR-18 framework-agnostic

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /