Where to start
When the numbers look wrong after install, open the matching page and finish its checks before contacting support. A collect request that returns 202 means the collector accepted the HTTP call. It does not mean the event was stored.
| Symptom | Open | What to confirm first |
|---|---|---|
| Realtime or reports show no visits | No data showing up | Whether the script sent a collect request, and whether domain, exclusions, quota, or pause dropped it |
| The same pageview or business action is counted twice | Duplicate pageviews or events | Two snippets on the page, or browser and server events without a shared insert_id |
| Visitors or sessions do not match another analytics tool | Numbers differ from other tools | Bots split out, cookie-free visitor ids, and which time zone cuts the day |
| A single-page app records only the first load | SPA pageviews missing | History API routing, and data-hash when the route lives in the hash |
| A whole day is shifted, or exports disagree with the UI | Dates and time zones | The project time zone, and client clocks more than 24 hours off |
| The API returns 401, 403, or 429 | API 401 / 403 / 429 | The key is present, the scope matches, and the per-minute or daily cap is not full |
| Client uploads fail, pile up, or switch hosts | SDK network and retries | Web snippet versus a queued SDK, and whether the status is 429, 5xx, or another 4xx |
| The site has no chat bubble | Chat widget not showing | The chat switch, whether w-chat.js loaded, domain allowlist, and mobile hide |
| The assistant will not answer, the figures disagree, or it keeps asking to clarify | AI assistant issues | Rule parsing, whether a platform model is enabled, and the daily model cap |
If you are still proving the snippet runs at all, start with Verify your installation. When you do write in, use the template on Contact support and do not paste a full API key.
No data showing up
A collect request can succeed and the event still never reaches a report. The checks follow the order in which the collector drops events.
Symptoms
- ConsoleWeb analyticsRealtime (or the realtime page for the other client) shows no new visit.
- The browser network panel shows a request to
/api/v1/pulsewith status202. - Only some pages, one device class, or the app / mini program is missing, while the website looks fine.
Check in this order
- The page has one
w.jstag withdata-site(or the other SDK has calledinit). The site key matches the SDK code page in the console. - The host is not
localhost,127.*,0.0.0.0, or[::1]. Those hosts do not send unless you setdata-allow-local="true"or an explicitdata-api. - The browser is not sending Do Not Track or Global Privacy Control.
w.jsreturns immediately on either signal unless the tag setsdata-respect-dnt="false". - The collect status code. The collector returns
202on reject as well. An empty body, or a body that is only site config, is not proof of storage. - The request's origin host (and the page URL host) is in the project's allowed domains, including subdomains. An app id that was never registered is dropped silently for apps and mini programs.
- The project is not paused. Pause is recorded as
pausedand the event is dropped. - Exclusion rules: path, hostname, UA substring, IP, test device id, or prerelease app version. Any hit drops the event.
- The free analytics plan's platform list is web only. Mini program and app events are dropped as a plan miss, still with
202, when the plan does not include that platform. - This month's events are over the owner's plan quota. Further events are dropped. The site itself keeps working. The counter resets on the 1st in the site time zone.
- The date range you are viewing is "today" in the project time zone. See Dates and time zones.
Causes
The collection service answers almost every rejected event with 202: unknown site key, domain mismatch, pause, exclusion, plan without that platform, monthly quota, a privacy tier that drops the whole event, the per-site minute bucket, or spike sampling. If the collection service has an internal error, the event is dropped and the response is still 202.
One privacy rule is easy to misread. When consent is required and the payload has no analytics consent bit, the tier falls to anonymous aggregate, and pageviews and custom events are still stored. Autocapture is dropped below tier 2. Identity operations are dropped below tier 3, except consent, opt-out, and opt-in receipts. Missing autocapture is not the same failure as missing pageviews.
With the default bot mode "separate", bots go to the bot report, not the human realtime stream. Look at Bots & AI crawlers.
Fix
- Site key, allowed domains, and app ids must match what the console has registered. See Project settings.
- For local debugging, add
data-allow-local="true", or load the script on a registered domain. - To measure browsers that send Do Not Track, set
data-respect-dnt="false"on the tag, and confirm the compliance center still applies your DNT / GPC policy on the server. See CSP, blockers & networks and Privacy & compliance. - Resume a paused project and remove exclusion rules that match real traffic.
- Mini programs and apps need a plan that includes those platforms. See Plans & quotas. After the monthly quota is exhausted, upgrade, add a top-up, or wait until the 1st in the site time zone.
- If autocapture is missing, confirm the project and the plan both allow it, then check that the current tier is not 1.
Verify
Open the registered domain in a private window without Do Not Track. After the collect request appears, refresh realtime and you should see one human pageview. If you only have 202 and realtime stays empty, read that site's reject reasons for the day under Abnormal traffic in the console (domain, exclusion, quota, pause, tier, plan).
Still failing?
Send the Contact support template with:
Project name:
Full page URL (query values may be redacted):
Whether the script loaded, and the first 8 characters of data-site:
Collect status code and the first 200 characters of the body:
Whether the browser sends Do Not Track or Global Privacy Control:
Which report and time range, including the project time zone:Do not paste a full API key. The site key is public and can be included. For a wck_ key, send only the first 8 characters.
Duplicate pageviews or events
First separate a browser that sent the same view twice from a site that reported the same fact from both the browser and the server. The two paths dedupe differently.
Symptoms
- One open produces two realtime rows for the same path.
- A single-page app adds 2 pageviews on every route change.
- An order or sign-up exists once from the browser and once from the server, so a funnel step doubles.
Check in this order
- How many
w.jstags are in the page source. A later tag that findswindow.webcountalready ready exits. It does not count again because of a second tag. Two requests usually mean another snippet or a framework plugin also calledpageview(). - Whether automatic recording and a manual
pageview()hit the same URL. The script ignores a URL that matches the previous one, including the view it just recorded. - The collector's short window: same visitor, type, event name, path, query string, and property signature. Pageviews collapse inside 2 seconds, other events inside 1 second. Leave and Web Vitals use session + name + path for 5 seconds. A second real view outside the window is kept.
- Whether
POST /api/v1/tracksent aninsert_id. Duplicates in the same batch, and the sameinsert_idalready stored with an event time within 1 day, are dropped. The first one wins. Without the key, browser plus server is two events. track_update/track_overwritelocate an existing event byinsert_id. They do not insert another row. Withoutinsert_idthey cannot merge the way you expect.
Causes
The website script has no durable dedupe key across requests. It relies on "do not record the same URL twice in a row" and a few seconds of collector fingerprinting to stop double clicks, duplicate tags, and replays. A business fact reported from both sides becomes one row only when they share an insert_id.
replaceState that changes state and not the URL does not add a pageview. A changed query string counts as a different URL, and the collector fingerprint includes the query string.
Fix
- Keep a single TapCub script on the page. Do not also call
pageview()from the framework unless you setdata-auto-track="false". See Pageviews & SPAs. - Report orders, payments, and sign-ups from the server only, with a stable
insert_id(for example the order id, at most 36 characters). If the browser must record behavior too, use a different event name. See Server events & log import. - When you retry a server batch, reuse the original
insert_id. Do not mint a new one per attempt.
Verify
Open the page once in a private window. Realtime should show one pageview for that path. Refresh after more than 2 seconds and a second row should appear. Send the same server event twice with one insert_id. The second attempt should not add a stored event. debug=1 validates without writing, which is the right first check.
Still failing?
Use Contact support and include both timestamps, the path or event name, and whether a browser request and a server request both exist. Strip keys from the sample. insert_id can stay. Never paste a full API key. If you must point at one, send the first 8 characters only.
SPA pageviews missing
The website script records a pageview only for navigations it can see.
Symptoms
- The first load is recorded. Later in-app routes are not.
- A hash router (
/#/pricing) collapses every section onto the home page. - After auto tracking is turned off, even the first load disappears.
Check in this order
- The router calls
history.pushStateorhistory.replaceState. The script wraps both and listens forpopstate. History mode in Vue Router, React Router, Next.js, and Nuxt uses this path. You do not also callpageview(). - Whether the URL is a hash route. By default the script drops
#and everything after it, so/docs#introand/docs#faqare one page, andhashchangeis not listened for. Hash routers needdata-hash="true"on the tag. - Whether the new URL is identical to the previous one. Identical URLs are ignored, so a
replaceStatethat changes state only does not add a page, and it also does not record a "new" view of an unchanged URL. - Whether
data-excludecovers these paths. Exclusion matcheslocation.pathnameonly, not the query or hash. - Whether
data-auto-trackis"false". That turns off the first pageview, route following, and engagement time together. Only your ownpageview()andtrack()calls remain. - If the document title updates after the next turn of the event loop, the URL is still the new one and the title may still be the previous page. The view was recorded.
Causes
The script does not read the framework's router store. It watches the History API, back/forward, and hash changes when you opt in. A view that swaps without changing the address is not an automatic pageview.
The collector also removes the query string from the path used in page reports. A route that only changes ?tab= still sends (the URL changed), and the page report can still file it under the same path.
Fix
Hash router:
<script async src="https://app.tapcub.com/w.js" data-site="{{SITE_KEY}}" data-hash="true"></script>If the router does not change the address, call window.webcount.pageview() from the route-ready callback. If automatic recording is also on and the URL has already changed, do not call it again. The same URL is ignored, or a second row appears once the 2-second window has passed. Framework notes: Vue guide, React guide, Next.js guide, Nuxt guide. The full rules are on Pageviews & SPAs.
Verify
Open the site and filter the network panel for collect. The first load should send one request. Navigate to another path. The next event-loop turn should send another, with the new URL in the body. Realtime should show both, with different paths.
Still failing?
Use Contact support with the framework and router mode (history or hash), the tag's data-hash, data-auto-track, and data-exclude, and whether a new collect request appeared between two clicks. Do not paste a full API key.
Numbers differ from other tools
Compare the same day and the same kind of person before comparing the count. TapCub's defaults are not the cookie-based counter.
Symptoms
- Visitors are clearly lower than the other tool, or "new visitors" look high every day.
- Session counts are higher or lower.
- One page has fewer pageviews, and URLs that differ only by query string were folded together.
- The totals move closer only after bots are added back in.
Check in this order
- The range is a calendar day in the project time zone, not the browser zone and not UTC midnight. See Dates and time zones.
- You are comparing humans with humans. The default bot mode is "separate": human reports exclude search-engine bots and AI crawlers, which live on Bots & AI crawlers. "Drop" does not store bots. "Off" mixes them with humans.
- How a visitor is recognized.
w.jswrites no cookie and no local storage. The visitor id is an HMAC of IP and user agent on the server. A new network, a new browser, or a daily salt in strict mode becomes another visitor. A first-party cookie tool will look more stable. See Visitors, users & identity and Counting modes. - Sessions default to 30 minutes idle and 24 hours maximum, with midnight split off. A tool with a different timeout will not match. See Sessions.
- Page paths: the collector strips the query string and a trailing
/for page stats, and keeps at most 512 characters. The hash is part of the path only when the URL contains#/. A tool that treats every query string as its own page will disagree on the page table even when total pageviews are close. - Browsers with Do Not Track or Global Privacy Control never run
w.jsby default, so those people are absent here and may still be present in the other tool. - Spike sampling: a minute above 1,000 events and above 10 times the same hour's average over the past 7 days enters about 10 minutes of sampling (1 of every 10 kept). This is a spike brake, not a standing sample.
Causes
Three choices move the numbers independently: who counts as a person, how the same person is recognized, and where the day boundary sits. TapCub splits bots out, uses a cookie-free visitor id, and cuts days in the project time zone. After those match, pageviews can still differ because of path folding and the short dedupe window (identical pageview fingerprints collapse for 2 seconds).
Fix
- Compare human traffic for the same project-time-zone day, and exclude internal IPs on both sides.
- A stable person across days needs identified mode (tier 3) and a login id. Do not expect anonymous visitors to match a cookie tool. See Privacy choices before you start.
- Change the session timeout in the project's session rules, then compare again. Do not match a 15-minute definition against the 30-minute default.
- To keep campaign parameters in the page report, use URL parameter handling on Project settings. The default page stat drops the query string.
Verify
Pick a quiet hour you can reproduce. In a private window, open 3 fixed URLs and stay longer than 2 seconds on each. Human realtime should show 3 pageviews and 1 visitor. Repeat the same hour in the other tool from a browser that is not sending Do Not Track.
Still failing?
Use Contact support with both metric names, the range, the time zone, and whether the TapCub side was humans only. Do not paste a full API key. Remove emails, phone numbers, and IP addresses from any export before you send it.
Dates and time zones
A TapCub day is cut in the project time zone. It is not your laptop's zone, and it is not UTC midnight unless the project zone is UTC.
Symptoms
- Evening traffic shows up on "tomorrow" or "yesterday".
- The boundary disagrees with a tool that buckets by UTC or by the browser zone.
- Backfilled server events land on the day they were received, not the day they happened.
Check in this order
- Open ConsoleConfigurationSettingsGeneral and read Time zone. That is the value set when the project was created. Daily, weekly, and monthly reports, an optional midnight session split, and hour-of-week heatmaps all use it.
- The other tool's zone matches. Compare people only after the calendar day matches. See Numbers differ from other tools.
- Browser time correction: the collector reconstructs event time as received minus (sent minus client time). If that result is more than 24 hours from the receive time, the receive time is stored instead. A wrong phone date, or an event that sat offline for a long time, lands on the receive day.
timeonPOST /api/v1/trackis a millisecond timestamp. More than 72 hours from the server clock is replaced with the receive time. Do not backfill a batch of timestamps outside that window and expect them to keep their original days.- Monthly quota uses the site time zone's calendar month, not the UTC month. Near the 1st, judge "over quota" in the project zone.
Causes
Events may keep a client clock, but day and month boundaries in reports use the project zone. Inside 24 hours (browser collect) or 72 hours (Track API) the client time is kept when it can be reconstructed. Outside that window the receive time replaces it, so a late backfill piles onto the day of the import.
Fix
- Set the project zone to the IANA zone of the business, for example
Asia/Shanghai. Days already bucketed in the old zone are not rewritten. Events after the change use the new boundary. - When backfilling, keep
timewithin 72 hours of the server clock, or accept that those rows use the receive day. Older logs belong in log import, Server events & log import, not in an out-of-windowtime. - After export, read dates in the project zone. Do not cut another day at UTC midnight. Date fields on the API are described on Conventions.
Verify
Note the project zone. Open the page after 23:50 in that zone. Realtime's "today" should still be that day, not the next UTC day, when the project is not UTC. Set the computer clock more than 24 hours fast and visit again. That hit should use the server receive time, not the fast-forwarded date.
Still failing?
Use Contact support with the project zone, the date you expected, the date on the report, and whether the event was browser collect or the Track API. Do not paste a full API key.