Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Meta Conversions Api Bundle Laravel Package

setono/meta-conversions-api-bundle

Symfony bundle for integrating the Meta Conversions API, enabling reliable server-side conversion tracking. Configure your pixel credentials and send events directly from your application to complement browser tracking with more resilient, privacy-conscious server events.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire it up

composer require setono/meta-conversions-api-bundle

If you're not on Symfony Flex, add the bundle to config/bundles.php manually. Then create a minimal config file. Exact keys vary by version, so confirm them first:

bin/console debug:config setono_meta_conversions_api
bin/console config:dump-reference setono_meta_conversions_api

A typical configuration looks like this:

# config/packages/setono_meta_conversions_api.yaml
setono_meta_conversions_api:
    pixel_id: '%env(META_PIXEL_ID)%'
    access_token: '%env(META_ACCESS_TOKEN)%'
    test_event_code: '%env(default::META_TEST_EVENT_CODE)%'

Keep the token in .env.local / a secret store, never in the committed YAML.

Where to look first

  1. vendor/setono/meta-conversions-api-bundle/README.md and src/DependencyInjection/Configuration.php — the README is the source of truth for config keys; the Configuration class tells you what's actually supported.
  2. The service definition in src/Resources/config/services.xml — find the client service ID (it will be a Setono\MetaConversionsApiBundle\...\ClientInterface or similar). Confirm with:
bin/console debug:container --show-private | grep -i meta
  1. The underlying setono/meta-conversions-api library in vendor/ — that's where the Event, UserData, CustomData, and content classes live. The bundle is mostly DI glue around it; most of your day-to-day work is with those value objects.

First use case: a server-side Purchase

The canonical win is sending a Purchase event from your checkout success controller (or an order subscriber) so it deduplicates against the browser pixel. Build the event once, give it an event_id, and pass the same event_id to the browser-side fbq('track', 'Purchase', {...}, {eventID: '...'}) call.

public function __construct(private ClientInterface $metaClient) {}

public function onOrderPaid(Order $order): void
{
    $event = new Event(
        eventName: 'Purchase',
        eventTime: $order->getPaidAt()->getTimestamp(),
        eventId: 'order-'.$order->getId(),   // must match browser pixel
        actionSource: 'website',
        userData: (new UserData())
            ->setEmails([$order->getCustomerEmail()])
            ->setClientIpAddress($ip)
            ->setClientUserAgent($ua)
            ->setFbc($fbcCookie)
            ->setFbp($fbpCookie),
        customData: (new CustomData())
            ->setCurrency('USD')
            ->setValue(99.00),
    );

    $this->metaClient->sendEvents([$event]);
}

Implementation Patterns

Hook into domain events, not controllers. A Doctrine listener on order state transitions or a Symfony event subscriber on your OrderPaidEvent keeps the Meta call out of HTTP code and makes it reusable for refunds (Refund), subscription renewals, and admin-created orders. Avoid postFlush hooks that make network calls inside the DB transaction.

Send asynchronously with Messenger. Checkout latency shouldn't depend on Meta's API. Serialize the event DTO into a message and dispatch it to a queue:

$this->bus->dispatch(new SendMetaEventMessage($event));

If the library's Event object isn't cleanly serializable, keep a small DTO with scalars (event name, order id, value, currency, hashed PII, fbp/fbc) and rebuild the Event in the handler. This also gives you retries and a dead-letter queue for free.

Decorator around the client. Wrap the bundle client in your own service that implements the same interface, and decorate it:

App\Meta\LoggingClient:
    decorates: Setono\MetaConversionsApiBundle\...\ClientInterface

Use this for: no-op in dev/test, tagging the test_event_code in staging, logging request/response payloads, and swallowing failures so a marketing API never breaks checkout.

Always send deduplication identifiers. Meta deduplicates browser + server events by (event_name, event_id) within ~48 hours. Generate an event_id at the point where the browser pixel fires (order id, cart token, UUID in a data attribute) and reuse it server-side. If you can't share it, at least send fbp/fbc and consistent event_time.

Capture request context at the edge. fbp, fbc, client IP, and user agent only exist on the inbound HTTP request. Read them in the controller/subscriber, put them in the message payload, and send them later. This is the single most common source of "event received, no match quality" complaints.

Consent gating belongs to you. The bundle won't know about your CMP. Gate on your consent service before dispatching, and decide policy for action_source (website vs system_generated for back-office or offline orders).

Multi-dataset setups. If you run several pixels/datasets, don't hack the single global config. Instantiate additional clients from the underlying library directly (or a small factory service) and register them by name. The bundle's config is designed for the common single-dataset case.

Testing. In test env, decorate the client with one that records events in memory, and assert on it from a functional test after hitting checkout. Keep test_event_code set in staging so events land in Events Manager's Test Events tab instead of polluting production reporting.

Gotchas and Tips

  • HTTP 200 does not mean success. The Conversions API returns 200 OK with events_received: 0 or a messages array containing errors (bad token, expired event_time, invalid currency, unknown field). Log the decoded body, and assert on events_received > 0 in your decorator at least in staging.
  • Hashing is on you (usually). Meta requires SHA-256 of normalized emails/phones/names. Check whether the library hashes UserData for you — read the source, don't assume. Double-hashing produces silently unmatched events, which is worse than an obvious error.
  • Event time must be recent. Events older than ~7 days are rejected. Backfilling historical orders is a one-shot migration task, not a recurring one, and requires action_source: system_generated.
  • Token leakage. Access tokens pasted into YAML, stack traces, or the Symfony profiler are a real incident. Mark the client service's arguments as sensitive, keep APP_DEBUG=0 in prod, and use a system-user token with the minimum dataset scope rather than a personal token.
  • Async workers lose cookies. If you send from a Messenger consumer, $requestStack->getCurrentRequest() is null. Capture context in the producer. This is the #1 bug in every CAPI integration.
  • Batching has limits. Up to 1,000 events per request; batch in the worker if you backfill, but keep individual retries small so one bad event doesn't poison a payload. Chunk to ~100 for sane retry granularity.
  • Currency and value types. Send ISO-4217 uppercase currency and a numeric value; strings with commas or symbols fail validation quietly.
  • Proxy/HTTP client config. Behind a proxy, configure the underlying symfony HttpClient/PSR-18 client. The bundle usually exposes an overridable HTTP client service — decorate or alias it rather than fighting SSL settings in YAML.
  • Debugging checklist. debug:config for resolved config → debug:container for the real client ID → debug:event-dispatcher to confirm your subscriber runs → log the raw response body → check Events Manager's Test Events tab and the "Diagnostics" panel, which reports deduplication and match-quality issues.
  • Assess the dependency before betting on it. This package has a single star and no dependents. Pin an exact version in composer.json, read the bundle source end-to-end (~a few files, it's glue code), and be ready to vendor it or bypass it. The escape hatch is easy: require setono/meta-conversions-api, build the client yourself from config, and drop the bundle — the bundle only saves you a dozen lines of DI.
  • Symfony version drift. Verify the released version against your Symfony major. With a low-traffic package, a constraint bump can lag. Prefer the underlying library directly if you're on a very new or very old Symfony.
  • Extension points worth knowing. The client interface (decorate), the Messenger transport (route to a failure transport), and Symfony's http_client options (retry + timeout). Anything else — new event types, new user-data fields — is a change in the underlying library, so contribute there rather than forking the bundle.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings