Libraries

TypeScript SDK

@fomopay/sdk — zero dependencies, typed, fetch + Web Crypto only. Node 18+, Bun, Deno, Cloudflare Workers and Vercel Edge.

Terminal
npm install @fomopay/sdk
client.ts
import { FomoPay } from "@fomopay/sdk";

const fomopay = new FomoPay({
  apiKey: process.env.FOMOPAY_API_KEY, // fp_test_… or fp_live_…
  baseUrl: "https://fomopay.family/v1", // or set FOMOPAY_BASE_URL
  timeoutMs: 30_000, // per attempt
  maxRetries: 2, // on 429 / 5xx / network errors, same idempotency key
});

Options

apiKeystringrequired
A project key. The constructor throws if it's missing or malformed, and refuses to run in a browser (opt out with dangerouslyAllowBrowser for local tooling only).
baseUrlstringoptional
This deployment: https://fomopay.family/v1 (or set FOMOPAY_BASE_URL). Locally http://localhost:6280/v1.
timeoutMsnumberoptional
Per attempt. Default 30 000.
maxRetriesnumberoptional
Default 2. Retries 429, 5xx, timeouts and network errors.
backoff{ baseMs, maxMs }optional
Default 500 ms doubling to 8 s, with ±25 % jitter. Retry-After is honoured up to 60 s.
fetchtypeof fetchoptional
Bring your own fetch (proxies, instrumentation, tests).
headersRecord<string, string>optional
Extra headers on every request.

Per-call options

Every method takes an optional last argument { idempotencyKey, timeoutMs, maxRetries, signal, headers }.

TypeScript
const ctl = new AbortController();

await fomopay.payments.get("pay_8k2m4q7r9t1v3x5z6b0c", {
  timeoutMs: 5_000,
  maxRetries: 0,
  signal: ctl.signal,
});

Methods

MethodEndpointDoes
send(params)POST /v1/rewardsPay a handle from your Reward Vault (USDG). Auto idempotency key. Alias: pay().
rewards.create(params)POST /v1/rewardsSame, with your own (required) idempotencyKey, reason, metadata, eligibility.
payments.create(params)POST /v1/paymentsWallet checkout (USDG or ETH) or vault payment. Auto idempotency key.
payments.get(id)GET /v1/payments/:idOne payment; refreshes in-flight ones from the chain.
payments.list(query)GET /v1/paymentsA page: { data, next_cursor, has_more }.
payments.iterate(query)GET /v1/payments …Async iterator over every page.
payments.wait(id, opts)GET /v1/payments/:id …Polls until CONFIRMED / FAILED / CANCELED (or `until`).
resolve(handle, { asset })GET /v1/resolve/@handleIdentity state and payout summary. No addresses.
profiles.payments(handle)GET /v1/profiles/@handle/paymentsYour project's payments to one identity.
bulk.validate(params)POST /v1/payments/bulkValidate rows; returns the batch. Nothing is paid.
bulk.execute(id, { onInvalid })POST /v1/payments/bulk/:id/executePay the payable rows from the vault.
bulk.get(id)GET /v1/payments/bulk/:idBatch progress and per-row results.
vaults.list() · vaults.get(id)GET /v1/vaults …Balance, allowance, reserved, available.
webhooks.create · list · get · update · delete · rotate/v1/webhooks …Manage endpoints; create and rotate return the secret.
me()GET /v1/meThe key's project, mode, scopes and rate limit.
FomoPay.webhooks.verify(raw, headers, secret)—Static. Verify a delivery and return the parsed event.

Amounts

Pass a decimal string ("100.25") or a number. Numbers are converted exactly — never rounded: 100 → "100", 1e-7 → "0.0000001". More decimals than the asset has (USDG 6, ETH 18) — like 0.1 + 0.2 for USDG — throws INVALID_AMOUNT before any request is made. Responses always carry decimal strings plus amount_base_units.

Retries and idempotency

  • Retried: 408, 429, 500, 502, 503, 504, timeouts, network errors. Never: other 4xx.
  • Every retry of a create call reuses the same Idempotency-Key, so a request that reached FomoPay before the connection dropped is replayed, not repeated.
  • send, payments.create and bulk.* generate a key when you don't pass one; rewards.create requires yours.
  • Calls without a key (webhook management) are only retried on 429.

Errors

Everything throws FomoPayError with code, status (0 when there was no response), message, requestId, details, idempotencyKey and a retryable getter. The full list is on Errors.

Types

TypeScript
import type { Payment, PaymentStatus, ResolveResult, BulkBatch, Vault, WebhookEndpoint, WebhookEvent, PaymentEventData } from "@fomopay/sdk";

function onConfirmed(event: WebhookEvent<PaymentEventData>) {
  const { recipient, amount, asset, tx } = event.data; // "@lyra", "500", "USDG", "0x…"
}

Response types mirror the /v1 JSON exactly (snake_case) and allow unknown extra fields, so new API fields never break your build.

Runtimes

worker.ts
// Cloudflare Workers / Vercel Edge / Deno / Bun — same API, nothing to polyfill
export default {
  async fetch(request: Request, env: { FOMOPAY_API_KEY: string }) {
    const fomopay = new FomoPay({ apiKey: env.FOMOPAY_API_KEY });
    const p = await fomopay.send({ to: "@lyra", amount: 100, asset: "USDG", idempotencyKey: "STREAK_REWARD_lyra_2026_10_12" });
    return Response.json({ payment: p.payment_id });
  },
};
Node 18

Node 18 doesn't expose Web Crypto globally: run it with --experimental-global-webcrypto, or pass your own idempotencyKey and verify webhooks on Node 20+. Node 20 and later need nothing.