Skip to content

fintech-sdk/moov-sdk

Repository files navigation

moov-client

A production-grade, zero-runtime-dependency TypeScript SDK for the Moov payments API — accounts, transfers, cards, ACH, issuing, wallets, disputes, and 30+ more resource areas covering the full API surface.

  • Zero runtime dependencies — built entirely on the Web Fetch API, Web Crypto, and FormData, so it runs unmodified on Node.js 18+, browsers, Deno, Bun, Cloudflare Workers, and other Fetch-compatible edge runtimes.
  • Dual ESM/CJSimport and require() both work out of the box, with full type declarations for each.
  • Fully typed — every request and response shape is a hand-derived TypeScript interface, not any.
  • Idempotent by default — mutation-safe endpoints (like transfers.create) auto-generate a UUID v4 idempotency key that's reused across retries.
  • Automatic retries — transient failures (429, 5xx, network errors) are retried with exponential backoff and full jitter, honoring the API's Retry-After hints.
  • Webhook verification — constant-time HMAC-SHA512 signature verification via a separate moov-client/webhooks entry point (keeps crypto code out of your main bundle unless you need it).

Install

npm install moov-client

Quick start

import { Moov } from "moov-client";

const moov = new Moov({
  publicKey: process.env.MOOV_PUBLIC_KEY!,
  privateKey: process.env.MOOV_PRIVATE_KEY!,
});

const account = await moov.accounts.create({
  accountType: "business",
  profile: {
    business: {
      legalBusinessName: "Whole Foods",
      businessType: "llc",
    },
  },
});

const transfer = await moov.transfers.create(account.accountID, {
  source: { paymentMethodID: sourcePaymentMethodID },
  destination: { paymentMethodID: destinationPaymentMethodID },
  amount: { currency: "USD", value: 2500 },
});

console.log(transfer.transferID, transfer.status);

Client-side (e.g. with a Moov.js-issued token) instead of a public/private key pair:

const moov = new Moov({ token: shortLivedAccessToken });

Error handling

Every method rejects with one of three typed errors:

Class When
MoovAPIError Moov returned a non-2xx response
MoovNetworkError The request never reached Moov (DNS, TCP, TLS, timeout, abort)
MoovDecodeError A 2xx response body wasn't valid JSON
import { isNotFound, isValidationError, MoovAPIError } from "moov-client";

try {
  return await moov.accounts.get(accountID);
} catch (err) {
  if (isNotFound(err)) return null;
  if (isValidationError(err)) {
    console.error("validation failed:", err.body);
  }
  if (err instanceof MoovAPIError) {
    console.error(err.statusCode, err.requestID, err.message);
  }
  throw err;
}

Other guards: isUnauthorized, isForbidden, isConflict, isRateLimited, isTransientError.

Idempotency

transfers.create() is idempotent by nature on Moov's API, so the SDK auto-generates a UUID v4 X-Idempotency-Key and reuses the same key across every retry of a single logical call. To stay deduplicated across process restarts (e.g. a webhook handler that might be invoked twice for the same order), pass your own key:

await moov.transfers.create(accountID, req, {
  idempotencyKey: `order-${orderID}`,
});

Retries & timeouts

const moov = new Moov({
  publicKey,
  privateKey,
  maxRetries: 3, // additional attempts beyond the first (default: 3)
  retryBaseDelayMs: 250, // default: 250
  retryMaxDelayMs: 8000, // default: 8000
  timeoutMs: 30_000, // per-request timeout; 0 disables it (default: 30000)
  onRetry: (attempt, delayMs, err) => console.warn(`retry ${attempt} in ${delayMs}ms`, err),
});

Every call also accepts a signal (AbortSignal) as part of its per-request options:

const controller = new AbortController();
await moov.accounts.get(accountID, { signal: controller.signal });

Webhooks

Import from the dedicated moov-client/webhooks entry point so signature-verification code (and its Web Crypto usage) stays out of bundles that don't need it:

import { parseEvent, InvalidSignatureError } from "moov-client/webhooks";

// Express example — req.headers works directly; for raw bytes/text, pass those instead.
app.post("/webhooks/moov", express.raw({ type: "application/json" }), async (req, res) => {
  try {
    const event = await parseEvent(req.headers, req.body, process.env.MOOV_WEBHOOK_SECRET!);
    switch (event.type) {
      case "transfer.updated":
        // event.data is `unknown` — validate/cast to your own payload shape
        break;
    }
    res.sendStatus(200);
  } catch (err) {
    if (err instanceof InvalidSignatureError) return res.sendStatus(401);
    throw err;
  }
});

parseEvent/verifySignature accept a native Headers, a Map, or a plain object as the header source, so they work the same in Express, Fastify, Next.js Route Handlers, or Cloudflare Workers.

Resources

Every one of Moov's resource areas is available as a property on the Moov instance:

Property Covers
accounts, capabilities, representatives, underwriting, billing, files, onboarding, resolution, partner Account lifecycle, KYC/KYB, billing, partner tooling
bankAccounts, cards, applePay, googlePay, paymentMethods, terminal, wallets Payment sources & rails
transfers, sweeps, refunds, disputes, issuing, invoices, paymentLinks, receipts, schedules Money movement
images, products, support Account-level tooling
branding, enrichment, institutions Enrichment & lookups
auth, e2ee OAuth2 tokens & end-to-end encryption keys

Every method also accepts a final RequestOptions argument (idempotencyKey, waitFor, apiVersion, maxRetries, signal, headers) to override defaults for a single call.

Requirements

  • Node.js 18+ (for native fetch, FormData, and crypto.subtle), or any modern browser / edge runtime with the same APIs.
  • TypeScript 5+ if you want the bundled type declarations (the package works fine from plain JavaScript too).

License

MIT

About

A production-grade, zero-runtime-dependency TypeScript SDK for the [Moov](https://moov.io) payments API — accounts, transfers, cards, ACH, issuing, wallets, disputes, and 30+ more resource areas covering the full API surface.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages