Skip to main content
Van Bo5E68
Level 1
September 20, 2026
New

Anatomy of an Adobe Analytics hit (AppMeasurement vs Web SDK)

  • September 20, 2026
  • 2 replies
  • 42 views

 

If you've ever opened your browser's network tab while debugging an Adobe
Analytics implementation, you've seen it: a long, ugly request to something
like /b/ss/examplecompanyprod/1/H29-abc123 or /ee/v1/interact, packed
with query params or a JSON body nobody memorizes on purpose. Tools like
Omnibug or Adobe's own
Experience Platform Debugger will decode that for you -- nicely, even --
so you can eyeball it and go "yep, that looks right."

That's the thing, though: eyeballing it is the whole workflow. There's no
step where a machine checks the hit and fails a build if it's wrong. You
ship a change, the tag either still works or it doesn't, and you find out
when an analyst asks why last week's numbers look weird.

This post is about what's actually inside these two hit shapes, and why I
ended up writing a parser (and then a few more tools) instead of just
living with the debugger-and-eyeball loop.

Two generations

Adobe Analytics tracking comes in two flavors depending on what's
implemented:

AppMeasurement -- the older, still extremely common library. Fires as
either a GET image request or a POST, always to
/b/ss/{reportSuiteId}/{version}/{requestType}, with the actual data as
query-string (or form-body) params.

Web SDK (Alloy) -- the newer Experience Platform-based approach. Fires
POST to */ee/{something}/interact or /collect, with a JSON body
shaped like an XDM event, and (if you're using classic Analytics reporting
alongside it) an __adobe.analytics block tucked inside data.

They look nothing alike on the wire. Here's the same "user viewed the
checkout confirmation page and fired a purchase event" hit, in both shapes.

AppMeasurement

https://metrics.example.com/b/ss/examplecompanyprod/1/H29-abc123
?pageName=checkout-confirmation
&g=https%3A%2F%2Fwww.example.com%2Fcheckout%2Fconfirmation
&events=purchase
&v12=checkout
&c3=checkout-flow
  • pageName -- the page name, straightforward.
  • g -- the current page URL, URL-encoded.
  • events -- comma-separated success events. purchase here is a custom
    event; it could just as easily be event5 or event5=29.99 (that =
    sets a numeric value) or event5:abc123 (that : is a dedup/
    serialization id instead -- easy to confuse with =, and I've seen
    parsers get this wrong).
  • v12 -- eVar 12. The v/c prefix plus a number is not the same as
    the JS variable name (s.eVar12) -- it's the wire key, and Adobe's docs
    list them on a different page than the one most people find first.
  • c3 -- prop 3, same story.

Numbered eVars/props are the part that trips people up long-term: v12
means nothing on its own. What it represents -- "checkout step," "search
term," whatever -- is configured server-side, per report suite. The wire
format has no idea; it's just a slot.

Web SDK

POST https://edge.adobedc.net/ee/v1/interact?configId=fake-datastream-id
{
"events": [
{
"xdm": { "eventType": "web.webpagedetails.pageViews" },
"data": {
"__adobe": {
"analytics": {
"pageName": "checkout-confirmation",
"events": ["purchase"],
"contextData": {
"checkout.step": "confirmation"
}
}
}
}
}
]
}

No numbered eVars or props on the wire at all. Web SDK sends XDM plus
free-form context data; the mapping from checkout.step (or whatever key
you choose) to an actual Analytics dimension happens in the datastream
configuration, server-side, same as the eVar-slot problem above but now
even further from the client. If you're migrating from AppMeasurement to
Web SDK, this is the part that's genuinely customer-specific -- there's no
universal prop3 -> XDM field table, because the mapping doesn't exist
until someone configures it.

Where a parser earns its keep

Once you've decoded a few hundred of these by hand, the actual interesting
question stops being "what does this hit say" and becomes "does this hit
say what it's supposed to say, every time, in CI, without a human opening
a debugger." That's a different job than Omnibug's, not a replacement for
it -- Omnibug is still what I reach for while I'm building a tag. This
is for after that, when I want "did this still work" to be a test that
fails the build instead of a question someone has to remember to ask.

That's beacon-parser: a
zero-dependency TypeScript library that turns either hit shape above into
one normalized, typed object.

import { parseHit } from "@bonv/beacon-parser";

const hit = parseHit({ url: appMeasurementUrl });
// hit.kind === "appmeasurement"
// hit.pageName, hit.events, hit.eVars["12"], hit.props["3"]

Same function, same output shape, for a Web SDK hit -- hit.kind just
comes back "websdk" instead, with hit.events[].analytics carrying the
pageName/events/contextData block. It never throws on something it
doesn't recognize either; unknown fields land under raw/unknown
instead of getting silently dropped or crashing your test run.

On top of that:

  • @bonv/beacon-playwright
    captures hits during a real Playwright test and gives you matchers like
    toHaveAdobeEvent("purchase") and toHaveEvar(12, "checkout").
  • @bonv/tracking-plan
    lets you define what a flow is supposed to send as a plain TypeScript
    object, then validate captured hits against it with structured pass/fail
    reasons instead of a wall of individual expect() calls.
  • @bonv/beacon-cli
    runs that plan against real flows in a real browser and exits non-zero
    on failure -- the part that actually plugs into CI.

If you want to see hit decoding without installing anything, there's a
playground that runs the
same published package entirely in your browser. And if you've never
touched Adobe Analytics at all and want to see what a browser actually
sends without needing an Adobe account, there's a
learning lab -- a fake shop with a live
inspector and three guided exercises.

Everything here is client-side only, on purpose. None of it verifies what
ends up in an Adobe report -- server-side processing rules, VRS, and
report-suite configuration are invisible to a browser, so they're invisible
to this too. It answers one narrower question reliably: did the browser
send what it was supposed to send.
 That turns out to be most of what
breaks in practice.

2 replies

LizzieSc
Adobe Champion
Adobe Champion
September 21, 2026

Really like this! Like the idea on the shift from ‘someone quickly debugged and it looked ok’ to actually being able to test tracking properly before it becomes a (n analyst) problem later on. We all know all to well that many implementation issues aren’t big obvious breaks, they’re the annoying ones where a value changes, a dimension stops populating, or an event quietly behaves differently, and nobody really notices until the reporting looks funky a bit later.

I was curious about the Web SDK side though. How are you handling implementations that populate data.__adobe.analytics directly with eVars / props, rather than mainly relying on XDM or context-data mappings? I assume that gives you another flavour of Web SDK hit to account for.

Also really interested in the tracking plan piece!! In practice, do you see that becoming part of the analytics spec/data contract, and who do you think should actually own keeping it up to date?

Van Bo5E68
Level 1
September 21, 2026

Thank you!! Really glad this resonated!

The quiet failures you describe (value change, a dimension stop populating..) are exactly the target, since they usually only show up later as odd reporting.

On data.__adobe.analytics: it's a separate shape from XDM-mapped hits. The parser reads that block alongside the XDM and keeps unrecognised fields as raw rather than dropping them. Caveat: it only sees the client payload, not how server-side mappings turn it into report variables. An anonymised example payload would be a great fixture if you can share one.

On the tracking plan: an early, experimental version now exists (@bonv/tracking-plan). You define the plan as a typed object and validate captured hits against it, and you get structured pass/fail reasons rather than one assertion per field. It's still a first iteration, so I'd value feedback on the shape. On ownership I still lean towards shared: analytics accountable for what the plan means, developers responsible for keeping it in sync with the code in the same change.

Two other things that might be useful: a small free lab to practise implementation with no Adobe account (fake shop, live hit inspector, three exercises, and a tab to decode any hit in the browser), and a diff helper for comparing two sets of hits, aimed at tag migrations such as AppMeasurement to Web SDK.

Lab: https://lab.averosi.com
Repo: https://github.com/bonguynvan/beacon-parser

Independent project, not affiliated with Adobe. It only decodes what the browser sends and doesn't verify what appears in reports.

Curious: in your team, who owns the tracking plan today, and where does it live (spreadsheet, Confluence, repo)?

Feedback on the tracking plan shape would be very welcome. Where does yours live today?