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

tipalti-sdk

v1.0.0

Published

A production-grade TypeScript client for the full Tipalti API surface: the modern OAuth2 REST API, the legacy HMAC-signed SOAP Payee/Payer API, and the Procurement REST API.

Readme

tipalti-sdk

A production-grade TypeScript client for the full Tipalti API surface: the modern OAuth2 REST API, the legacy HMAC-signed SOAP Payee/Payer API, and the Procurement REST API.

Zero runtime dependencies. Built entirely on Web-standard APIs — the native fetch, crypto.subtle (Web Crypto), URLSearchParams, AbortController — so it works unmodified in Node 18+, browsers, and edge runtimes (Cloudflare Workers, Deno, Bun) alike. Ships as dual ESM/CJS with full TypeScript declarations.

Installation

npm install tipalti-sdk

Quick start

import { TipaltiClient } from 'tipalti-sdk';

const client = new TipaltiClient({
  mode: 'sandbox',
  rest: { clientId: '...', clientSecret: '...' },
  soap: { payerName: '...', apiKey: '...' },
  procurement: { apiKey: '...' },
});

// Modern REST API
const page = await client.payees.list();

for await (const payee of client.payees.stream()) {
  console.log(payee.id, payee['name']);
}

// Legacy SOAP API
const result = await client.soap.payer.processPayments(
  [{ idap: 'vendor-123', amount: 100.0, currency: 'USD', refCode: 'pay-1' }],
  { paymentGroupTitle: 'Weekly payout' },
);

// Procurement REST API
const pos = await client.procurement.purchaseOrders.list();

Only populate the credential sections you actually use — a client built with just soap is fine as long as you only call client.soap.* methods. Unconfigured API families are undefined on the client instance (and typed that way), so accessing them without configuring them is a compile-time error, not a runtime surprise.

Error handling

Every error extends TipaltiError, so instanceof works uniformly, and each subclass carries the details specific to its failure kind:

import { AuthenticationError, RateLimitError, ValidationError, SoapFaultError } from 'tipalti-sdk';

try {
  await client.payees.get('p_123');
} catch (err) {
  if (err instanceof AuthenticationError) {
    // bad/expired credentials
  } else if (err instanceof RateLimitError) {
    // rate limited; err.retryAfterMs has the hint, if any
  } else if (err instanceof ValidationError) {
    // malformed request; err.errors has field-level details
  } else if (err instanceof SoapFaultError) {
    // SOAP <soap:Fault>; err.faultCode / err.faultString
  } else {
    throw err;
  }
}

Pagination

Every REST list method has a matching stream() returning a native AsyncGenerator — use for await...of, or the collect helper:

import { collect } from 'tipalti-sdk';

for await (const invoice of client.invoices.stream({ status: 'pending' })) {
  // ...
}

const all = await collect(client.invoices.stream({ status: 'pending' }));

SOAP signing

HMAC-SHA256 request signing (built on crypto.subtle, not a Node-specific crypto module) is handled automatically — every client.soap.payee/client.soap.payer method knows its operation's EAT (Encryption Additional Terms) parameter and folds it into the signed request for you. All 45 legacy operations (21 Payee + 24 Payer) are covered.

Procurement employee import

import { readFile } from 'node:fs/promises';

const csv = await readFile('employees.csv', 'utf8');
await client.procurement.employees.importEmployees(csv);

IPN webhooks

import { parseWebhook, webhookEventType } from 'tipalti-sdk';

app.post('/webhooks/tipalti', async (req, res) => {
  const event = parseWebhook(await req.text());
  handleEvent(webhookEventType(event), event);
  res.sendStatus(200);
});

Telemetry

const client = new TipaltiClient({
  rest: { clientId, clientSecret },
  onRequest: (event) => {
    console.log(
      `${event.api}.${event.operation} -> ${event.status ?? event.error} in ${event.durationMs}ms`,
    );
  },
});

Rate limiting

import { RateLimiter } from 'tipalti-sdk';

const limiter = new RateLimiter(5, 60_000); // Procurement API's documented PO-update limit
await limiter.wait();
await client.procurement.purchaseOrders.update(attrs);

Design notes

  • HTTP transport: built on native fetch with AbortController-based timeouts, exponential backoff + full jitter on retries, and a pluggable onRequest telemetry hook — no HTTP client dependency.
  • XML: SOAP responses are parsed with a small, purpose-built tokenizer (src/soap/xml.ts) rather than a general XML library — handles the nested-element/self-closing-tag/entity subset Tipalti's SOAP API actually produces, with namespace-prefix stripping so <soap:Fault> matches the same way local-name() would in XPath. Not a general-purpose XML parser by design.
  • REST endpoint shapes (Payees/Invoices/Payments) follow Tipalti's documented conventions for the modern REST API — see the doc comment on src/rest/client.ts if your instance's exact response envelope differs; the request/auth/error-handling machinery there is meant to be reused as-is.
  • Custom fetch implementation supported via TipaltiConfig.fetch, for non-standard runtimes or test injection.

Quality

npm run typecheck   # tsc --noEmit against both src/ and test/ (strict mode)
npm run lint         # eslint, typescript-eslint strict+stylistic type-checked rulesets
npm run format:check # prettier
npm run test          # vitest — 105 tests
npm run test:coverage # vitest --coverage
npm run build          # tsup — dual ESM/CJS + .d.ts

All clean, verified in a fresh checkout. Test coverage is ~84% statements overall — REST resource modules and the shared HTTP/OAuth2/ config layers are fully covered; the legacy SOAP Payee/Payer wrapper classes (45 thin methods total) are covered by a representative sample per class (idap handling, EAT-parameter extraction, nested/repeated field rendering) rather than one test per method, since they're low-risk, uniform pass-throughs onto the already-thoroughly-tested SoapClient.call engine.

Testing this package

The test suite uses a small node:http-based mock server (test/support/mockServer.ts) instead of nock/msw, keeping the dependency list minimal even for development/test.

npm test

License

MIT