Troubleshooting
Diagnose missing events, duplicate page views, lost visitors, and SDK errors.
Start with diagnostics: they tell you what the SDK is doing in the background, where it never throws. Log them while you integrate:
const voidhash = createVoidhashClient({
onDiagnostic: (diagnostic) => console.debug("[voidhash]", diagnostic),
publishableKey: "vh_pk_…",
});No events arrive
Check these in order.
- Is
initialize()called? In React, the provider calls it. Elsewhere, call it once per page. - Is analytics consent granted? With
analyticsConsent: "pending", nothing is sent until you callsetAnalyticsConsent("granted"). TheANALYTICS_CONSENT_DENIEDdiagnostic says so. - Is the publishable key right? A rejected key pauses all requests and reports
AUTHENTICATION_FAILEDonce. Copy the key again from Settings → API keys in Studio. - Does a Content Security Policy block the requests? Add
https://api.voidhash.com, or your proxy's origin, toconnect-src. - Does an ad blocker block them? In the Network tab, look for blocked requests to
/i/v1/batch. Set up a first-party proxy. - Are you testing with a headless browser? Events from browsers that identify themselves as headless or as test tools, such as headless Chrome (including Playwright's and Puppeteer's default) or Cypress, are discarded as bot traffic. Test in a regular browser window.
- Is the event turned on? Self-hosted projects store
$pageviewand$sign_outonly after they are turned on under Settings → Events.
Events are sent every 5 seconds, so allow a few seconds before they appear in Activity →
Analytics events. Call flushAnalytics() to send them at once; it resolves with
{ flushed, pending, lastError }.
Page views are counted twice
- Your app calls
page()on every route change while automatic page views are on. Remove the calls, or setanalytics: { capturePageViews: false }. - Two clients for the same project run on the page, for example one from a module and one from a
VoidhashProviderwithconfig. Create one client and pass it to the provider withclient.
React Strict Mode does not double page views: the provider keeps one client through its remount.
Returning visitors show up as new visitors
- The browser cleared storage. The anonymous ID lives in
localStorage. Private windows, "clear on exit" settings, and blocked storage start a new visitor every time. Safari and other browsers with tracking prevention may also delete storage a script wrote after about seven days without a visit. Identify signed-in users, so each visit is linked to them again. - The publishable key changed. The SDK keeps its saved state per project key, so another key starts new visitors.
- Another subdomain.
localStorageis per origin, sowww.example.comandapp.example.comhave different anonymous visitors until the visitor is identified on both.
A purchase is not credited to a campaign
- The Checkout Session or customer carries no Voidhash ID. See Stripe revenue.
- The ID was read before
identify()while the visitor was signed in, so the purchase landed on the anonymous visitor. It moves to the user when that visitor is identified. - The visitor arrived without campaign parameters, or declined analytics consent, so they have no first touch.
- The ad's URL does not carry the campaign IDs. See Meta ads.
Errors
| Error | Cause and fix |
|---|---|
VoidhashNotInitializedError | A method ran before initialize() was called. Call it first, or use the provider. |
VoidhashDestroyedError | The client was destroyed, for example by a provider that unmounted. Create a new client. |
VoidhashConfigurationError | An option is invalid, such as a blank publishableKey. |
initialize() needs a browser | initialize() ran on a server. Call it from client-side code, such as an effect, or render the provider. |
VoidhashInvalidArgumentError | identify() got a blank ID or one that starts with vh:anon:, or track() a blank event name. Nothing was changed. |
useVoidhash must be used within a VoidhashProvider | A hook rendered outside the provider. |
Diagnostics
Each diagnostic has a stable code, a kind for routing, the SDK operation, whether the SDK will
retry on its own, the httpStatus when there was a response, and a readable message.
| Code | Kind | Meaning |
|---|---|---|
TRANSPORT_FAILED | transport | A request failed or timed out. Events and identity writes are retried. |
ANALYTICS_UNEXPECTED_STATUS | transport | Voidhash answered with an unexpected status. The batch stays queued. |
MALFORMED_CAPTURE_RESPONSE | transport | Voidhash accepted a batch but its answer could not be read. |
KEEPALIVE_EVENT_TOO_LARGE | transport | An event did not fit the request sent while leaving the page. It is sent on the next visit. |
IDENTITY_WRITE_REFUSED | transport | Voidhash refused an identity link for good. It was dropped; the browser keeps its identity. |
IDENTITY_WRITE_DROPPED | eviction | More than 20 users identified while Voidhash could not be reached; the oldest link was dropped. |
ANALYTICS_EVENT_DROPPED | eviction | An event was dropped: the queue was full, or Voidhash refused it for good. |
CIRCUIT_OPENED | breaker | Repeated failures paused requests to Voidhash. They resume on their own. |
CIRCUIT_OPEN | breaker | A request was skipped while requests are paused. |
AUTHENTICATION_FAILED | auth | The publishable key was rejected. Requests pause until initialize(), identify(), signOut(), or reset(), or until one request is tried again a minute later when the tab is visible. |
CACHE_READ_FAILED | cache | Browser storage could not be read. The SDK continues in memory. |
CACHE_WRITE_FAILED | cache | Browser storage refused a write, for example when full. The queue stays in memory. |
ANALYTICS_CONSENT_DENIED | consent | Analytics consent is not granted, so analytics are not recorded. Reported once. |
Report auth diagnostics to your monitoring: they mean no data reaches Voidhash.