SDKs oficiales
Liqfy publica clientes oficiales para Node.js, Python, PHP y .NET. Envuelven la misma
API REST /v1 descrita en la Referencia de la API: todo lo que puedes hacer
con curl, puedes hacerlo sin un SDK. Lo que aportan los SDKs es la parte en la que es fácil
equivocarse de forma sutil: la idempotencia, la política de reintentos y la verificación de firmas de webhooks.
Los cuatro comparten una misma versión: 0.8.0.
| Lenguaje | Paquete | Instalación | Entorno de ejecución |
|---|---|---|---|
| Node.js / TypeScript | @liqfy/node | npm i @liqfy/node | Node 18+ |
| Python | liqfy | pip install liqfy | Python 3.8+ |
| PHP | liqfy/sdk-php | composer require liqfy/sdk-php | PHP 8.1+ |
| .NET / C# | Liqfy | dotnet add package Liqfy | netstandard2.0, net8.0 |
#Lo que garantiza cada SDK
Los cuatro clientes son idénticos en comportamiento, no simplemente parecidos. En particular, la verificación de firmas de webhooks se ejecuta en CI contra un conjunto compartido de vectores de prueba: si Node acepta una firma, Python, PHP y .NET aceptan la misma, byte a byte.
#Familias de recursos
Cada cliente expone las mismas seis familias de recursos: mismas rutas y mismos formatos en el cable que la Referencia de la API:
| Familia | Node | Python | PHP | .NET |
|---|---|---|---|---|
Cobros (/v1/charges) | client.charges | client.charges | $client->charges | client.Charges |
Operaciones de pago (/v1/payments) | client.payments | client.payments | $client->payments | client.Payments |
Retiros (/v1/withdrawals) | client.payouts | client.payouts | $client->payouts | client.Payouts |
Saldo (/v1/wallets) | client.balance | client.balance | $client->balance | client.Balance |
Webhooks (/v1/webhooks) | client.webhooks (incl. client.webhooks.endpoints) | client.webhooks (incl. client.webhooks.endpoints) | $client->webhooks (incl. $client->webhooks->endpoints) | client.Webhooks (incl. client.Webhooks.Endpoints) |
| Casos MED | client.medCases | client.med_cases | $client->medCases | client.MedCases |
Los nombres de los métodos siguen la convención de cada lenguaje (client.payments.create(input, { idempotencyKey }) en Node, client.payments.create(input, idempotency_key) en Python, $client->payments->create($input, $idempotencyKey) en PHP, client.Payments.CreateAsync(input, idempotencyKey) en .NET). payments.refund está ausente deliberadamente: la API responde 403 a toda solicitud con clave de API — error.code es "forbidden" y el mensaje comienza con el marcador REFUND_ADMIN_ONLY (un prefijo del mensaje, no un código), porque los reembolsos se realizan desde el panel de administración.
#Autenticación
Authorization: Bearer lq_live_…Los SDKs envían el encabezado canónico Authorization: Bearer; la API sigue aceptando el encabezado heredado
apikey: por compatibilidad. El entorno (test o live) se deriva de la propia clave.
Nunca configuras un entorno, y nunca se envía ni se devuelve ningún identificador de cuenta
o de comercio.
#La idempotencia es obligatoria en las escrituras financieras
Crear un cobro o un retiro requiere una clave de idempotencia. Los SDKs rechazan la llamada sin ella en lugar de enviarla y esperar que salga bien.
Deriva la clave de tu pedido (pedido-1234), nunca de un valor aleatorio. Un valor aleatorio
anula todo el mecanismo: si tu solicitud llegó pero la respuesta se perdió, reintentar con una
clave nueva crea un segundo cobro; reintentar con la misma clave devuelve el original.
#Política de reintentos
| Situación | ¿Se reintenta? |
|---|---|
GET / HEAD ante 5xx o error de red | sí |
POST con clave de idempotencia | sí — la misma clave en cada intento |
POST sin clave de idempotencia | nunca |
Cualquier 4xx | nunca — la solicitud es incorrecta, reintentar no la arreglará |
El backoff es exponencial con jitter completo, con un máximo de 8s.
La tercera fila es la que importa. Un fallo de red no te dice si el servidor procesó la solicitud, solo que no llegó ninguna respuesta. Reintentar una escritura financiera sin clave ante esa ambigüedad es exactamente como a un cliente se le cobra dos veces.
#Inicio rápido
#Node.js
import { LiqfyClient } from '@liqfy/node';
const liqfy = new LiqfyClient({ 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 LiqfyClient
liqfy = LiqfyClient(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"]El SDK de Python no tiene dependencias en tiempo de ejecución: solo la biblioteca estándar. Un SDK de pagos se ejecuta dentro de tu proceso; cada paquete transitivo que incorpora se convierte en superficie de cadena de suministro que heredas de nosotros.
#PHP
<?php
require __DIR__ . '/vendor/autoload.php';
use Liqfy\LiqfyClient;
$cliente = new LiqfyClient(['apiKey' => getenv('LIQFY_API_KEY')]);
$cobranca = $cliente->charges->create([
'amount' => 15000,
'currency' => 'BRL',
'payment_method' => 'pix',
'customer' => ['name' => 'Maria Silva', 'document' => '12345678901'],
], 'pedido-' . $pedidoId);
$cobranca['pix']['br_code'];#C#
using Liqfy;
// 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 LiqfyClient(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();#Verificación de webhooks
La verificación no necesita clave de API: instancia el helper de webhooks por sí solo.
// 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();Pasa los bytes sin procesar. No deserialices y vuelvas a serializar el cuerpo antes de verificarlo: cualquier diferencia en los espacios en blanco o en el orden de las claves cambia el HMAC y la firma falla. Este es el error de integración de webhooks más común, en todos los lenguajes.
Las firmas con más de 5 minutos de antigüedad se rechazan por defecto, lo que impide que un POST capturado se reproduzca indefinidamente. Consulta Webhooks para conocer el formato de la firma y el calendario de reintentos.
#Errores
Cada SDK lanza errores tipados que incluyen status, code, type y request_id.
| Tipo de fallo | Node | Python | PHP | C# |
|---|---|---|---|---|
| 401 / 403 | LiqfyAuthError | LiqfyAuthError | LiqfyAuthError | LiqfyAuthException |
| 400 / 422 | LiqfyValidationError | LiqfyValidationError | LiqfyValidationError | LiqfyValidationException |
| 409 | LiqfyConflictError | LiqfyConflictError | LiqfyConflictError | LiqfyConflictException |
| Ninguna respuesta | LiqfyNetworkError | LiqfyNetworkError | LiqfyNetworkError | LiqfyNetworkException |
Ramifica según code, nunca según el mensaje: los mensajes están escritos para personas y cambian
sin previo aviso. code es contrato; consulta Errores para ver el catálogo. El
único marcador de mensaje documentado es REFUND_ADMIN_ONLY (los reembolsos solo se realizan desde el panel de administración y
responden un forbidden simple); compáralo como prefijo, no como código.
El error de red deliberadamente no es una subclase del error de la API en ninguno de los cuatro SDKs. Cuando no llega ninguna respuesta, no sabes si el servidor procesó la solicitud. Capturar ambos en una misma rama oculta precisamente la distinción que decide si reintentar es seguro.
Indica el request_id cuando contactes con soporte: permite localizar la solicitud exacta en nuestros
registros.