# Node SDK | Vitix Pay

> Install and use @vitix/pay, the zero-dependency TypeScript SDK for Vitix Pay: typed resources, idempotency, pagination, errors and webhook verification for Node, Next.js and Cloudflare Workers.

Canonical: https://vitix.net/developers/sdk
Markdown: https://vitix.net/developers/sdk.md


Documentation menu

@vitix/pay is the official TypeScript SDK. It is a thin, fully typed wrapper over the REST API with no runtime dependencies: it uses the global fetch and Web Crypto, so it runs on Node 18+, Bun, Deno, Cloudflare Workers, Vercel Edge and Next.js route handlers alike.

## Install
bash

```
npm install https://api.vitix.net/sdk/vitix-pay-0.2.0.tgz# orpnpm add https://api.vitix.net/sdk/vitix-pay-0.2.0.tgz# oryarn add https://api.vitix.net/sdk/vitix-pay-0.2.0.tgz
```

| Requirement | Detail |
| --- | --- |
| Runtime | Node 18 or newer, or any runtime with global fetch, Headers and crypto.subtle. |
| Module formats | ESM and CommonJS, with bundled .d.ts type definitions. |
| Dependencies | None. |
| Size | Under 10 kB minified. |

## Initialize
Create one client per process and reuse it. The key decides the mode: a `vx_test_` key talks to test data, a `vx_live_` key to live data.
ts

```
import { VitixPay } from "@vitix/pay";const vitix = new VitixPay(process.env.VITIX_API_KEY!, { // baseUrl: "https://api.vitix.net", // default // timeout: 15_000, // per request // maxRetries: 2, // on network errors / 429 / 5xx (GET and idempotent POST only)});
```

## Resources
Every method returns a Promise of the typed resource and maps 1:1 to an endpoint in the [API reference](https://vitix.net/developers/api).
ts

```
// Checkoutsconst checkout = await vitix.checkouts.create({ amount: 2000, currency: "usd", success_url: "https://app.example.com/ok" }, { idempotencyKey: "topup_8f1c" });const same = await vitix.checkouts.retrieve(checkout.id);const open = await vitix.checkouts.list({ status: "open", customer: "user_42", limit: 50 });await vitix.checkouts.expire(checkout.id);// Paymentsconst payment = await vitix.payments.retrieve("py_live_…");const recent = await vitix.payments.list({ customer: "user_42" });const { url } = await vitix.payments.invoice("py_live_…");// Refundsconst refund = await vitix.refunds.create({ payment: "py_live_…", amount_usd: 500, reason: "customer_request" }, { idempotencyKey: "refund_8f1c" });await vitix.refunds.retrieve(refund.id);await vitix.refunds.list({ payment: "py_live_…" });// Eventsconst events = await vitix.events.list({ type: "payment.succeeded" });const event = await vitix.events.retrieve("evt_live_…");const deliveries = await vitix.events.deliveries("evt_live_…");await vitix.events.redeliver("evt_live_…");// Webhook endpointsconst ep = await vitix.webhookEndpoints.create({ url: "https://app.example.com/api/vitix/webhook", events: ["payment.succeeded"] });await vitix.webhookEndpoints.retrieve(ep.id);await vitix.webhookEndpoints.list();await vitix.webhookEndpoints.update(ep.id, { status: "disabled" });await vitix.webhookEndpoints.rotateSecret(ep.id);await vitix.webhookEndpoints.ping(ep.id);await vitix.webhookEndpoints.del(ep.id);// Accountconst account = await vitix.account.retrieve();
```

### Per-request options
Methods that create resources accept a second argument: `{ idempotencyKey?: string, timeout?: number, signal?: AbortSignal }`. Always pass an `idempotencyKey` when creating checkouts and refunds so network retries are safe.

## Pagination
List methods return `List<T>`: `{ object: "list", data: T[], has_more: boolean }`. Pass the last id as `starting_after` to continue.
ts

```
// Walk every payment for a customer, newest firstlet cursor: string | undefined;do { const page = await vitix.payments.list({ customer: "user_42", limit: 100, starting_after: cursor }); for (const p of page.data) reconcile(p); cursor = page.has_more ? page.data.at(-1)!.id : undefined;} while (cursor);
```

## Errors
Failed requests reject with a subclass of `VitixError` carrying `status`, `type`, `code`, `param` and `requestId`. See the [Errors](https://vitix.net/developers/errors) page for the full code list.

| Class | HTTP | When |
| --- | --- | --- |
| AuthenticationError | 401 / 403 | Missing, malformed, revoked key, or suspended account. |
| InvalidRequestError | 400 / 404 | Validation failures, unknown ids, business-rule violations. |
| IdempotencyError | 400 | Idempotency-Key reused with a different request. |
| RateLimitError | 429 | More than 100 requests in 10 seconds on one key. |
| APIError | 5xx | Something failed on Vitix's side; safe to retry. |
| VitixError | — | Base class, also used for network/timeout failures (status 0). |
ts

```
import { VitixPay, InvalidRequestError, AuthenticationError, RateLimitError, VitixError } from "@vitix/pay";try { await vitix.checkouts.create({ amount: 100, success_url: "https://app.example.com/ok" });} catch (err) { if (err instanceof InvalidRequestError) { // err.code === "amount_too_small", err.param === "amount" showFormError(err.param, err.message); } else if (err instanceof RateLimitError) { await sleep(2_000); // then retry } else if (err instanceof AuthenticationError) { throw new Error("Check VITIX_API_KEY"); } else if (err instanceof VitixError) { log.error({ requestId: err.requestId, status: err.status, code: err.code }, err.message); } else throw err;}
```
Network errors, `429` and `5xx` responses are retried automatically up to `maxRetries` times with exponential backoff for `GET` requests and for `POST` requests that carry an idempotency key.

## Webhooks
`Webhooks.constructEvent` verifies the [Standard Webhooks signature](https://vitix.net/developers/webhooks#signature) (including the 5-minute timestamp tolerance) and returns a typed `Event`. Pass the raw request body string, the request's headers (a `Headers` instance or a plain object) and the endpoint secret.
ts

```
import { Webhooks, WebhookSignatureError } from "@vitix/pay";// Throws WebhookSignatureError on a bad or stale signature, returns the typed Event otherwise.const event = await Webhooks.constructEvent(rawBody, request.headers, process.env.VITIX_WEBHOOK_SECRET!);// Boolean variantconst ok = await Webhooks.verify(rawBody, request.headers, process.env.VITIX_WEBHOOK_SECRET!);if (event.type === "payment.succeeded") { event.data.object; // typed as Payment}
```

## TypeScript
All resource shapes and event types are exported, and `event.data.object` narrows by `event.type`.
ts

```
import type { Checkout, Payment, Refund, Event, WebhookEndpoint, WebhookDelivery, Account, List } from "@vitix/pay";function onPaid(p: Payment) { /* p.amount_usd, p.customer?.id, p.metadata … */ }const page: List<Checkout> = await vitix.checkouts.list();
```

## Cloudflare Workers & Edge
ts

```
// Cloudflare Worker / Edge runtime: same API, no Node built-ins requiredexport default { async fetch(req: Request, env: { VITIX_API_KEY: string }) { const vitix = new VitixPay(env.VITIX_API_KEY); const checkout = await vitix.checkouts.create({ amount: 2000, success_url: "https://app.example.com/ok" }); return Response.redirect(checkout.url, 303); },};
```

[← Testing](https://vitix.net/developers/testing)[Webhooks →](https://vitix.net/developers/webhooks)
Questions or a missing feature? Email [support@vitix.net](mailto:support@vitix.net)
