npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zhexio/node

v0.2.0

Published

Official Node.js SDK for the Zhex API. Typed, retry-safe, idempotent by default.

Readme

@zhexio/node

Official Node.js SDK for the Zhex API.

npm install @zhexio/node
import Zhex from '@zhexio/node';

const zhex = new Zhex(process.env.ZHEX_SECRET_KEY!);

const customer = await zhex.customers.create({
  email: '[email protected]',
  name: 'Jane Doe',
});

const intent = await zhex.paymentIntents.create({
  amount: 19900,
  currency: 'brl',
  customer: customer.id,
  payment_method_types: ['pix'],
  products: [{ product: 'prod_…', price: 'price_…', quantity: 1 }],
});

Authentication

Pass your secret key (zsk_live_… or zsk_test_…) to the constructor. Never check it into source — the SDK will refuse to send requests if the key looks malformed, but a leaked key is a leaked key.

const zhex = new Zhex(process.env.ZHEX_SECRET_KEY!);

The SDK derives the mode (test vs. live) from the key prefix. There's no livemode toggle — a zsk_test_* key returns only sandbox data, a zsk_live_* key only production. Mismatched IDs across modes return 404, never 200.

Idempotency

Every POST and DELETE automatically gets an Idempotency-Key (UUIDv4). Retries on 429 / 5xx replay the same key, so you never double-charge a customer because of a flaky network. Pass your own key when you want the dedup boundary to match your business id:

await zhex.paymentIntents.create(
  { amount: 9700, currency: 'brl', customer: 'cus_…' },
  { idempotencyKey: `mensalidade:${customerId}:2026-04` },
);

If you replay the same key with a different body within 24h, the API returns 409 and the SDK throws ZhexIdempotencyError — fix the caller, don't reuse keys across logical operations.

Auto-pagination

Every list() returns a single page synchronously and exposes autoPagingEach() to walk every page lazily:

// page-at-a-time (Stripe-style)
const page = await zhex.customers.list({ limit: 100 });
for (const c of page.data) { /* … */ }
if (page.has_more) { /* fetch next */ }

// or iterate everything
for await (const c of zhex.customers.list({}).autoPagingEach()) {
  /* one request per 100 customers, transparently */
}

Cursor pagination uses starting_after / ending_before under the hood — no offsets, no page numbers, no skipped records under concurrent writes.

Webhook verification

Verify the Zhex-Signature header on every delivery — never trust a webhook payload that hasn't been verified.

import Zhex from '@zhexio/node';

app.post(
  '/webhooks/zhex',
  express.raw({ type: 'application/json' }), // RAW body — JSON-parsed bodies break the HMAC
  (req, res) => {
    let event;
    try {
      event = Zhex.webhooks.constructEvent(
        req.body,
        req.headers['zhex-signature'] as string,
        process.env.ZHEX_WEBHOOK_SECRET!,
      );
    } catch (err) {
      return res.status(400).send('invalid signature');
    }

    if (event.type === 'payment_intent.succeeded') {
      // …
    }
    res.json({ received: true });
  },
);

constructEvent enforces a 5-minute replay tolerance and uses timingSafeEqual to compare HMACs. If you need a wider window for a debugging session, pass a third arg — but don't ship that to prod, you'll defeat the anti-replay guarantee.

Errors

Every failure is one of these, all extending ZhexError:

| Class | Status | When | |---|---|---| | ZhexInvalidRequestError | 400 | Bad input — fix the call, don't retry | | ZhexAuthenticationError | 401 / 403 | Bad key, wrong key type, or revoked | | ZhexNotFoundError | 404 | Resource doesn't exist (or wrong mode) | | ZhexIdempotencyError | 409 | Same Idempotency-Key, different body | | ZhexRateLimitError | 429 | Surfaced after the SDK exhausts retries | | ZhexCardError | 402 | Card-side decline — card_declined, insufficient_funds, etc. | | ZhexAPIError | 5xx | Our problem; retry with the same idempotency key | | ZhexConnectionError | — | DNS / socket / fetch threw |

Each carries code, statusCode, requestId, and raw. Always include requestId when filing support tickets — we look up the request in the audit log straight from there.

try {
  await zhex.paymentIntents.create({ /* … */ });
} catch (err) {
  if (err instanceof ZhexCardError) {
    // show err.code to the customer ('card_declined', 'expired_card', …)
  } else if (err instanceof ZhexInvalidRequestError) {
    // log err.message + err.requestId, fix your call
  } else {
    throw err;
  }
}

Per-request options

Override retries, timeouts, or headers on a single call:

await zhex.refunds.create(
  { payment_intent: 'pi_…' },
  { idempotencyKey: 'refund:order-42', timeoutMs: 30_000, maxRetries: 1 },
);

What's covered

| Resource | Methods | |---|---| | customers | create, retrieve, update, list | | paymentIntents | create, retrieve, confirm, cancel, list | | paymentMethods | create (from token), retrieve, detach, list | | refunds | create, retrieve, list | | tokens | create (server-side; for browser tokenization use @zhexio/zhex-js) | | webhooks | constructEvent (static) |

The SDK doesn't try to wrap every resource Zhex exposes — only the ones you reach for in a charge flow. For raw access to anything else (events, balance, webhook endpoints, customer subscriptions), use fetch against the documented endpoints; the SDK is a convenience layer, not a wall.

Compatibility

  • Node 18+ (uses built-in fetch).
  • ESM and CJS both supported via dual exports.
  • TypeScript 5+ for the bundled .d.ts.

Status

0.1.0-alpha — the API surface above is stable, but we're still polishing some edges (better typed event narrowing, automatic Connect account propagation). Pin to a specific minor while we hit 1.0.0.

License

MIT.