You compare what your site sends with what an analytics platform receives by following one event through four layers: the source (dataLayer or SDK call), the request that left the browser or app, what your server forwarded, and what the destination shows. Each layer proves one handoff. None of them proves the whole path. This post is part of the web analytics audit series and covers the comparison itself: how to line the records up, what each one can prove, and the readings that send teams in the wrong direction.
Four layers, four different claims
A green check in one tool proves one thing. Write down which layer you are looking at before you draw a conclusion.
| Layer | Where you read it | What it proves | What it doesn’t prove |
|---|---|---|---|
| Source | dataLayer push, SDK call, order record | The action happened and which values the implementation had | That a tag fired or a request left |
| Request | Browser Network panel, app proxy, HAR file | What this client sent, to which endpoint, with which payload | That a server forwarded it or a platform processed it |
| Server | sGTM logs, middleware, API client logs | What your server sent and how it changed the event | That the destination used it in reports or deduplication |
| Destination | Debuggers, test views, reports, exports | What the platform shows it received or processed | The original values or the changes along the way |
The last row varies the most. Some platforms expose a validation response, some a real-time debugger, some only processed reports. A platform can accept a request and later normalize, delay, deduplicate, or drop it.
Pick one event and one key
Comparing everything at once turns into an argument about totals. Start with a single event that matters, such as purchase or generate_lead, and write down what it should look like before you test:
Action: a completed order
Source event: purchase
Key: order_id, sent as transaction_id to GA4 and event_id to Meta
Required: value, currency, items
Consent: advertising requests only with ad consent
Destinations: GA4, Meta Pixel, Meta CAPI
The key is what makes the comparison possible. An order_id, transaction_id, or event_id that survives every layer lets you match records one to one. Without it you can still compare counts, but a gap in counts can’t tell you whether an event was dropped, duplicated, retried, delayed, or sent to the wrong property.
If the key changes name between layers, document the mapping. order_id in the dataLayer, transaction_id in GA4, and event_id in Meta are the same order only if your notes say so.
Line the records up field by field
Capture the same event at each layer and put the values side by side. A short table is enough:
| Field | Source | Request | Server | Destination |
|---|---|---|---|---|
| Event name | purchase | purchase | Purchase | Purchase |
| Key | 8472 | 8472 | order-8472 | order-8472 |
| Value | 149.00 | 149 | "149.00" | 149 |
| Currency | USD | USD | USD | USD |
| Consent | ads granted | ads granted | ads granted | n/a |
| Time | 14:02:11 | 14:02:11 | 14:02:13 | 14:04 |
Then read each row from left to right, looking for four kinds of difference:
- Missing. A field, or the whole event, is present at one layer and absent at the next. That is where the drop happened.
- Changed. A name, a type, a unit, or a format differs. Number to string, cents to units, seconds to milliseconds, and renamed or nested properties all live here.
- Extra. The event appears more times downstream than upstream. One action, two requests, or a browser and a server event that don’t share the deduplication key.
- Late. The values match but the times don’t. Batching, retries, queues, and processing delay all show up as a gap in the last column.
Some differences are intentional: a server that hashes an email or renames a field for a platform is doing its job. The comparison tells you where the event changed. Your expected definition tells you whether it should have.
Once a row points at a problem, the fix belongs to a more specific playbook. For duplicates between browser and server, see CAPI deduplication issues and duplicate conversion tracking. For a conversion that doesn’t match the business result, see conversion tracking validation.
Read each tool for what it shows
Most mistakes in this comparison come from asking a tool a question it can’t answer. These are the readings to watch.
A 2xx is not a receipt. GA4’s Measurement Protocol reference says the endpoint returns a 2xx when the HTTP request is received, and doesn’t return an error when the payload is malformed or the data isn’t processed. A green status in your server log proves the request was accepted, nothing more.
To check the payload itself, send it to the validation server at /debug/mp/collect and read the validationMessages. Two limits apply: events sent there never appear in reports, and a clean validation response proves the payload passed that check, not that the production event will show up.
DebugView is not a request log. GA4 DebugView shows the events Analytics collected from a device in debug mode, in near real time. It needs debug mode, and consent settings or privacy controls can keep events out of it. An empty DebugView doesn’t prove nothing was sent, and a full one doesn’t prove all production traffic behaves the same way. For what left the page, use the Network panel in Chrome DevTools; the GTM debugging guide walks through it.
A canceled request is not always a lost one. When a click navigates to a new page, the Network panel often marks the analytics request as canceled. Adobe’s debugging documentation notes that the browser can stop waiting for the response after the request has already gone out. Turn on Preserve log, check whether the request was sent with sendBeacon or keepalive, and confirm on the server or in the destination before you call it a drop.
A test view is not production. Meta’s Test Events, GA4 DebugView, and Adobe’s debuggers all show a controlled session: your browser, your consent choice, your route. They prove the path you ran. They don’t cover other browsers, app versions, regions, consent states, or the journeys nobody tested. The Meta Pixel and CAPI debugging tools post goes tool by tool for Meta.
A missing request can be the right answer. If the visitor denied advertising consent, no Meta request is the expected result. Compare against the policy, not against “every tag fires.” The comparison has to say, for each consent state you test, which requests should exist and which shouldn’t.
After the controlled test ends
A manual comparison answers the question for one session, on the day you ran it. The next release, a new browser version, or a change in a server-side template can change the answer without anyone noticing.
That is the gap Trackingplan covers. It listens to the requests your website, apps, and server-side setup already send to each platform, so the request and server layers above are captured on real traffic, not on a test session. From there it learns how each event and property normally looks and flags the differences: a property that went missing, a value that changed type, an event that started firing twice, a vendor firing before consent. You install it with one tag, an SDK, or a server-side hook.
It doesn’t replace the destination’s own tools. What a platform did with an event after receiving it (deduplication, modeling, attribution) is only visible in that platform. Use its debugger or test view for that layer, and use real-traffic monitoring to know the other layers keep matching after the test.
A short checklist
Before you say an event reaches its destination correctly, check that:
- The expected event is written down: action, name, key, required fields, consent rule, and destinations.
- One key survives every layer, and its name mapping is documented.
- Source, request, server, and destination values were compared field by field.
- Every difference is either explained by a rule or logged as a defect.
- No success code, debugger, or test view was read as more than it proves.
- Each consent state you support was tested, including the ones where no request should exist.
- The same comparison was run again after the fix, with the same event and the same states.
What your site sends and what a platform receives are two records. The work is keeping them lined up, one layer at a time, and knowing which claim each layer is allowed to make.