# Verify and troubleshoot

Prove your tags are installed and your conversions arrive, and find the reason when they do not.

Tracking fails quietly. A tag that never loads, a conversion that never arrives and a visitor who refused cookies all look the same from inside your app: nothing happens, and no error is raised anywhere. This page is how you tell them apart.

## Prove the tags are installed

Open your app with devtools on the **Network** tab.

1. **Our configuration arrives.** Look for a request to `/api/v1/public/<org>/tracking`. It should answer `200` with your scripts. Empty means nothing is attached to this app's auth client - check **Auth › Clients**, not the tag library.
2. **The vendor's script loads.** `googletagmanager.com/gtag/js`, `connect.facebook.net/en_US/fbevents.js`, and so on. A request that never appears is usually the Content-Security-Policy; see [Providers](/tracking/providers).
3. **Consent comes first.** In the console, `window.dataLayer[0]` should be `["consent","default",{…all denied…}]`, before any Google tag.
4. **What we think is installed:** run `window.__buildbase.tracking` in the console. It lists every script the SDK actually mounted, and the app it mounted for.

## Prove the events arrive

| Vendor | Where to look                            | How to get there                                                         |
| ------ | ---------------------------------------- | ------------------------------------------------------------------------ |
| GA4    | Admin › DebugView, or Reports › Realtime | Install the Google Analytics Debugger extension, or pass `debug: true`   |
| GTM    | Preview                                  | Your container's Preview button - our events appear as data layer pushes |
| Meta   | Events Manager › Test events             | The Meta Pixel Helper extension for the browser side                     |
| Others | Each vendor's own live-events view       | -                                                                        |

> **Note:**
  **Checking GA4 in the Network tab takes two filters, not one.** GA4 collects
  on both `www.google-analytics.com/g/collect` and
  `analytics.google.com/g/collect`, and which one a page uses depends on its
  configuration - a property linked to Google Ads commonly uses the second.
  Filtering for the first alone shows an empty list while every hit is leaving
  normally. Filter on `/collect` instead.

Two more things make a correct GA4 hit look like a missing one. It batches:
several events queued together go in one **POST body**, so only the first
event name appears in the query string and the rest are invisible unless you
open the request. And its hits are sent as beacons, so a hit fired next to a
navigation is often reported as `(canceled)` or `ERR_ABORTED` in devtools -
which means the browser stopped waiting for a reply it never needed, not that
the event failed to send.



Set `tracking={{ debug: true }}` and the SDK logs every decision it makes to the console: what it installed, what it refused, what it fired and what it handed to your own callback.

```tsx
<SaaSOSProvider tracking={{ debug: true, onEvent: (e) => console.log(e) }}>
```

For the server-side half, **Settings › Tracking & Tags › Send a test conversion** shows what the vendor said, and the log below it lists every send with its reason. For GA4 the test also sends a live `buildbase_test` event, which carries no revenue and appears in DebugView - that is the only way to prove an API secret works, because GA4 answers `204` to a wrong one.

## When something is wrong

### No events at all

- **Nothing is attached to this app.** A script in the library loads nowhere until it is ticked on the auth client under **Auth › Clients**.
- **The id is missing.** A row with no measurement id or pixel id is a placeholder: it is deliberately not served, not installed and not named in your consent banner.
- **The visitor has not answered.** Marketing tags do not mount until consent is given. Analytics tags load in a denied state and send cookieless pings, which is Consent Mode working, not failing.
- **Your CSP blocks it.** The browser console says so once, as a `Refused to load` line.

### Events arrive, but conversions do not attribute

- **The click id never reached the sale.** It is captured on the landing page and kept in a cookie on your registrable domain. If your landing page and your app are different registrable domains - `abc.com` and `abc.io` - nothing carries across, and no ad platform will attribute the sale.
- **The buyer refused marketing.** Google's tags still count the visit; the ad network does not get the conversion. That is the correct outcome, not a bug.
- **You are looking at a new user.** A GA4 Measurement Protocol hit sent without the browser's client id starts a fresh user. We refuse to send one rather than do that - it appears in the log as `no GA4 client id for this buyer`.

### The same sale counted twice

Every event carries one id, shared by the browser and the server, which is what Meta deduplicates on. GA4 has no such mechanism, so we never send it a purchase your browser already reported. If you are seeing doubles:

- **You also have your own pixel on the page.** We refuse to install a tag whose id is already present, and say so in the debug log - but a second pixel with a _different_ id is a second pixel, and both will count.
- **You fire the event yourself as well.** Set that event to `manual` so only one of you sends it. See [Events](/tracking/events).
- **A renewal reused the sale's id.** If you send conversions from your own backend, a renewal needs an id of its own; reusing `tracking.eventId` makes every renewal look like the original sale.

### The numbers do not match the ad platform

They never match exactly, and the gaps have names: ad blockers drop browser hits, attribution windows differ, and an ad platform counts a conversion against the click's date while your database counts it against today. Compare trends, not totals, and use the server-side send to narrow the first gap.

### Our own visits show up

Tracking runs in development too, because a tag that only exists in production is a tag nobody has tested. Set `tracking={{ enabled: false }}` where you do not want it, or exclude your own traffic in the vendor.

## Next

- [Events](/tracking/events) - what fires, and what you send yourself.
- [Server-side conversions](/tracking/server-side) - the delivery log and its reasons.
- [Providers](/tracking/providers) - ids, CSP and per-vendor traps.
