> ## 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.

# Batching and flushing

> Events are queued in memory and sent in batches. The flush triggers, the retry rules, and when to flush by hand.

Each `step()` call, and each pageview when `trackPageviews` is on, pushes an event onto an in-memory queue. The queue drains in batches, so a busy page makes a handful of requests instead of one per event. Every request goes through `fetch`; `navigator.sendBeacon` is never used.

## Automatic flush triggers

<Steps>
  <Step title="Timer interval">
    A timer fires every `flushInterval` milliseconds (default: **5000 ms**) and sends one batch. The timer only runs in the browser.

    ```ts theme={null}
    Wendung.init({
      apiKey: 'pk_live_xxxx',
      flushInterval: 3000, // flush every 3 seconds instead of 5
    })
    ```
  </Step>

  <Step title="Batch size threshold">
    If the queue reaches `maxBatchSize` events before the timer fires, the SDK flushes immediately. The default is **50 events per batch**; the hard maximum per request is **100 events**. Requests must also fit within the [payload limits](#payload-limits).

    ```ts theme={null}
    Wendung.init({
      apiKey: 'pk_live_xxxx',
      maxBatchSize: 25, // flush sooner on high-traffic pages
    })
    ```
  </Step>

  <Step title="Page hide">
    On `visibilitychange` to hidden and on `pagehide`, the SDK drains the entire queue with parallel `fetch` requests using `keepalive: true`, so the browser completes them even as the page unloads.
  </Step>
</Steps>

`destroy()` also sends one last batch after it removes the timer and listeners.

## Payload limits

The ingest endpoint accepts request bodies up to **51,200 bytes (50 KiB)**. Each event's `properties` object can contain at most **20 top-level keys**. Properties and traits must be JSON-serializable and fit within the request body limit.

The SDK batches by event count and does not split oversized request bodies. A request over the byte limit receives `413`; an event with too many properties causes the request to receive `400`. The SDK drops both kinds of rejected batch. Reduce `maxBatchSize` or the data sent with each event to stay within these limits.

## The internal queue

The queue holds at most **1000 events**. When a new `step()` call overflows it, the **oldest** events are dropped to make room. When a failed batch is re-queued at the front and overflows it, the **newest** events are dropped instead.

With a flush every few seconds the queue stays far below this limit. If your users go offline for long stretches, lower `flushInterval` and `maxBatchSize` so the backlog drains in smaller, more frequent batches once they reconnect.

## Retry behavior

| Response | Behavior |
| - | - |
| `2xx` | Batch delivered and removed from the queue. |
| `4xx` | Client error (bad API key, malformed payload). The batch is **dropped**; retrying would produce the same result. |
| `5xx` | Server error. The batch is **re-queued** at the front and retried, with the same `batchId`, on the next flush trigger. |
| Network failure | Same as `5xx`: re-queued for retry. |

There is no backoff and no retry cap; a failing batch is retried on every flush trigger until it succeeds, is dropped by a `4xx`, or is pushed out of the queue.

## Manual flush methods

### `flush()`

Sends at most **one batch** (up to `maxBatchSize` events) per call using `fetch`. Returns a `Promise` that resolves once the request settles; it never rejects, and failed batches are re-queued.

```ts theme={null}
await Wendung.flush()
```

<Tip>
  You rarely need `flush()`. The one real use is a send attempt that has to settle before something else happens, like a redirect to an external checkout page.
</Tip>

### `sendBeacon()`

Drains the **entire queue** in parallel `fetch` requests with `keepalive: true`, one per batch of up to `maxBatchSize` events (capped at 100). The name is historical: it never calls `navigator.sendBeacon`, because keepalive `fetch` can carry the API key header and still outlive the page. Returns a `Promise` that resolves when all requests settle; failures are re-queued. If `fetch` is unavailable, events are retained.

```ts theme={null}
await Wendung.sendBeacon()
```

<Note>
  `visibilitychange` and `pagehide` are hooked for you. Call `sendBeacon()` yourself only if a custom unload flow bypasses those events.
</Note>

## Summary

| Mechanism | Trigger | Sends |
| - | - | - |
| Timer | Every `flushInterval` ms (default 5000) | One batch |
| Batch size | Queue reaches `maxBatchSize` (default 50, max 100) | One batch |
| Page hide | `visibilitychange` / `pagehide` | Whole queue (keepalive) |
| `flush()` | On demand | One batch |
| `sendBeacon()` | On demand | Whole queue (keepalive) |


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