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

@hello-bill/node

v2.12.1

Published

Server-side SDK for the HelloBill Partner API. Framework-agnostic core with an Express adapter; ships a webhook verifier and the spec-derived TypeScript types.

Readme

@hello-bill/node

Server-side SDK for the HelloBill Partner API (Revision 1.13) (api_version 2026-05-10). Framework-agnostic core with an Express adapter; ships a webhook verifier and the spec-derived TypeScript types.

  • ESM only, bare TypeScript source — no runtime dependencies.
  • Subpath imports: @hello-bill/node/express, @hello-bill/node/webhook, @hello-bill/node/types.
  • Auto OAuth token caching, single-flight refresh, idempotency, retry with Retry-After, per-attempt timeout.

Install

npm install @hello-bill/node
# or
bun add @hello-bill/node

The package is currently private: true and consumed via the monorepo workspace. See Pre-publish checklist below.

Quick start (Express)

import express from 'express';
import { createHellobillRouter } from '@hello-bill/node/express';

const app = express();

app.use('/api/hellobill', createHellobillRouter({
  clientId: process.env.HELLOBILL_CLIENT_ID!,
  clientSecret: process.env.HELLOBILL_CLIENT_SECRET!,
  apiBaseUrl: process.env.HELLOBILL_API_BASE_URL!,
  buildSessionPayload: async (req) => {
    // Map whatever your host page posted → canonical snake_case SessionPayload.
    const body = req.body as { firstName: string; lastName: string; email: string };
    return {
      customer: {
        first_name: body.firstName,
        last_name: body.lastName,
        email: body.email,
      },
      addresses: { current: { /* ... */ } },
      move: { in: { move_in_date: '2026-06-01' } },
      consent: { data_sharing_accepted: true },
    };
  },
}));

app.listen(3000);

The router applies express.json({ limit: '1mb' }) internally — do not wrap it with another body parser.

Endpoints mounted

createHellobillRouter(opts) mounts these paths under the prefix you choose:

| Method | Path | Upstream | |--------|--------------|-----------------------------------------------------| | POST | /session | POST /partner/sessions | | GET | /products | GET /partner/sessions/:session_id/products | | GET | /loa | GET /partner/sessions/:session_id/loa | | POST | /customers | POST /partner/sessions/:session_id/customers | | GET | /status | GET /partner/customers/:customer_id/status |

/products, /loa, /customers and /status require a session_token Bearer (issued from POST /session). The SDK decodes it without verification to extract session_id and forwards the upstream call with a fresh OAuth access token.

Framework-agnostic core

Partners on Hono / Koa / Fastify / Nitro / raw Node can skip the Express adapter and use the core directly:

import { createHellobillHandler } from '@hello-bill/node';

const handlers = createHellobillHandler({
  clientId: '…', clientSecret: '…', apiBaseUrl: '…',
  buildSessionPayload: async (req) => ({ /* … */ }),
});

// Each handler takes the SDK's portable HellobillRequest / HellobillResponse.
// You write the ~15-line adapter that shapes your framework's req/res into them.
await handlers.session(req, res);

Webhook verification

import express from 'express';
import { verifyWebhook, WebhookVerificationError } from '@hello-bill/node/webhook';

app.post(
  '/api/hellobill/webhooks',
  express.raw({ type: 'application/json' }), // ← raw bytes, not parsed JSON
  (req, res) => {
    try {
      const event = verifyWebhook(
        req.body,
        req.header('x-hellobill-signature')!,
        process.env.HELLOBILL_WEBHOOK_SECRET!,
        { tolerance: 300, supportedVersions: ['v1'] },
      );
      // event is typed as WebhookEvent — switch on event.type
      res.status(204).end();
    } catch (err) {
      if (err instanceof WebhookVerificationError) return res.status(400).end();
      throw err;
    }
  },
);
  • Algorithm: HMAC-SHA256 over ${unix_timestamp}.${raw_body} (Stripe-style).
  • Header format: t=<unix>,v1=<hex> (and optionally v2=<hex> during rotation).
  • Replay window: 300s by default (tolerance).
  • Key rotation: pass supportedVersions: ['v1', 'v2'] and accept either; cut over once v2 is verified end-to-end.
  • Constant-time compare: implemented via crypto.timingSafeEqual.

Never parse and re-stringify the body before verification — the HMAC is computed over the exact bytes the server sent.

Token management

  • Automatic client_credentials OAuth grant against POST /auth/partner/token.
  • Cached in-process by default; refreshed at expires_in − 30s.
  • Single-flight: parallel API calls during a refresh share one upstream request.
  • Pluggable via tokenStorage:
import type { TokenStorage } from '@hello-bill/node';

const redisStorage: TokenStorage = {
  async get() { /* return { accessToken, expiresAt } | null */ },
  async set(tok) { /* persist */ },
};

createHellobillRouter({ /* …, */ tokenStorage: redisStorage });

Retry policy

Applied to every upstream call:

  • Retried statuses: 429, 500, 502, 503, 504, plus auth.token_expired (refreshes the token then retries once).
  • Backoff: exponential with jitter; honours Retry-After (seconds or HTTP date).
  • Attempts: up to 5 per call.
  • Timeout: per-attempt, default 30 000 ms; override with upstreamTimeoutMs.
  • Network failures: a hung / refused / aborted fetch becomes a clean internal.error envelope (502/504 depending on failure kind).

Idempotency

  • The SDK auto-generates a UUID v4 Idempotency-Key for every POST.
  • Override with idempotencyKeyFactory: () => string.

Gotcha: the Partner API caches the first response keyed by Idempotency-Key and ignores the body on subsequent requests with the same key. If you reuse the same key for two semantically-different POSTs (e.g. two different customer payloads), you get the first response back — not an error. Partners must mint a distinct key per logical operation.

Types

The full spec surface is re-exported under a stable subpath:

import type {
  SessionPayload,
  SessionResponse,
  ProductsResponse,
  LoaResponse,
  CustomersPayload,
  CustomersResponse,
  StatusResponse,
  WebhookEvent,
} from '@hello-bill/node/types';

Coverage

Measured with bun run test:coverage (vitest + v8). 69 / 69 tests passing.

| Area | Statements | Branches | Functions | Lines | |-----------|------------|----------|-----------|--------| | All | 90.90 % | 84.95 % | 96.49 % | 94.70 % | | core/ | 91.63 % | 85.02 % | 97.43 % | 95.79 % | | express/| 83.33 % | n/a | 90.90 % | 86.20 % | | webhook/| 90.90 % | 89.18 % | 100.00 % | 94.11 % |

Uncovered lines are exhaustively defensive branches (e.g. unreachable else in error envelope shaping, top-level Express error middleware that fires only on developer mis-wiring).

Pre-publish checklist

Before the first npm publish:

  • [ ] Remove "private": true from package.json.
  • [ ] Confirm the "files" field (currently ["src", "README.md"]) — add a dist/ if you ship compiled output.
  • [ ] Add a build step (tsup or tsc) and point "types" at the emitted .d.ts.
  • [ ] Verify all "exports" subpaths resolve post-build (., /express, /webhook, /types).
  • [ ] Choose a licence + add LICENSE file.
  • [ ] Add "repository" and "homepage" fields.
  • [ ] Seed CHANGELOG.md (recommend Keep a Changelog).
  • [ ] Run npm pack --dry-run and review the tarball contents.

License

TBD.