@liquidafy/liquidafy-js
v1.1.0
Published
Official Liquidafy SDK for Node.js — crypto-native checkout for LATAM (USDT charges, withdrawals, refunds, webhooks).
Downloads
443
Maintainers
Readme
Liquidafy SDK — JavaScript/TypeScript
Official, hand-crafted SDK for Node.js. npm:
@liquidafy/liquidafy-js.Liquidafy — crypto-native checkout gateway for LATAM. Accept USDT, pay out in fiat, refund customers — one REST API.
Install
npm install @liquidafy/liquidafy-js
# or
pnpm add @liquidafy/liquidafy-jsRequirements: Node.js >= 18 (native fetch). Zero runtime dependencies. TypeScript types included.
⚠️ NEVER use your secret key in the browser
lr_live_* and lr_test_* are secret keys. Anyone who reads them in your page source can move your funds. This SDK is server-side only in 1.x and throws immediately if instantiated in a browser.
For browser checkout, create the charge on your server and redirect the buyer to the hosted pay-page (charge.checkout_url).
A browser bundle with publishable keys (lr_pk_*) and an embeddable checkout component is planned for a future release — it is intentionally not part of this package today.
Quickstart
1. Create a charge (server-side)
import { randomUUID } from 'node:crypto';
import { Liquidafy } from '@liquidafy/liquidafy-js';
const liquidafy = new Liquidafy(process.env.LIQUIDAFY_API_KEY!); // lr_test_... or lr_live_...
const charge = await liquidafy.charges.create(
{
amount: '100.00', // amounts are ALWAYS decimal strings — never floats
currency: 'USDT',
description: 'Pedido #1234',
customer: { email: '[email protected]' },
metadata: { order_id: '1234' },
},
{ idempotencyKey: randomUUID() }, // makes retries safe; the SDK warns if omitted
);
// Redirect the buyer to the hosted pay-page:
console.log(charge.checkout_url);2. Verify a webhook (Express)
The signature covers the raw body bytes — use express.raw, never express.json, on the webhook route.
import express from 'express';
import { Liquidafy, LiquidafySignatureError } from '@liquidafy/liquidafy-js';
const liquidafy = new Liquidafy(process.env.LIQUIDAFY_API_KEY!);
const app = express();
app.post(
'/webhooks/liquidafy',
express.raw({ type: 'application/json' }), // ← raw body, REQUIRED
(req, res) => {
try {
const event = liquidafy.webhooks.constructEvent(
req.body, // raw Buffer
req.headers['liquidafy-signature'] as string,
process.env.LIQUIDAFY_WEBHOOK_SECRET!, // shown once at endpoint creation
300, // tolerance in seconds (default 300)
);
switch (event.type) {
case 'charge.confirmed':
// fulfill the order — event.data.object is the Charge
break;
case 'withdrawal.confirmed':
break;
}
res.status(200).send('OK');
} catch (err) {
if (err instanceof LiquidafySignatureError) {
return res.status(400).send('Invalid signature');
}
throw err;
}
},
);constructEvent verifies the Liquidafy-Signature header (t=<unix>,v1=<hex(hmac-sha256(secret, "<t>.<body>"))>)
with a constant-time comparison and rejects timestamps outside the tolerance window (replay protection). It works in any handler that gives you the raw body (Next.js route handlers, Fastify with rawBody, Cloudflare Workers with nodejs_compat, ...).
3. Auto-pagination
// Lazy async iterator — fetches pages on demand, cancelable via break:
for await (const charge of liquidafy.charges.list({ status: 'confirmed' }).autoPaginate()) {
console.log(charge.id, charge.amount);
}
// Or eagerly (careful with huge lists):
const all = await liquidafy.withdrawals.list({ type: 'crypto' }).autoPaginate().toArray();
// Manual cursor:
const page1 = await liquidafy.charges.list({ limit: 50 });
const page2 = await liquidafy.charges.list({ limit: 50, cursor: page1.next_cursor! });Resources
| Resource | Methods |
|---|---|
| liquidafy.charges | create, retrieve, list, cancel, refund |
| liquidafy.refunds | create(chargeId, ...), retrieve, list |
| liquidafy.withdrawals | create (crypto/fiat), retrieve, list, cancel |
| liquidafy.webhookEndpoints | create, retrieve, list, update, del, rotateSecret, disable, enable, listDeliveries, test |
| liquidafy.fx | quote, retrieveQuote, rates |
| liquidafy.merchants | me, update, balance, getSettings, updateSettings |
| liquidafy.test | simulateDeposit (sandbox only — requires lr_test_*) |
| liquidafy.webhooks | constructEvent |
Client options
const liquidafy = new Liquidafy('lr_test_...', {
apiVersion: '2026-05-01', // pin the API shape
baseURL: 'https://api.sandbox.liquidafy.com',
maxRetries: 3, // network/5xx/429 only — never other 4xx
timeout: 30_000, // ms
appInfo: { name: 'MeuApp', version: '1.2.3', url: 'https://meuapp.com' },
telemetry: true, // runtime info in User-Agent
});Retries & idempotency
Failed requests are retried up to 3 times with exponential backoff (1s/2s/4s ± 25% jitter) on network errors, 5xx, and 429 (honoring Retry-After). Other 4xx are never retried.
Mutations (POST/PATCH) are only retried when you pass { idempotencyKey } — the SDK refuses to blind-retry a request it cannot prove idempotent, and logs a console.warn to remind you.
Errors
import {
LiquidafyAPIError,
LiquidafyAuthenticationError,
LiquidafyRateLimitError,
LiquidafyValidationError,
} from '@liquidafy/liquidafy-js';
try {
await liquidafy.charges.create({ amount: '10.00', currency: 'USDT' }, { idempotencyKey: key });
} catch (err) {
if (err instanceof LiquidafyRateLimitError) {
console.log('retry in', err.retryAfterMs);
} else if (err instanceof LiquidafyValidationError) {
console.log(err.code, err.details);
} else if (err instanceof LiquidafyAPIError) {
console.log(err.statusCode, err.code, err.requestId);
} else {
throw err;
}
}Every API error exposes statusCode, code (stable, switchable), requestId, details and headers. API keys are always masked (lr_live_***1234) and never logged.
Sandbox helpers
const liquidafy = new Liquidafy('lr_test_...');
await liquidafy.test.simulateDeposit({
to_address: charge.deposit_address,
amount: '100.000000',
confirmations: 19, // ≥19 lands the deposit at `confirmed`
});Calling test.simulateDeposit with a live key throws locally (and is rejected server-side).
Development
pnpm install
pnpm lint # tsc --noEmit (strict)
pnpm test # vitest
pnpm build # tsup → dist (ESM + CJS + d.ts)src/types/api.ts is generated from the Liquidafy OpenAPI specification (pnpm generate:types, maintainers only — expects an openapi.yaml in the repo root, which is not committed). Don't hand-edit generated files.
Security
Found a vulnerability? Never open a public issue. Report it privately to [email protected] or via GitHub private vulnerability reporting — see SECURITY.md for scope, supported versions, and response times.
License
MIT © Liquidafy Labs Ltda
Contact
- Bugs / feature requests: GitHub issues
- Support: [email protected]
