Node SDK
@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
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.
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.
// 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.
// 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 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). |
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 (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.
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.
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
// 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); },};