Official SDKs
Orbita Pay publishes official clients for Node.js, Python and .NET. They wrap the same
/v1 REST API described in the API Reference — anything you can do
with curl, you can do without an SDK. What the SDKs add is the part that is easy to
get subtly wrong: idempotency, retry policy, and webhook signature verification.
| Language | Package | Install | Runtime |
|---|---|---|---|
| Node.js / TypeScript | @liqfy/node | npm i @liqfy/node | Node 18+ |
| Python | liqfy | pip install liqfy | Python 3.8+ |
| .NET / C# | Orbita Pay | dotnet add package Orbita | netstandard2.0, net8.0 |
PHP integrators can use the WooCommerce plugin or call the API directly; a standalone Composer package is not published yet.
#What every SDK guarantees
The three clients are behaviourally identical, not merely similar. The webhook signature verification in particular runs against a shared set of test vectors in CI — if Node accepts a signature, Python and .NET accept the same one, byte for byte.
#Authentication
apikey: lq_live_…The environment (test or live) comes from the key itself. You never configure an environment, and no account or merchant identifier is ever passed in or returned.
#Idempotency is required on financial writes
Creating a charge or a payout requires an idempotency key. The SDKs refuse the call without one rather than sending it and hoping.
Derive the key from your order (pedido-1234), never from a random value. Random
defeats the entire mechanism: if your request arrived but the response was lost,
retrying with a fresh key creates a second charge; retrying with the same key
returns the original.
#Retry policy
| Situation | Retried? |
|---|---|
GET / HEAD on 5xx or network error | yes |
POST with an idempotency key | yes — same key on every attempt |
POST without an idempotency key | never |
Any 4xx | never — the request is wrong, retrying will not fix it |
Backoff is exponential with full jitter, capped at 8s.
The third row is the one that matters. A network failure does not tell you whether the server processed the request — only that no response came back. Retrying a keyless financial write on that ambiguity is how a customer gets charged twice.
#Quickstart
#Node.js
import { OrbitaClient } from '@liqfy/node';
const liqfy = new OrbitaClient({ apiKey: process.env.LIQFY_API_KEY });
const charge = await liqfy.charges.create(
{
amount: 15000, // R$ 150.00 in centavos — always an integer
currency: 'BRL',
payment_method: 'pix',
customer: { name: 'Maria Silva', document: '12345678901' },
},
{ idempotencyKey: `pedido-${orderId}` },
);
charge.pix.br_code; // Pix Copia e Cola#Python
import os
from liqfy import OrbitaClient
liqfy = OrbitaClient(api_key=os.environ["LIQFY_API_KEY"])
cobranca = liqfy.charges.create(
{
"amount": 15000,
"currency": "BRL",
"payment_method": "pix",
"customer": {"name": "Maria Silva", "document": "12345678901"},
},
idempotency_key=f"pedido-{pedido_id}",
)
cobranca["pix"]["br_code"]The Python SDK has no runtime dependencies — standard library only. A payments SDK runs inside your process; every transitive package it pulls in becomes supply-chain surface you inherit from us.
#C#
using Orbita;
// Register as a SINGLETON — it is thread-safe and reuses its HttpClient.
// One client per request exhausts TCP ports, the classic .NET trap.
var liqfy = new OrbitaClient(Environment.GetEnvironmentVariable("LIQFY_API_KEY")!);
var cobranca = await liqfy.Charges.CreateAsync(new
{
amount = 15000,
currency = "BRL",
payment_method = "pix",
customer = new { name = "Maria Silva", document = "12345678901" },
}, idempotencyKey: $"pedido-{orderId}");
var brCode = cobranca!.RootElement.GetProperty("pix").GetProperty("br_code").GetString();#Verifying webhooks
Verification needs no API key — instantiate the webhook helper on its own.
// Node — Express with a raw body parser
app.post('/webhooks/liqfy', express.raw({ type: 'application/json' }), (req, res) => {
if (!liqfy.webhooks.verify(req.body, req.headers['x-liqfy-signature'], secret)) {
return res.status(401).end();
}
const event = JSON.parse(req.body.toString('utf8'));
res.status(200).end(); // any 2xx confirms delivery
});# Python — Flask
if not webhooks.verify(request.get_data(), request.headers.get("X-Liqfy-Signature"), secret):
return "", 401// C# — minimal API
if (!webhooks.Verify(corpo, req.Headers["X-Liqfy-Signature"], secret))
return Results.Unauthorized();Pass the raw bytes. Do not deserialize and re-serialize the body before verifying: any difference in whitespace or key order changes the HMAC and the signature fails. This is the single most common webhook integration bug, in every language.
Signatures older than 5 minutes are rejected by default, which stops a captured POST from being replayed forever. See Webhooks for the signature format and the retry schedule.
#Errors
Every SDK raises typed errors carrying status, code, type and request_id.
| Class of failure | Node | Python | C# |
|---|---|---|---|
| 401 / 403 | OrbitaAuthError | OrbitaAuthError | OrbitaAuthException |
| 400 / 422 | OrbitaValidationError | OrbitaValidationError | OrbitaValidationException |
| 409 | OrbitaConflictError | OrbitaConflictError | OrbitaConflictException |
| No response at all | OrbitaNetworkError | OrbitaNetworkError | OrbitaNetworkException |
Branch on code, never on the message — messages are written for humans and change
without notice. code is contract; see Errors for the catalogue.
The network error is deliberately not a subclass of the API error in any of the three SDKs. When no response came back, you do not know whether the server processed the request. Catching both in one branch hides exactly the distinction that decides whether retrying is safe.
Quote the request_id when you contact support — it locates the exact request in our
logs.