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

@taphubhq/sdk-server

v0.9.0

Published

Node-only SDK for Taphub server-to-server API with HMAC authentication

Downloads

101

Readme

@taphubhq/sdk-server

Node-only SDK for Taphub's server-to-server API with HMAC authentication.

Installation

npm install @taphubhq/sdk-server
# or
pnpm add @taphubhq/sdk-server

This package is for Node.js only. It must not be imported from browser or edge runtimes. The constructor will throw if window is defined.

Configuration

All five fields are required. The SDK does not read environment variables internally — you control how credentials are provided.

import { TaphubServer } from '@taphubhq/sdk-server';

const taphub = new TaphubServer({
  apikey: process.env.TAPHUB_APIKEY!,
  agencyId: process.env.TAPHUB_AGENCY_ID!,
  agencySecret: process.env.TAPHUB_AGENCY_SECRET!,
  builderCode: process.env.TAPHUB_BUILDER_CODE!,
  endpoint: process.env.TAPHUB_ENDPOINT!, // e.g. https://api.taphub.io
});

| Field | Purpose | |----------------|--------------------------------------------------------------------------------------------------------------------------| | apikey | Builder API key. Sent as x-api-key header on REST + GraphQL, AND as apikey field in REST request bodies. | | agencyId | Agency UUID. Sent as agency_id field in REST request bodies, and used as a variable on auth.userLogin. | | agencySecret | HMAC signing secret for REST calls. Never logged or serialized. | | builderCode | Builder identifier (human-readable handle). Sent as x-builder-code header on REST + GraphQL. | | endpoint | Base URL of the TapHub API. Must be https:// (or http://localhost). |

REST transport authentication layers

REST methods (wallet.*, user.create, ping) send S2S credentials on two layers simultaneously, because the backend enforces both:

  • Headersx-api-key and x-builder-code are read by the BuilderAuth middleware, which resolves the agency UUID from x-builder-code via its in-process cache and exposes it to controllers that read the agency from middleware context (e.g. wallet.getTxByRef's route).
  • Bodyapikey and agency_id are required by every Agency request VO's binding:"required" tags (controller ShouldBindJSON step). Some controllers also read agency_id from the body directly (e.g. wallet.transfer, wallet.getUserWallet, wallet.listUserWallets).

Authorization: HMAC <sig> is computed over the full JSON body including the SDK-appended apikey and agency_id. Do not modify the body after the SDK constructs it.

The endpoint must use https:// (or http://localhost for local development). Plain http:// URLs are rejected.

Operations

taphub.auth.userLogin(sessionToken)

Exchange an opaque builder session token (issued by your own backend after the user logs in) for a TapHub access token. The resulting accessToken is a JWT the frontend can hand to @taphubhq/sdk-core to make authenticated GraphQL calls.

const { accessToken, user } = await taphub.auth.userLogin('abc123');
// user: { id, name, currency }
// Return accessToken to your frontend; sdk-core will send it as Authorization: Bearer <jwt>

Errors:

  • TaphubValidationError (code InvalidPayload) — sessionToken was missing, empty, or not a string.
  • TaphubAuthError — TapHub rejected the session (codes include TokenExpired, Unauthorized, NotAuthenticated).
  • TaphubValidationError — other TapHub-side error with the original code preserved on the error.
  • TaphubServerError / TaphubInternalError — TapHub returned a 5xx or an unparseable response.

This method uses a GraphQL transport (POST {endpoint}/query) and sends x-builder-code + x-api-key headers. It does not sign requests with HMAC and is not subject to the SDK's retry policy — userLogin always issues exactly one request per call.

taphub.auth.loginS2S({ agencyUid, name? })

Server-to-server login. Use this from a builder backend when you want to mint a TapHub user accessToken directly from your own server identity — no Privy session token, no frontend round-trip. The user is keyed by agencyUid (the builder's own user identifier); pass name on first login to seed the display name.

const { accessToken, user } = await taphub.auth.loginS2S({
  agencyUid: 'user-123',
  name: 'Alice', // optional, used on user creation
});
// user: { id, name, currency }

Errors:

  • TaphubValidationError (code InvalidPayload) — input missing/non-object, agencyUid missing/empty/non-string, or name provided as non-string.
  • TaphubAuthError — HMAC signature rejected, missing agency context, or Unauthorized / TokenExpired / NotAuthenticated GraphQL error code.
  • TaphubValidationError — other TapHub-side error with the original code preserved on the error.
  • TaphubServerError / TaphubInternalError — TapHub returned a 5xx or an unparseable response.

This method uses the HMAC GraphQL transport (POST {endpoint}/query with Authorization: HMAC <sig> signed over the request body using agencySecret). It requires agencyId, agencySecret, builderCode, and apikey from the constructor — identical to other HMAC module calls (user.create, wallet.transfer). Like userLogin, it is not subject to the SDK's retry policy.

When to use which:

  • userLogin(sessionToken) — your frontend already has a Privy session for the user; your backend forwards the session token to TapHub.
  • loginS2S({ agencyUid }) — your backend is the source of truth for the user identity and just needs a TapHub accessToken to act on their behalf.

taphub.user.create({ agencyUid, username })

Provision a user under your agency.

const user = await taphub.user.create({
  agencyUid: 'user-123',
  username: 'alice',
});

taphub.wallet.transfer({ ref, agencyUid, amount, direction, currency })

Transfer funds to or from a user's wallet. ref is a mandatory idempotency key — use crypto.randomUUID() or a stable external reference.

import { randomUUID } from 'node:crypto';

const result = await taphub.wallet.transfer({
  ref: randomUUID(),
  agencyUid: 'user-123',
  amount: '10.00',
  direction: 'deposit',
  currency: 'USDC',
});

direction must be 'deposit' or 'withdraw'. amount must be a positive decimal string (not a number, and not prefixed with -) — the SDK negates it on the wire when direction === 'withdraw'. currency is required and must match a currency supported by your agency configuration.

Wire shape: the SDK posts { apikey, agency_id, tx_ref, agency_uid, amount, currency } to POST /api/user/v1/agency/transfer. The backend derives deposit/withdraw from the sign of amount; there is no direction field on the wire.

Result shape (WalletTransferResult): { id, walletId, userId, amount, beforeAmount, afterAmount, ref, timestamp }. amount carries the signed wire value (negative for withdraw). currency, type, and status are intentionally not on this response — use wallet.getTxByRef({ ref }) if you need them.

Wallet lookups

The SDK provides three read-only wallet methods. They use the same HMAC transport as wallet.transfer and return camelCase-typed objects (no snake_case keys leak to the caller).

taphub.wallet.getUserWallet({ agencyUid })

Fetch a user's default wallet for your agency's configured currency.

const wallet = await taphub.wallet.getUserWallet({ agencyUid: 'user-123' });
// wallet: { id, amount, currency, isEnabled }

taphub.wallet.listUserWallets({ agencyUid })

List all wallets for a user. In single-currency setups (v0.2) this typically returns one entry.

const wallets = await taphub.wallet.listUserWallets({ agencyUid: 'user-123' });
// wallets: Wallet[]

taphub.wallet.getTxByRef({ ref })

Look up a wallet transaction by its reference key. This uses the same ref value you pass to wallet.transfer — pass the ref from a transfer to retrieve the transaction details.

const tx = await taphub.wallet.getTxByRef({ ref: 'my-ref-123' });
// tx: { id, walletId, userId, amount, beforeAmount, afterAmount, currency, ref, source, type }

If the transaction is not found, the SDK throws TaphubNotFoundError with the backend error code preserved (e.g., 'TransactionNotFound').

v0.2 note: These methods do not accept a currency parameter. The backend infers the currency from your agency configuration. Multi-currency support is planned for a future version.

Exported types

import type { Wallet, WalletTransaction } from '@taphubhq/sdk-server';

taphub.ping()

Connectivity check against the Taphub API.

const response = await taphub.ping();

Security

Do not intercept the request body

The SDK signs the request body with HMAC-SHA256. The same bytes that are signed are sent on the wire. If you modify the body (e.g., via middleware that re-serializes JSON), the signature will not match and the server will reject the request with an authentication error.

Ensure that any HTTP client or proxy between your application and Taphub transmits the request body verbatim.

Secret redaction

agencySecret is stored as a non-enumerable property. JSON.stringify(client) returns '[REDACTED]' in place of the secret. Error messages never include the secret or the computed signature.

Replay risk (v0.1)

In v0.1, each signed request is valid indefinitely if replayed. This will be addressed in v0.2 with a timestamp and nonce in the HMAC payload, coordinated with a backend change. In the meantime, use ref-based idempotency for mutation endpoints and ensure that your agency's IP whitelist is configured in the Taphub dashboard.

Error handling

The SDK throws typed error classes:

| Class | HTTP Status | |---|---| | TaphubValidationError | 400, 422 | | TaphubAuthError | 401, 403 | | TaphubNotFoundError | 404 | | TaphubConflictError | 409 | | TaphubInternalError | 500–599, unmapped | | TaphubRateLimitError | 429 | | TaphubNetworkError | DNS/TCP/TLS failures |

TaphubRateLimitError and TaphubInternalError carry an optional retryAfterMs?: number parsed from the Retry-After response header when present.

All errors extend TaphubServerError and include code (backend error tag or HTTP_<status>) and status (HTTP status when available).

import { TaphubAuthError } from '@taphubhq/sdk-server';

try {
  await taphub.ping();
} catch (err) {
  if (err instanceof TaphubAuthError) {
    console.error('Auth failed:', err.code, err.status);
  }
}

Retry

Starting in 0.3.0, the SDK retries transient failures automatically with full-jitter exponential backoff.

Defaults

| Field | Default | Description | |---|---|---| | maxAttempts | 3 | Total attempts including the first | | baseDelay | 200 ms | Base for exponential backoff | | maxDelay | 5000 ms | Upper bound per sleep |

Trigger matrix

| Error class | HTTP context | Retry? | |---|---|---| | TaphubNetworkError | fetch failure | ✅ | | TaphubInternalError | 5xx | ✅ | | TaphubRateLimitError | 429 | ✅ | | TaphubAuthError | 401, 403 | ❌ | | TaphubValidationError | 400, 422 | ❌ | | TaphubNotFoundError | 404 | ❌ | | TaphubConflictError | 409 | ❌ |

Sync validation errors (thrown before any HTTP request) bypass the retry loop.

Backoff formula

delay = random_between(0, min(maxDelay, baseDelay * 2 ** attemptIndex))

attemptIndex is 0-based for the first scheduled retry.

Retry-After honoring

If a failed response carries a Retry-After header (integer seconds or HTTP-date), the SDK uses that value (clamped to maxDelay) for the next attempt instead of the computed backoff. No jitter is applied to a server-supplied delay.

Disable per call

await taphub.wallet.transfer(
  { ref: 'r-1', agencyUid: 'u-1', amount: '1.00', direction: 'deposit', currency: 'USDC' },
  { retry: false },
);

Disable for the whole client

const taphub = new TaphubServer({ apikey, agencyId, agencySecret, endpoint, retry: false });

Observability with onRetry

const taphub = new TaphubServer({
  apikey, agencyId, agencySecret, endpoint,
  retry: {
    onRetry: ({ attempt, error, nextDelayMs }) => {
      console.warn(`retry attempt ${attempt} after ${error.code}, sleeping ${nextDelayMs}ms`);
    },
  },
});

Errors thrown inside onRetry are caught and ignored — the retry loop always continues.

Mutation retry safety

Retry applies uniformly to mutations and reads. The backend dedupes by idempotency keys:

  • wallet.transfer dedupes by ref.
  • user.create dedupes by agencyUid.

A duplicate request surfaces as TaphubConflictError (409) — a deterministic, recognizable signal.

Upgrading from 0.2

In 0.2.x the SDK issued exactly one attempt per call. 0.3.0 enables retry by default. To preserve the previous fail-fast behavior, pass retry: false to the constructor or to individual calls.

HMAC algorithm

The SDK uses HMAC-SHA256 over the raw UTF-8 bytes of the JSON request body, hex-encoded as a 64-character string. This matches the algorithm in grid-user-service/pkg/cryptography/hmac.go. See the design doc for details.

License

MIT