Conversions fire before they're real
Tracking is often tied to a button click or a front-end success message. Validation failures, declined payments and duplicate submissions still count as conversions.
This is for teams running a custom Laravel app, such as a SaaS, marketplace, booking system or store, who need ad platforms to see real sign-ups, bookings and payments. There's no plugin to install, so tracking has to be designed into the app. I write the event spec, work with your developers on the dataLayer and server events, and set up GTM and server-side GTM to deliver them.
Proper Laravel tracking means a documented event spec, dataLayer pushes rendered from Blade or your frontend after an action succeeds, and server-side events sent from controllers, listeners or queued jobs once the backend confirms the conversion. GTM handles browser tags, and server events go to Meta CAPI, GA4 and Google Ads with shared IDs for deduplication.
The events I set up on Laravel, and where each one is sent.
Tracking is often tied to a button click or a front-end success message. Validation failures, declined payments and duplicate submissions still count as conversions.
A typical Laravel flow posts a form, then redirects. If the dataLayer push isn't carried across the redirect, for example in session flash data, the event never reaches the browser.
With Stripe, Paddle or similar, the real confirmation arrives as a webhook to your app, often after the customer has left. Browser-only tracking misses these, and subscription renewals never show up at all.
Server events need the GA4 client ID, Meta's fbp and fbc values and the gclid from the visit. If the app doesn't save them with the user or order, server events can't be tied back to ads.
Apps using Inertia, Livewire navigation or a separate Vue or React front-end change pages without a full reload, so page views and funnels need history-based tracking.
| Event | When it fires | Sent to |
|---|---|---|
sign_up / CompleteRegistration | Account created and saved by the backend | GA4, Google Ads, Meta Pixel + CAPI |
generate_lead / Lead | Form request validated and stored | GA4, Google Ads, Meta Pixel + CAPI |
begin_checkout / InitiateCheckout | Checkout or plan selection page loads | GA4, Meta Pixel |
purchase / Purchase | Payment confirmed by your payment provider's webhook | GA4, Google Ads, Meta CAPI, TikTok |
start_trial / StartTrial | Trial created in the app | GA4, Meta Pixel + CAPI |
subscription_renewed | Renewal webhook processed by a queued job | GA4 (server), Google Ads offline |
qualified_lead (offline) | Lead marked qualified or won in your admin | Google Ads, Meta CAPI |
We list the actions that matter to the business and where in the app each one is confirmed: a controller, a model event or a webhook.
I write the dataLayer and server event spec, including the IDs to store, so your developers know exactly what to build.
Your team adds the Blade pushes, stored IDs and queued jobs; I build the GTM and server-side GTM containers that receive them.
I test on staging and production, checking each event in GA4 DebugView, Meta Events Manager and Google Ads, and that duplicates are removed.
You get the spec, container notes and a checklist for adding new events as the app grows.
The simplest reliable pattern is to push events after the server has done its work. The controller saves the record, puts the event and its parameters in session flash data, and redirects. The next page renders a small Blade partial that writes the dataLayer push, and GTM picks it up. Because the push only appears after a successful save, failed submissions are never counted.
Each push includes an event ID generated on the server. The same ID is used in the matching server event, which is what lets Meta and other platforms deduplicate the two.
For conversions that matter most, such as payments, I have the app send a server event as well. A listener or queued job builds the payload with the stored GA4 client ID, Meta's fbp and fbc values, hashed customer details and the event ID, then sends it to server-side GTM. From there it goes to GA4 via Measurement Protocol, Meta CAPI and other platforms.
Queuing keeps requests fast and lets failed sends retry. It also means events that happen with no browser involved, like webhook-confirmed payments or renewals, still reach your ad platforms.
There are community packages, but I usually recommend a small amount of your own code plus server-side GTM. That keeps the app's job simple, one endpoint, while GTM handles the details of each platform.
For anything confirmed by the backend or a webhook, such as payments and renewals, yes. Browser tracking alone misses conversions that happen after the visitor leaves, and is affected by ad blockers and browser limits.
Use GA4's Measurement Protocol with your measurement ID, an API secret and the visitor's client ID from the _ga cookie. Sending it through server-side GTM instead lets you reuse the same event for Meta and other platforms.
Generate one event ID on the server and use it in both the dataLayer push and the server event. Meta deduplicates on that ID, and for GA4 I only send the server copy for events the browser can't see.
Yes, that's the usual setup. I write the spec and build the GTM side; your developers make the app changes. I review pull requests if you want a second pair of eyes on the tracking code.
What server-side tracking really is, how server-side GTM works, what it fixes and what it does not, and a checklist to decide if your business needs it.

Meta merges Pixel and server events only when the event name and event ID match. Here is how to set that up correctly and prove it works.
How to capture GCLID, store it in your CRM and send qualified leads and closed sales back to Google Ads so Smart Bidding optimises for real revenue.
Get a free audit of your tracking and ad account. Usually replies within 1–2 hours.