ZyloPaydocs

Node SDK (@zylopay/node)

Typed access to the ZyloPay API from Node.js and TypeScript, with auto-pagination, retries and webhook verification.

Pre-release

@zylopay/node is not published on npm yet. The interface below is the one the SDK ships with. Until it is published, use the curl examples or fetch.

Install

Coming soon on npm. Once published:

npm install @zylopay/node

Node.js 18 or later (it uses the built-in fetch). No runtime dependencies; ESM and CommonJS; TypeScript types generated from the OpenAPI spec. Secret keys only: the constructor refuses a publishable key.

Configure

import ZyloPay from '@zylopay/node';

const zp = new ZyloPay(process.env.ZYLOPAY_SECRET_KEY!);

Options, as a second argument:

OptionDefaultMeaning
maxNetworkRetries2Automatic retries (see below). 0 disables them.
timeout80000Per-attempt timeout in milliseconds (refunds wait up to 60 s for the chain).
apiVersionZyloPay.API_VERSION (2026-09-28)ZyloPay-Version sent with every request, so responses always match the SDK's types.
baseUrlhttps://zylopay-api.fly.devAPI origin. Override only to test against a local stub.
telemetrytrueSends ZyloPay-Client-Telemetry (the previous request's ID and latency, never payload data).
appInfoYour application's name and version, added to the User-Agent.
fetchglobal fetchA custom fetch, for proxies or tests.
const zp = new ZyloPay(process.env.ZYLOPAY_SECRET_KEY!, { maxNetworkRetries: 3, timeout: 20_000, apiVersion: '2026-09-28' });

The key's prefix sets the mode: a zp_test_sk_ key works on the testnet sandbox, a zp_live_sk_ key on mainnet.

Resources

Every method follows one pattern: (id?, params?, requestOptions?). Methods without parameters take the request options second. Responses are the API's JSON objects, with the same snake_case fields.

ResourceMethods
zp.paymentIntentscreate(params, opts), retrieve(id), list(params), cancel(id, opts)
zp.paymentsretrieve(id), list(params)
zp.refundscreate(params, opts), retrieve(id), list(params)
zp.terminalsretrieve(id), list(params)
zp.balanceretrieve()
zp.eventsretrieve(id), list(params)
zp.webhookEndpointscreate(params, opts), retrieve(id), update(id, params, opts), del(id), list(), rotateSecret(id, params, opts), sendTest(id), listDeliveries(endpointId, params)
zp.webhookDeliveriesretrieve(id), retry(id, opts)
zp.webhooksconstructEvent(rawBody, signatureHeader, secret, toleranceSeconds = 300)

Request options, the last argument of every method: idempotencyKey, apiVersion, timeout, maxNetworkRetries and signal (an AbortSignal; an aborted request throws ConnectionError with code: "aborted" and is never retried).

import { randomUUID } from 'node:crypto';

const intent = await zp.paymentIntents.create(
  { amount: 2500000, terminal: 'tml_ZP-4821-K', order_id: 'A7K2Q9XZ' },
  { idempotencyKey: `intent-${orderId}` },
);

const refund = await zp.refunds.create({ payment: 'pay_1042', amount: 1000000 }, { idempotencyKey: randomUUID() });

Pagination

list methods can be awaited for one page, or iterated with for await to walk every page:

const page = await zp.payments.list({ limit: 50 });
console.log(page.data, page.has_more, page.next_cursor);

for await (const payment of zp.payments.list({ limit: 50 })) {
  console.log(payment.id);
}

Retries and timeouts

The SDK retries automatically, up to maxNetworkRetries times, with exponential backoff and jitter, on:

  • network errors and timeouts;
  • 409 idempotency_request_in_progress;
  • 429, honouring Retry-After and RateLimit-Reset;
  • 5xx.

Every POST, PATCH and DELETE gets an idempotency key generated by the SDK (a UUID v4) and reused across its retries, so retries never create a second object. Pass your own idempotencyKey when your application may re-submit the same operation later, for example after a crash: an SDK-generated key does not survive a restart.

Webhooks

const event = zp.webhooks.constructEvent(rawBody, req.headers['zylopay-signature'], process.env.ZYLOPAY_WEBHOOK_SECRET!);

rawBody must be the body exactly as received (a string or Buffer). constructEvent checks the timestamp (5 minutes by default, or the fourth argument in seconds), accepts any matching v1 signature, and returns the parsed event. It throws ZyloPay.errors.SignatureVerificationError otherwise, with a code such as malformed, no_matching_signature or timestamp_out_of_tolerance. ZyloPay.webhooks.constructEvent(…) works without a client or API key. See Verify signatures.

To test your handler without ZyloPay, sign a payload yourself:

const header = ZyloPay.webhooks.generateTestHeaderString({ payload: rawBody, secret: 'whsec_test_secret' });
// Pass several secrets to simulate a rotation overlap: { payload, secret: [newSecret, oldSecret] }

Errors

Every error extends ZyloPay.errors.ZyloPayError and has .type, .code, .param, .message, .requestId, .statusCode and .headers.

ClassWhen
AuthenticationError401
PermissionError403
InvalidRequestError400, 409 invalid_request_error
NotFoundError404 (a subclass of InvalidRequestError)
IdempotencyErroridempotency_error
RateLimitError429, after retries
APIError5xx after retries, or an unknown error type
ConnectionErrorNo response. .code is network_error, timeout or aborted.
SignatureVerificationErrorA webhook failed verification.
try {
  await zp.paymentIntents.cancel(intentId, { idempotencyKey: `cancel-${intentId}` });
} catch (err) {
  if (err instanceof ZyloPay.errors.InvalidRequestError && err.code === 'payment_intent_unexpected_state') {
    // already paid: refund instead
  } else {
    throw err;
  }
}

Request IDs

Each result exposes the response metadata:

const payment = await zp.payments.retrieve('pay_1042');
console.log(payment.lastResponse.requestId, payment.lastResponse.statusCode);

const refund = await zp.refunds.create({ payment: 'pay_1042' }, { idempotencyKey: 'refund-R-2211' });
console.log(refund.lastResponse.idempotencyKey, refund.lastResponse.idempotentReplayed);

lastResponse also carries the response headers and the apiVersion it was rendered with. It is non-enumerable, so it does not appear when you serialize the object.

On this page