@billium/node
Official Node.js SDK for Billium — invoices, webhook management, and webhook signature verification.
@billium/node is the official Node.js SDK for Billium. It covers invoices, customers, products, wallets, webhook management, and webhook signature verification.
Coverage: the SDK exposes billium.invoices, billium.customers, billium.products, billium.wallets, and billium.webhooks. A few less-common REST resources (API keys, team management, reports) aren't wrapped yet — call those directly with your HTTP client of choice, using the same x-api-key header documented in Authentication.
Installation
npm install @billium/node
# or
pnpm add @billium/node
# or
yarn add @billium/nodeRequirements: Node.js ≥ 18.
Initialization
import { Billium } from '@billium/node';
const billium = new Billium({
apiKey: process.env.BILLIUM_API_KEY, // sk_... or pk_...
merchantId: process.env.BILLIUM_MERCHANT_ID, // mer_...
webhookSecret: process.env.BILLIUM_WEBHOOK_SECRET, // whsec_...
});Billium is a named export only — there is no default export. Both ESM and CJS consumers must destructure it:
// ESM
import { Billium } from '@billium/node';
// CJS
const { Billium } = require('@billium/node');Constructor options
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | — | Billium API key. Use a secret key (sk_*) for full access. Public keys (pk_*) only cover invoices.create, invoices.get, and read-only product/wallet access; calling anything else throws a BilliumError at the SDK layer without a round-trip. |
merchantId | string | — | Your merchant ID (mer_...). Required together with apiKey to call the merchant resource methods (invoices, customers, products, wallets) and the webhook management methods. |
webhookSecret | string | — | Your webhook secret (whsec_...). When set, billium.webhooks.verify() can be called without passing the secret every time. |
baseUrl | string | https://api.billium.to | Override the API base URL (useful for self-hosted deployments or integration tests against a staging env). |
maxRetries | number | 2 | Retry attempts on network errors, 5xx, and 429. Set to 0 to disable. |
baseDelayMs | number | 500 | Starting backoff delay. Subsequent retries use exponential backoff with full jitter. |
maxDelayMs | number | 30000 | Upper bound on the backoff delay. Retry-After headers are honored and clamped by this value. |
Retry behavior
GET,PUT,PATCH,DELETEare always retried whenmaxRetries > 0.POSTis retried only when anIdempotency-Keyheader is set on the request — otherwise retrying could create a duplicate resource.invoices.create()sends this header automatically when you passoptions.idempotencyKey.- Retryable statuses:
429,500,502,503,504. Everything else (400, 401, 403, 404, 409, 422…) throwsBilliumApiErrorimmediately. Retry-Afterheaders are honored — the SDK waits for the server-specified delay before retrying.
See Idempotency for the full rationale and recommended patterns.
Invoices
Requires apiKey and merchantId in the constructor.
billium.invoices.create(params, options?)
Creates a new invoice.
import { randomUUID } from 'crypto';
const invoice = await billium.invoices.create(
{
name: 'Order #8821',
rawAmount: 49.99,
currency: 'USD',
customerEmail: 'customer@example.com',
redirectUrl: 'https://yoursite.com/thank-you',
},
{ idempotencyKey: randomUUID() },
);
console.log(invoice.id); // 'inv_...'
console.log(invoice.status); // 'AWAITING_PAYMENT'Params
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Invoice display name (max 200 chars). |
rawAmount | number | Yes | Expected amount in the currency (min 0, max 1 000 000). |
currency | string | No | Currency code. Defaults to USD. |
customerEmail | string | No | Customer email. |
customerName | string | No | Customer name. |
customerAddress | string | No | Customer billing address. |
customerPhoneNumber | string | No | Customer phone number. |
description | string | No | Invoice description (max 1000 chars). |
redirectUrl | string | No | Post-payment redirect URL. |
Options
| Field | Type | Description |
|---|---|---|
idempotencyKey | string | Set this in production. Enables safe retries and protects against accidental duplicate invoices when your own code retries. See Idempotency. |
billium.invoices.get(invoiceId)
const invoice = await billium.invoices.get('inv_...');billium.invoices.list(params?)
const result = await billium.invoices.list({ page: 1, limit: 20, search: 'order' });
// result.data → Invoice[]
// result.total → number
// result.page → number
// result.limit → numberbillium.invoices.cancel(invoiceId)
Cancels an invoice. Throws BilliumApiError if the invoice is already in a terminal state, and BilliumError immediately if the configured API key is a public key (pk_*).
const invoice = await billium.invoices.cancel('inv_...');
console.log(invoice.status); // 'CANCELLED'Invoice shape
Decimal fields are serialized as strings to preserve precision — use a decimal library (decimal.js) for arithmetic.
interface Invoice {
id: string;
merchantId: string;
customerId: string | null;
productId: string | null;
name: string;
description: string | null;
redirectUrl: string | null;
rawAmount: string; // e.g. "49.990000"
endAmount: string; // e.g. "49.990000" (rawAmount + fees, when applicable)
currency: string;
status: InvoiceStatus;
expiresAt: string | null;
createdAt: string;
updatedAt: string;
// Relations — always present on responses, never undefined
customer: InvoiceCustomer | null;
product: InvoiceProduct | null;
payments: InvoicePayment[];
invoiceTimeline: InvoiceTimelineEntry[];
}Customers
Requires apiKey and merchantId, and the apiKey must be a secret key (sk_*) — every customer method is secret-only.
Customers aren't created through the SDK: a customer record is provisioned automatically the first time you generate an invoice for a given email under your merchant. This client lists, retrieves, and updates those records.
billium.customers.list(params?)
const result = await billium.customers.list({ page: 1, limit: 20, search: 'jane' });
// result.data → Customer[]Accepts page (default 1), limit (max 100, default 10), and search (matches email, name, phone, address, or ID).
billium.customers.get(customerId)
Returns a single customer, including a derived location (approximate country/city from the customer's checkout activity; either field is null when it can't be determined). location is present only on get() — not on list() rows or the update() response.
const customer = await billium.customers.get('cus_...');billium.customers.stats(customerId)
Aggregate spend and invoice stats for a customer.
const stats = await billium.customers.stats('cus_...');
// { revenue, totalInvoices, paidInvoices, paidRate }revenue is the summed amount of the customer's paid invoices, returned as a string to preserve precision (or the number 0 when they have no paid invoices).
billium.customers.update(customerId, params)
Updates a customer's name, address, or phoneNumber — only the fields you pass change. Email isn't updatable: it's the key that ties a customer to their invoices.
await billium.customers.update('cus_...', { name: 'Jane Doe' });Customer shape
interface Customer {
id: string;
merchantId: string;
email: string;
name: string | null;
address: string | null;
phoneNumber: string | null;
metadata: unknown; // arbitrary JSON attached by Billium, or null
createdAt: string; // ISO 8601
updatedAt: string; // ISO 8601
location?: { // only on get(); each field may be null
country: string | null;
city: string | null;
};
}Products
Reads (get, list) work with a public key (pk_*); create, update, and delete require a secret key (sk_*).
billium.products.create(params)
const product = await billium.products.create({
name: 'Pro plan',
price: 49.99,
currency: 'USD',
});Params
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Product display name (max 200 chars). |
price | number | Yes | Fiat price (0–1 000 000). Sent as a number; returned as a string. |
currency | string | No | USD | EUR | GBP | CAD | AUD | JPY. Defaults to USD. |
description | string | No | Product description (max 1000 chars). |
image | string | No | Storage key of a previously uploaded image. |
isActive | boolean | No | Whether the product is purchasable. Defaults to true. |
askForName | boolean | No | Collect the customer's name at checkout. Defaults to false. |
askForAddress | boolean | No | Collect the customer's address at checkout. Defaults to false. |
askForPhoneNumber | boolean | No | Collect the customer's phone number at checkout. Defaults to false. |
billium.products.get(productId) / billium.products.list(params?)
const product = await billium.products.get('prd_...');
const result = await billium.products.list({ page: 1, limit: 20, search: 'plan' });
// result.data → Product[]billium.products.update(productId, params)
Accepts the same fields as create, all optional — only the keys you send change.
await billium.products.update('prd_...', { price: 59.99, isActive: false });billium.products.delete(productId)
Soft-deletes a product so it can no longer be purchased.
await billium.products.delete('prd_...');Product shape
interface Product {
id: string;
merchantId: string;
name: string;
description: string | null;
price: string; // decimal serialized as a string
currency: 'USD' | 'EUR' | 'GBP' | 'CAD' | 'AUD' | 'JPY';
isActive: boolean;
askForName: boolean;
askForAddress: boolean;
askForPhoneNumber: boolean;
image: string | null; // raw storage key — treat as opaque
signedImage?: string | null; // ready-to-render URL; prefer this for display
createdAt: string;
updatedAt: string;
}Wallets
Reads (get, list) work with a public key (pk_*); create, update, and delete require a secret key (sk_*). The API only ever returns public wallet configuration — a direct wallet's receiving address or an xpub wallet's extended public key. No private keys or seed material are stored or returned.
billium.wallets.list()
Returns a plain array (not a PaginatedResult) — a merchant has at most one wallet per cryptocurrency/network pair, so the full set is returned unpaginated.
const wallets = await billium.wallets.list(); // Wallet[]billium.wallets.get(walletId)
const wallet = await billium.wallets.get('wal_...');billium.wallets.create(params)
Adds a wallet. Pass address for a DIRECT_WALLET (one static receiving address, reused across invoices) or xpub for an XPUB_WALLET (a fresh address derived per invoice; BTC and LTC only). Only one wallet may exist per cryptocurrency/network pair — a duplicate is rejected.
const wallet = await billium.wallets.create({
cryptocurrency: 'BTC',
network: 'BTC',
walletType: 'DIRECT_WALLET',
address: 'bc1q...',
});Params
| Field | Type | Required | Description |
|---|---|---|---|
cryptocurrency | string | Yes | Asset the wallet settles in (BTC, ETH, USDT, USDC, …). |
network | string | Yes | Chain the wallet belongs to. Must be a network the asset is supported on. |
walletType | string | Yes | DIRECT_WALLET or XPUB_WALLET. |
address | string | Cond. | Receiving address. Required for DIRECT_WALLET. Validated against network. |
xpub | string | Cond. | Extended public key. Required for XPUB_WALLET. |
derivationPath | string | No | Derivation path for xpub wallets (e.g. m/84'/0'/0'/0). |
isEnabled | boolean | No | Whether the wallet can receive new payments. Defaults to true. |
requiredConfirmations | number | No | Confirmation depth before a payment settles. Each asset enforces its own minimum. |
billium.wallets.update(walletId, params)
Updates mutable configuration (address, xpub, isEnabled, requiredConfirmations, derivationPath). Identity fields (cryptocurrency, network, walletType) can't change — create a new wallet instead.
await billium.wallets.update('wal_...', { isEnabled: false });billium.wallets.delete(walletId)
Deletes a wallet. Throws BilliumApiError if the wallet still has active (non-terminal) payments against it.
await billium.wallets.delete('wal_...');Wallet shape
interface Wallet {
id: string;
merchantId: string;
cryptocurrency: 'BTC' | 'ETH' | 'USDT' | 'USDC' | 'BNB' | 'SHIB' | 'POL' | 'LTC' | 'DAI' | 'CRO' | 'TRX' | 'UNI';
network: 'BTC' | 'ETH' | 'BNB' | 'POL' | 'LTC' | 'CRO' | 'TRX';
walletType: 'DIRECT_WALLET' | 'XPUB_WALLET';
isEnabled: boolean;
requiredConfirmations: number;
address: string | null; // set for DIRECT_WALLET
xpub: string | null; // set for XPUB_WALLET (public key)
derivationPath: string | null;
createdAt: string;
updatedAt: string;
}Webhook signature verification
billium.webhooks.verify(rawBody, signature, secret?, options?)
Parses and verifies an incoming webhook request. Throws if the signature is invalid or the timestamp is outside the tolerance window.
// With webhookSecret set in the constructor:
const event = billium.webhooks.verify(
rawBody, // Buffer | string — raw request body
req.headers['x-signature'], // x-signature header value
);
// Or pass the secret explicitly (overrides the constructor value):
const event = billium.webhooks.verify(rawBody, signature, 'whsec_...');Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
rawBody | Buffer | string | — | Raw request body, not parsed JSON. |
signature | string | — | Value of the x-signature header. |
secret | string | — | Webhook secret. Can be omitted if webhookSecret is set in the constructor. |
options.tolerance | number | 300 | Max allowed age of the timestamp in seconds. Set to 0 to disable. |
Returns WebhookEvent
interface WebhookEvent {
event: string; // e.g. 'invoice.paid'
id: string; // unique event ID — use for deduplication
data: unknown; // event-specific payload
timestamp: string; // ISO 8601
}Throws
| Error | When |
|---|---|
BilliumError | No secret was provided (neither in constructor nor as argument) |
BilliumWebhookSignatureError | Header is malformed or HMAC does not match |
BilliumWebhookTimestampError | Timestamp is outside the tolerance window |
Webhook management
Requires apiKey and merchantId, and the apiKey must be a secret key (sk_*). Public keys throw BilliumError at the SDK layer before hitting the network.
billium.webhooks.create(params)
const webhook = await billium.webhooks.create({
url: 'https://yoursite.com/webhooks/billium',
events: ['invoice.paid', 'invoice.expired'], // or ['invoice.*']
description: 'Production order fulfillment',
});
// Store this secret now — it's only shown once.
const secret = webhook.webhookSecrets[0].secretKeyPreview; // 'whsec_...'Params
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS URL Billium will POST events to. |
events | WebhookEventType[] | Yes | Subscribed events. Use invoice.* or payment.* for wildcards. |
description | string | No | Free-text description. |
isActive | boolean | No | Defaults to true. |
retryCount | number | No | 0–10. Defaults to 3. |
timeout | number | No | Delivery timeout in ms (1000–30000). Defaults to 30000. |
billium.webhooks.list()
const webhooks = await billium.webhooks.list();billium.webhooks.update(webhookId, params)
await billium.webhooks.update('wh_...', {
events: ['invoice.*', 'payment.confirmed'],
});billium.webhooks.delete(webhookId)
await billium.webhooks.delete('wh_...');billium.webhooks.ping(webhookId)
Sends a test delivery to the endpoint so you can confirm your handler is reachable.
await billium.webhooks.ping('wh_...');Error types
All errors extend BilliumError, which extends the native Error class.
import {
BilliumError,
BilliumApiError,
BilliumWebhookSignatureError,
BilliumWebhookTimestampError,
} from '@billium/node';
// API errors
try {
const invoice = await billium.invoices.create({ /* ... */ });
} catch (err) {
if (err instanceof BilliumApiError) {
console.error(err.status); // HTTP status code, e.g. 400, 401, 403
console.error(err.code); // optional error code from the API
console.error(err.message); // human-readable message
}
}
// Webhook errors
try {
const event = billium.webhooks.verify(rawBody, signature);
} catch (err) {
if (err instanceof BilliumWebhookTimestampError) {
// Possible replay attack — reject
return res.status(400).send();
}
if (err instanceof BilliumWebhookSignatureError) {
// Invalid or tampered signature
return res.status(400).send();
}
throw err;
}| Class | name | Cause |
|---|---|---|
BilliumError | 'BilliumError' | Base class / configuration error (e.g. pk_* used for sk_*-only method) |
BilliumApiError | 'BilliumApiError' | Non-2xx HTTP response from the Billium API |
BilliumWebhookSignatureError | 'BilliumWebhookSignatureError' | Malformed header or HMAC mismatch |
BilliumWebhookTimestampError | 'BilliumWebhookTimestampError' | Timestamp outside tolerance window |
See Errors for the REST error envelope.