Node.js: @tuppence/node
The server-side SDK for Node 18+: typed objects, an idempotency key on every write, safe retries and webhook verification.
1. Install and configure
Section titled “1. Install and configure”npm install @tuppence/nodeimport { Tuppence } from "@tuppence/node";
const tuppence = new Tuppence(process.env.TUPPENCE_SECRET_KEY);
const customer = await tuppence.customers.create({ name: "Ada Lovelace", email: "ada@example.com" });const payment = await tuppence.payments.create({ amount: 2000, currency: "gbp", customer: customer.id,});console.log(payment.id, payment.status, payment.approval_url);
// Iterate every item in a list. Pages are fetched as you reach them.for await (const c of tuppence.customers.listAll({ limit: 100 })) { console.log(c.id, c.email); break;}import { Tuppence } from "@tuppence/node";
const tuppence = new Tuppence(process.env.TUPPENCE_SECRET_KEY);
const customer = await tuppence.customers.create({ name: "Ada Lovelace", email: "ada@example.com" });const payment = await tuppence.payments.create({ amount: 2000, currency: "usd", customer: customer.id,});console.log(payment.id, payment.status, payment.approval_url);
// Iterate every item in a list. Pages are fetched as you reach them.for await (const c of tuppence.customers.listAll({ limit: 100 })) { console.log(c.id, c.email); break;}import { Tuppence } from "@tuppence/node";
const tuppence = new Tuppence(process.env.TUPPENCE_SECRET_KEY);
const customer = await tuppence.customers.create({ name: "Ada Lovelace", email: "ada@example.com" });const payment = await tuppence.payments.create({ amount: 2000, currency: "eur", customer: customer.id,});console.log(payment.id, payment.status, payment.approval_url);
// Iterate every item in a list. Pages are fetched as you reach them.for await (const c of tuppence.customers.listAll({ limit: 100 })) { console.log(c.id, c.email); break;}2. Common tasks
Section titled “2. Common tasks”| Task | Call |
|---|---|
| Create a payment link | tuppence.paymentLinks.create({ name, amount, currency }) |
| Create a payment | tuppence.payments.create({ amount, currency, customer }) |
| Confirm one (test mode) | tuppence.payments.confirm(id, { payment_method: "pm_card_gb" })tuppence.payments.confirm(id, { payment_method: "pm_card_us" })tuppence.payments.confirm(id, { payment_method: "pm_card_de" }) |
| Capture, cancel | tuppence.payments.capture(id), tuppence.payments.cancel(id) |
| Refund | tuppence.refunds.create({ payment, amount }) |
| Customers | tuppence.customers.create / retrieve / update / list / listAll |
| Balance | tuppence.balance.retrieve() |
| Pay out | tuppence.payouts.create({}), tuppence.payouts.settings() |
| Webhook endpoints | tuppence.webhookEndpoints.create / list / retrieve / update / del |
| Verify a webhook | tuppence.webhooks.constructEvent(rawBody, signatureHeader, secret) |
| Act as a connected account | tuppence.as("acct_…") returns a client that sends Tuppence-Account |
| Anything else | tuppence.request("GET", "/v1/…", { body, query }), with the same auth, retries and idempotency |
Every list has list(params) for one page and listAll(params), an async iterator over all
pages.
What it does for you
Section titled “What it does for you”| Types | For every object and request, generated from the API’s contract so they can’t lag it |
Idempotency-Key | On every POST, reused across that call’s retries; pass { idempotencyKey } to choose it |
| Retries | Twice by default, with jittered backoff, honouring Retry-After. Retries network errors, timeouts, 5xx, 429 and a request still in flight; never another 4xx |
| Typed errors | CardError, InvalidRequestError, AuthenticationError, PermissionError, RateLimitError, IdempotencyError, ApiError, ConnectionError. All extend TuppenceError, with code, param, declineCode, requestId and the full body on raw |
import { CardError } from "@tuppence/node";
try { await tuppence.payments.confirm(paymentId, { payment_method: "pm_card_chargeDeclined" });} catch (err) { if (err instanceof CardError) console.log(err.code, err.declineCode, err.requestId); else throw err;}Instant payouts
Section titled “Instant payouts”Get a quote first. It says what arrives and when, or why not and where to fix it. The bank is
the default destination. The card balance (destination: "card_balance", UK only) is described before
it can be used. See Instant payouts.
const quote = await tuppence.payouts.instantQuote({ amount: 5872 });if (quote.available) { // £58.13 arrives within 30 minutes. The £0.59 fee comes out of the £58.72. await tuppence.payouts.create({ method: "instant", amount: quote.total_amount });} else { console.log(quote.reason, quote.message, quote.fix_url, quote.available_from);}
const { instant_payouts } = await tuppence.payouts.settings();console.log(instant_payouts.destinations.card_balance.message);const quote = await tuppence.payouts.instantQuote({ amount: 5872 });if (quote.available) { // $58.13 arrives within 30 minutes. The $0.59 fee comes out of the $58.72. await tuppence.payouts.create({ method: "instant", amount: quote.total_amount });} else { console.log(quote.reason, quote.message, quote.fix_url, quote.available_from);}
const { instant_payouts } = await tuppence.payouts.settings();console.log(instant_payouts.destinations.card_balance.message);const quote = await tuppence.payouts.instantQuote({ amount: 5872 });if (quote.available) { // €58.13 arrives within 30 minutes. The €0.59 fee comes out of the €58.72. await tuppence.payouts.create({ method: "instant", amount: quote.total_amount });} else { console.log(quote.reason, quote.message, quote.fix_url, quote.available_from);}
const { instant_payouts } = await tuppence.payouts.settings();console.log(instant_payouts.destinations.card_balance.message);Next-day payouts and your prices
Section titled “Next-day payouts and your prices”payouts.setNextDay switches next-day payouts (UK) on and
off. Until next_day.available, the call is refused with the reason. account.retrievePricing
returns every price the account pays now (list price or one agreed with Tuppence) with its dates.
const { next_day } = await tuppence.payouts.settings();if (next_day.available) await tuppence.payouts.setNextDay({ enabled: true });
const { prices } = await tuppence.account.retrievePricing();for (const p of prices) console.log(p.type, p.bps, p.fixed, p.source, p.ends_at);Options
Section titled “Options”new Tuppence(key, { apiVersion: "2026-09-20", // pin a version (default: your account's) maxRetries: 4, timeoutMs: 10_000,});await tuppence.balance.retrieve({ maxRetries: 0, timeoutMs: 2_000 }); // per requestbaseUrl (or TUPPENCE_API_URL in the environment) points it at another API, such as a test
environment.