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

# Errors, retries & pagination

> Typed errors, built-in retries, pagination, webhook verification, and the SDK version.

Every request retries transient failures automatically and throws a typed error on real ones. Discriminate with `instanceof` — every error extends `ApiError`.

## Typed errors

All errors carry `status`, `code`, `message`, `requestId`, and `headers`. `ApiError.generate` maps HTTP status to the right subclass:

| Class                 | When it throws                                              |
| --------------------- | ----------------------------------------------------------- |
| `ApiError`            | Base class for every error below.                           |
| `BadRequestError`     | `400` — malformed request.                                  |
| `AuthenticationError` | `401` — missing or invalid API key.                         |
| `NotFoundError`       | `404` — resource doesn't exist.                             |
| `ConflictError`       | `409` — idempotency or state conflict (retried first).      |
| `ValidationError`     | `422` — request body failed validation.                     |
| `RateLimitError`      | `429` — rate limited. Carries `retryAfter: number \| null`. |
| `InternalServerError` | `5xx` — server error (retried first).                       |
| `ConnectionError`     | Network failure after retries are exhausted.                |
| `TimeoutError`        | Request exceeded the configured `timeout`.                  |

```ts theme={null}
import { UnitPay, ApiError, RateLimitError, ValidationError } from '@unitpay/node';

const unitpay = new UnitPay({ apiKey: process.env.UNITPAY_API_KEY });

try {
  await unitpay.customers.get('cus_missing');
} catch (err) {
  if (err instanceof ValidationError) {
    console.error('bad input', err.code, err.message);
  } else if (err instanceof RateLimitError) {
    console.error('retry after', err.retryAfter, 'seconds');
  } else if (err instanceof ApiError) {
    // catches NotFoundError, ConnectionError, TimeoutError, etc.
    console.error('api error', err.status, err.code, err.requestId);
  } else {
    throw err; // something unexpected
  }
}
```

## Retries & idempotency

Requests retry on `429`, `409`, and `5xx` responses plus network errors, with jittered exponential backoff. A `429` honors the `Retry-After` header. `POST` and `DELETE` requests automatically carry an `Idempotency-Key` so retries never double-apply — pass your own via `RequestOptions.idempotencyKey`, or set a `idempotencyKeyPrefix` on the client.

Tune retries and timeout on the constructor or per request:

```ts theme={null}
const unitpay = new UnitPay({
  apiKey: process.env.UNITPAY_API_KEY,
  retries: 2,        // default
  timeout: 30_000,   // ms, default
});

// Per-request override
await unitpay.customers.get('cus_123', {
  timeout: 5_000,
  idempotencyKey: 'my-key',
});
```

## Pagination

List methods return a `PagePromise<T>`. `await` it for a single `Page<T>` (`{ data, hasMore, nextCursor, prevCursor }`), or `for await` over it to stream every item across pages. `toArray()` collects them all.

```ts theme={null}
// One page
const page = await unitpay.customers.list();
console.log(page.data, page.hasMore, page.nextCursor);

// Every item, auto-paginated
for await (const customer of unitpay.customers.list()) {
  console.log(customer.id);
}

// Collect all into an array
const all = await unitpay.customers.list().toArray();
```

## Verifying webhooks

Verify inbound webhook signatures with the static `UnitPay.verifyWebhook(body, headers, secret)`. It validates the Svix signature and returns the parsed, camelCased event; it throws if the signature is invalid.

```ts theme={null}
export async function POST(req: Request) {
  const body = await req.text(); // raw body — do not parse first
  const headers = Object.fromEntries(req.headers);

  const event = await UnitPay.verifyWebhook(
    body,
    headers,
    process.env.UNITPAY_WEBHOOK_SECRET!,
  );

  // handle event…
  return new Response('ok');
}
```

## Observability

Subscribe to raw request/response events for logging or tracing, and read the SDK version:

```ts theme={null}
import { SDK_VERSION } from '@unitpay/node';

unitpay
  .on('request', ({ method, path, attempt }) => console.log(method, path, attempt))
  .on('response', ({ status, requestId }) => console.log(status, requestId));

console.log('sdk', SDK_VERSION);
```

## See also

<CardGroup cols={2}>
  <Card title="Customers & subscriptions" icon="users" href="/node/customers">
    Paginated reads.
  </Card>

  <Card title="Introduction" icon="node-js" href="/node/introduction">
    Install and instantiate.
  </Card>
</CardGroup>
