> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wendung.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Debugging

> Enable verbose logging, interpret [Wendung - Warning] messages, and troubleshoot the most common SDK integration issues.

The SDK writes two kinds of console output:

* **Debug logs** (`[Wendung] ...`): opt-in via `debug: true`, trace normal SDK activity.
* **Warnings** (`[Wendung - Warning] ...`): always on, emitted when the SDK hits something unexpected.

## Enable debug mode

```ts theme={null}
Wendung.init({
  apiKey: 'pk_your_publishable_key',
  debug: import.meta.env.DEV, // or: process.env.NODE_ENV !== 'production'
})
```

With `debug: true`, the SDK logs:

| Log | When |
| - | - |
| `Initialized with endpoint: <endpoint>, flushInterval: <ms>, maxBatchSize: <n>` | On `init()` |
| `queued event <name> eventId=<uuid>` | On each `step()` |
| `flushing batch batchId=<uuid> events=<n>` | When a batch is sent |
| `ingest accepted batch batchId=<uuid>` (plus ` requestId=<id>` when the response carries one) | On a successful response |
| `Tracker reset` | On `reset()` |

## Warning messages

Warnings are emitted regardless of the `debug` setting. This is the complete list.

| Warning | Cause and fix |
| - | - |
| `track() requires an event name between 1 and 100 characters` | Event name is empty, not a string, or over 100 characters. The event is dropped. |
| `track() properties must be a plain object` | `properties` is an array, class instance, or other non-plain value. The event is dropped. |
| `fetch is unavailable; retaining queued events` | The runtime has no `fetch`. Events stay queued until one is available. |
| `fetch is unavailable; retaining queued events because authenticated keepalive delivery is not possible` | Same condition during the page-hide drain. |
| `maxBatchSize exceeds 100; capping payload batches to 100` | Emitted once at init. The ingest endpoint accepts at most 100 events per request; batches are capped to that. |
| `Failed to serialize batch (circular reference or unserializable value); dropping events` | Make properties and traits JSON-serializable. The batch is dropped. |
| `Endpoint rejected batch batchId=<uuid> with status <code>; dropping events` | 4xx response. The batch is dropped, never retried. Check your API key and payload. |
| `Endpoint returned status <code>; re-queuing batch batchId=<uuid>` | 5xx response. The batch is re-queued and retried on the next flush. |
| `Failed to send payload batchId=<uuid>, re-queuing batch` | Network error. The batch is re-queued and retried on the next flush. |

Invalid configuration makes `init()` throw instead of warning; see [init() options](/configuration/options#errors).

## Verifying your integration

After your first `step()` call, confirm events are flowing:

1. **Network tab.** Filter for `events` in DevTools. A `POST` to the ingest endpoint appears within 5 seconds (sooner if the batch fills). A 2xx response means the batch was accepted.
2. **Dashboard.** Your user appears in the **Recent identities** card on the Overview within a minute.
3. **Debug mode.** `debug: true` shows the init log, queued events, and flushes in the console.
4. **Pageviews.** With `trackPageviews: true`, the site appears on the **Sites** page within a minute of the first pageview, and the green badge next to its name counts visitors from the last five minutes.

## Common issues

<AccordionGroup>
  <Accordion title="Events are not being sent">
    Most common causes, in order:

    1. `init()` wasn't called, or ran after the first `step()`. Initialize at app startup.
    2. The `apiKey` is wrong or deactivated. Check [Settings → API Keys](/help/settings#api-keys) in your dashboard.
    3. A 4xx response is dropping your batches. Look for `[Wendung - Warning] Endpoint rejected batch batchId=... with status 401; dropping events`.
    4. The origin isn't allowed. A `403` means the page's origin is missing from [Settings → Allowed origins](/help/settings#allowed-origins). A new project rejects every origin until you add one.
    5. An ad-blocker or privacy extension is blocking the request. Blocked requests show as `(blocked)` in the Network tab.
    6. Check `Wendung.getConsent()` in the console. `denied` stops all collection. `pending` drops custom events and identification; it sends only aggregate pageviews when `trackPageviews` is `'auto'`. See the [consent guide](/guides/consent).
  </Accordion>

  <Accordion title="'Call .init() first' error">
    Every method except `init()` and `getConsent()` throws `[Wendung] Call .init() first` when the SDK isn't initialized. Usual causes:

    * A module calls an SDK method at import time, before your entry file runs `init()`.
    * In Next.js, a Server Component or Route Handler used the SDK. It is browser-only; move the call into a Client Component.
  </Accordion>

  <Accordion title="Events sent near page unload aren't arriving">
    On `visibilitychange` (hidden) and `pagehide`, the SDK drains the whole queue with parallel `fetch` requests using `keepalive`, so unload delivery normally needs no extra work. If events still go missing:

    * A custom unload flow in your app may preempt the SDK's listeners. Call `sendBeacon()` from your own handler to drain the queue.
    * Mobile Safari is aggressive about killing background tabs. Some event loss on mobile is unavoidable.
  </Accordion>

  <Accordion title="Endpoint is rejecting requests with 4xx">
    The SDK drops the batch on any 4xx response and never retries it. Check:

    * `401 Unauthorized`: the `apiKey` is wrong, disabled, or belongs to a different project.
    * `403 Forbidden`: the request's origin isn't on the project's allowed origins list.
    * `429 Too Many Requests`: the monthly event quota is used up, or you've hit your plan's rate limit. Check usage in the dashboard.
    * `400 Bad Request`: the payload is invalid, for example because an event has more than 20 top-level properties. Check the [payload limits](/concepts/batching#payload-limits).
    * `413 Payload Too Large`: the request body exceeds 51,200 bytes (50 KiB). Reduce `maxBatchSize` or the data sent with each event.
  </Accordion>

  <Accordion title="Events arrive in production but not from localhost">
    Only origins on the allowed origins list can send events, so `http://localhost:3000` is rejected unless you added it. The ingest endpoint answers `403` and the SDK logs `Endpoint rejected batch batchId=... with status 403; dropping events`.

    Turn on **Dev mode** in [Settings → Allowed origins](/help/settings#dev-mode). For the next 24 hours the project also accepts loopback origins (`localhost`, `*.localhost`, `127.0.0.0/8`, `[::1]`, on any port). It switches itself off after that.

    The change can take a few minutes to reach the ingest endpoint. Private network addresses such as `192.168.1.42` are not covered, so testing from a phone on the same Wi-Fi still needs that origin on the allowed list. Requests without an `Origin` header, such as curl or server-side scripts, are always rejected.
  </Accordion>

  <Accordion title="Duplicate events under React Strict Mode">
    `init()` is safe to call twice: each call tears down the previous instance, so Strict Mode won't duplicate timers or listeners.

    Duplicate `step()` events usually come from calling `step()` in a `useEffect`, which Strict Mode runs twice in development. Either move the call into an event handler, or guard with a ref:

    ```tsx theme={null}
      const tracked = useRef(false)
      useEffect(() => {
        if (tracked.current) return
        tracked.current = true
        Wendung.step('onboarding_viewed')
      }, [])
    ```
  </Accordion>

  <Accordion title="Pageviews are not showing">
    * `trackPageviews` is off. Use `true` for pageviews after consent, or `'auto'` for aggregate pageviews while pending and full pageviews after consent. `denied` stops both. See [consent](/guides/consent).
    * The hostname is not registered. Pageviews are stored for every hostname, but the reports are per site: add it under **Sites** in the dashboard, or open **All sites**.
    * The workspace is on the free plan, which has no pageview allowance. Web analytics needs a paid plan.
    * The monthly pageview allowance is used up. The ingest endpoint still answers `202` but reports the pageviews as `dropped`, and the Sites page shows the quota state. Your `step()` events keep flowing.
    * The request was rejected entirely. A `403` means the origin is not on the allowed origins list; see the issue above.
  </Accordion>

  <Accordion title="Pageviews arrive but visitors stay at zero">
    With `trackPageviews: 'auto'` and pending consent, this is expected. Aggregate pageviews contain no visitor or session identifiers. They increase pageview totals without increasing unique visitors, current visitors, or sessions. Bounce rate and other session metrics describe consented traffic only.

    Changing the configuration does not remove earlier consented visitors from the selected date range. Check the new request body for `collection: 'aggregate'` and no `identity` or `sessionId`. See [aggregate pageviews](/concepts/pageviews#aggregate-pageviews).
  </Accordion>

  <Accordion title="Events are attributed to the wrong user">
    * Call `reset()` on logout. The SDK persists identity in `localStorage`, so omitting it can attribute later activity to the previous user.
    * Call `identify()` when the user becomes known. Switching between identified user IDs starts a new session. The first `identify()` in an anonymous session keeps that session.
    * Each `identify()` replaces the traits object, including clearing it when you omit traits. Already queued events keep their original identity, traits, and session.
  </Accordion>
</AccordionGroup>

## Still stuck?

Reach out to support with:

* Your project ID (dashboard, **Settings → API Keys**)
* A console dump with `debug: true` enabled
* The request/response from your Network tab for a failing event

[Contact support →](mailto:support@wendung.app)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.