# Error handling

URL: https://supapulse.app/docs/error-handling

What happens when a send fails in the browser tracker, the React hooks and server-side revenue calls, and how to notice.

## Browser tracker [#browser-tracker]

The tracker never throws from `pageview()` or `event()`, so a network problem can't break your page.

| Situation | What happens |
| --- | --- |
| Called before `init()` | Logs `[Supapulse] Not initialized` as a warning; nothing is sent. |
| `sendBeacon` accepted the request | Counted as sent. The browser delivers it, even after the page closes. |
| `sendBeacon` unavailable or refused | Falls back to `fetch` with `keepalive`. |
| `fetch` fails, or returns a status other than the ones below | Retried after `retryBackoff × 2^(attempt − 1)` ms, capped at 30 s. |
| `400`, `401`, `402`, `403`, `404`, `413` or `422` | Final. The server will never accept it, so the event is dropped without retrying. |
| `429 Too Many Requests` | Retried after the `Retry-After` header (60 s if absent). |
| `maxRetries` reached | Dropped. With `debug: true`, logs `[Supapulse] Dropped after N attempts`. |
| Page hidden or closed with retries pending | Each pending event is tried once more. |

The retry queue lives in memory and is lost when the page closes. There's no persistent offline queue.

`init()` throws `Browser environment required` if it runs without `window`, during server rendering for example. Guard it:

```ts
if (typeof window !== 'undefined') {
  supapulse.init({ apiToken: 'pub_your_token' });
}
```

## React hooks [#react-hooks]

- `useSupapulse()` throws if there's no `<SupapulseProvider>` above the component.
- Before the provider has initialized, calls to the tracker are dropped with a `[Supapulse] Not initialized` warning, including `useEvent()` and `useIdentify()`. Send events from handlers, or wait for `isReady` in effects.
- If `init()` throws inside the provider, the error is logged as `[Supapulse React] Initialization error` and stored in the context's `error`.

## Server-side revenue [#server-side-revenue]

Revenue is a plain HTTP call, so handle it like any other dependency:

- **Non-2xx response:** throw, and let your webhook return an error so the payment provider retries it.
- **`401`:** the secret token is wrong or was regenerated. Alert on it; every payment until it's fixed is lost.
- **`402`:** the site's team has no active plan, or (for pageviews, events and identify) reached its monthly limit. Not retried by the SDKs. Revenue isn't blocked by the limit.
- **`422`:** the payload failed validation. The response names the field. Fix it; don't retry.
- **`429`:** wait for `Retry-After`.
- **`202`:** validated and queued. A repeated `transaction_id` with the same event type is ignored.

Retrying is safe: Supapulse keeps the first event per `transaction_id`.

The `@supapulse/revenue` SDK retries network errors, `5xx` and `429`s up to `maxRetries` (3) times, then throws. Other `4xx` responses throw at once.

## Monitoring [#monitoring]

- Keep `debug: false` in production; turn it on in a staging build to watch sends.
- Log failed revenue calls with the transaction ID, so you can resend them by hand.
- Compare Supapulse's revenue total with your processor's dashboard now and then. A gap means webhooks are failing or events are being rejected.

The full list of API errors is in [Error codes](https://supapulse.app/docs/api/errors).
