setono/meta-conversions-api-php-sdk
PHP SDK for the Meta Conversions API that lets you send server-side conversion events to Meta/Facebook with typed requests, event models, and a simple client for integrating online and offline conversions into PHP or Laravel apps.
No official Laravel wrapper exists; use the framework-agnostic SDK directly. Install the beta and pin it:
composer require setono/meta-conversions-api-php-sdk:^2.0@beta
Requirements are PHP 8.1+, a PSR-18 HTTP client, and PSR-17 request/stream factories. Laravel's Illuminate\Http\Client is not PSR-18, so add Guzzle and PSR-7 factories (guzzlehttp/guzzle, guzzlehttp/psr7) or bind another compliant implementation.
First read the README and UPGRADE-2.0.md — especially the new “stricter types” section — then bind the SDK client in a service provider using config values:
// config/services.php
'meta_conversions' => [
'pixel_id' => env('META_PIXEL_ID'),
'access_token' => env('META_ACCESS_TOKEN'),
'test_event_code' => env('META_TEST_EVENT_CODE'),
],
// AppServiceProvider::register()
$this->app->singleton(\Setono\MetaConversionsApi\Client::class, function ($app) {
return new \Setono\MetaConversionsApi\Client(
$app->make(\Psr\Http\Client\ClientInterface::class),
$app->make(\Psr\Http\Message\RequestFactoryInterface::class),
$app->make(\Psr\Http\Message\StreamFactoryInterface::class),
config('services.meta_conversions.pixel_id'),
config('services.meta_conversions.access_token'),
);
});
Your first use case should be a server-side Purchase event fired when an order is paid. Attach the _fbp/_fbc cookies captured at checkout and use a stable event_id that matches the browser pixel:
$event = Event::create('Purchase')
->setEventId('order-'.$order->id)
->setEventTime(new \DateTimeImmutable())
->setUser(
User::create()
->setEmail($order->email) // verify whether the SDK hashes; otherwise hash SHA-256
->setFbp($order->fbp) // raw string or Fbp object
->setFbc($order->fbc)
)
->setCustomData([
'value' => $order->total,
'currency' => 'USD',
]);
app(Client::class)->send($event);
Exact builder method names may differ; treat the README examples as the source of truth.
Queue the send. Do not block checkout or web requests on Meta's API. Create a job that accepts scalar identifiers and rebuilds the event inside handle(). This avoids serializing SDK objects and makes retries safe:
class SendMetaConversion implements ShouldQueue
{
public function __construct(
public string $eventName,
public string $eventId,
public array $userData,
public array $customData,
) {}
public function handle(Client $client): void
{
$event = Event::create($this->eventName)
->setEventId($this->eventId)
->setUser(User::create()->setEmail($this->userData['email'] ?? null))
->setCustomData($this->customData);
$client->send($event);
}
}
Persist cookie values early. Capture _fbp and _fbc in middleware or at checkout and store the raw strings on the order/user. Rebuild them later with Fbp::fromCookie() / Fbc::fromCookie(), or pass the strings directly to User::setFbp()/setFbc() — the typed properties accept Fbc|string|null and Fbp|string|null.
Use event IDs for deduplication. If the browser pixel also reports the same event, set the same event_id on both. Otherwise Meta may count duplicate conversions.
Multiple pixels. If the client supports an array of pixel IDs, be aware of the known limitation: when Meta rejects the event for one pixel, the client throws at that pixel and the remaining pixels are not attempted. Until #20 is resolved, consider sending per pixel in separate jobs or wrapping each pixel send in try/catch if you must fan out, and monitor the exception type before 2.0 stable.
Test events. Use a test event code outside production and verify delivery in Events Manager. Keep test codes out of production config.
Custom data and user data. Stick to Meta's expected keys (value, currency, content_ids, etc.). For PII, confirm whether the SDK normalizes/hashes automatically; if not, normalize and SHA-256 hash emails/phones before assigning.
Fluent immutable value objects. Fbp, Fbc, and Fb are immutable. Use withRandomNumber(), withCreationTime(), withSubdomainIndex(), and withAppendix() and reassign the result:
$fbp = $fbp->withRandomNumber(1234567890);
$fbc = $fbc->withCreationTime(new \DateTimeImmutable());
2.0.0-beta.3. Pin the exact version and read the changelog before each beta. The known multi-pixel exception behavior may change before 2.0 stable.Fbp::$randomNumber is now private: read it with getRandomNumber() and set it with withRandomNumber(). Assigning anything other than Fbc|Fbp|string|null to User::$fbc/$fbp throws \TypeError. Fb::withCreationTime() now accepts only int|\DateTimeInterface and throws \TypeError for other values — not InvalidArgumentException, and it does not implement ExceptionInterface, so catch \TypeError separately if validating user input.Http facade is not PSR-18. Install guzzlehttp/guzzle and guzzlehttp/psr7, then bind ClientInterface, RequestFactoryInterface, and StreamFactoryInterface in the container. If you use another PSR-18 client, bind its interfaces explicitly._fbp and _fbc are first-party cookies and may be absent due to consent, ad blockers, or browser privacy settings. Handle null gracefully and still send the event. Do not fail the job because a cookie is missing.event_id for browser and server events. Generating a new UUID per send will create duplicate conversions.Throwable in the queued job and log the event ID plus Meta's response. Retry 5xx/network errors with backoff, but do not retry 4xx validation errors indefinitely. Never let a Meta failure roll back an order.handle(). If you must serialize an event, test that it survives serialize()/unserialize().Middleware::log) in local/staging to inspect requests, but redact access_token and PII. The SDK may not expose the final payload.UPGRADE-2.0.md; the stricter-types section matters if you previously assigned arbitrary values to User::$fbc/$fbp or read $fbp->randomNumber directly.How can I help you explore Laravel packages today?