Configurar Webhooks
Configure endpoints de webhook para receber notificações em tempo real sobre eventos da plataforma Liqi, como pagamentos de parcelas, mudanças de status e liquidações.
Visão geral
Webhooks permitem que a Liqi notifique seu sistema automaticamente quando eventos importantes acontecem. Em vez de fazer polling nos endpoints, você recebe um POST na URL que você cadastrar.
Liqi Platform Seu Servidor
| |
| 1. Evento ocorre (ex: pagamento) |
| |
| 2. POST https://seu-servidor/webhook |
| ────────────────────────────────────> |
| Headers: |
| X-Webhook-Signature: hmac... |
| X-Webhook-Id: evt_abc123 |
| X-Webhook-Timestamp: 170853... |
| Body: { event, data } |
| |
| 3. Responde 200 OK |
| <──────────────────────────────────── |
| |Importante
200 em até 5 segundos. Caso contrário, a Liqi considerará a entrega como falha e fará retentativas.1Criar um endpoint HTTP
Crie um endpoint POST no seu servidor que receba o payload do webhook. O endpoint deve ser acessível publicamente via HTTPS.
import express from "express";
import crypto from "crypto";
const app = express();
// IMPORTANTE: usar raw body para validar a assinatura
app.use("/webhooks/liqi", express.raw({ type: "application/json" }));
app.post("/webhooks/liqi", (req, res) => {
const signature = req.headers["x-webhook-signature"] as string;
const webhookId = req.headers["x-webhook-id"] as string;
const timestamp = req.headers["x-webhook-timestamp"] as string;
// 1. Validar assinatura (ver Passo 3)
const isValid = verifySignature(req.body, signature, timestamp);
if (!isValid) {
return res.status(401).json({ error: "Invalid signature" });
}
// 2. Processar evento
const event = JSON.parse(req.body.toString());
console.log(`Evento recebido: ${event.type} (${webhookId})`);
// 3. Responder 200 rapidamente (processar async se necessário)
res.status(200).json({ received: true });
// 4. Processar em background
processEventAsync(event).catch(console.error);
});
app.listen(3001, () => console.log("Webhook server on :3001"));from flask import Flask, request, jsonify
import hmac
import hashlib
app = Flask(__name__)
@app.route("/webhooks/liqi", methods=["POST"])
def handle_webhook():
signature = request.headers.get("X-Webhook-Signature")
webhook_id = request.headers.get("X-Webhook-Id")
timestamp = request.headers.get("X-Webhook-Timestamp")
# 1. Validar assinatura
if not verify_signature(request.data, signature, timestamp):
return jsonify({"error": "Invalid signature"}), 401
# 2. Processar evento
event = request.get_json()
print(f"Evento recebido: {event['type']} ({webhook_id})")
# 3. Responder 200 rapidamente
return jsonify({"received": True}), 2002Registrar o webhook na Liqi
Registre a URL do seu endpoint e selecione os eventos que deseja receber. Você receberá um webhook secret que será usado para validar as assinaturas.
curl -X POST "https://api.liqi.com.br/developer/webhooks" \
-H "X-API-Key: lq_dev_xxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://seu-servidor.com/webhooks/liqi",
"events": [
"payment.completed",
"tranche.status_changed",
"settlement.processed"
],
"description": "Webhook de produção"
}'{
"id": "wh_abc123def456",
"url": "https://seu-servidor.com/webhooks/liqi",
"events": [
"payment.completed",
"tranche.status_changed",
"settlement.processed"
],
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"status": "active",
"createdAt": "2026-02-21T10:00:00Z"
}Atenção
secret so é retornado uma vez, no momento do cadastro. Armazene-o de forma segura no seu servidor (ex: variável de ambiente).3Validar a assinatura
Todo webhook enviado pela Liqi inclui uma assinatura HMAC-SHA256 no header X-Webhook-Signature. Valide essa assinatura para garantir que o payload é autêntico e não foi adulterado.
O payload assinado e composto por:
{webhook_id}.{timestamp}.{body}const WEBHOOK_SECRET = process.env.LIQI_WEBHOOK_SECRET!;
function verifySignature(
rawBody: Buffer,
signature: string,
timestamp: string
): boolean {
const webhookId = ""; // extrair do header X-Webhook-Id
const payload = `${webhookId}.${timestamp}.${rawBody.toString()}`;
const expectedSignature = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(payload)
.digest("hex");
// Comparação timing-safe para evitar timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}import hmac
import hashlib
import os
WEBHOOK_SECRET = os.environ["LIQI_WEBHOOK_SECRET"]
def verify_signature(raw_body: bytes, signature: str, timestamp: str) -> bool:
webhook_id = "" # extrair do header X-Webhook-Id
payload = f"{webhook_id}.{timestamp}.{raw_body.decode()}"
expected = hmac.new(
WEBHOOK_SECRET.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)Validação de timestamp
4Processar eventos
O payload do webhook segue um formato padrão:
{
"type": "payment.completed",
"id": "evt_abc123def456",
"createdAt": "2026-02-21T14:30:00Z",
"data": {
"trancheTicker": "ROB1SR06",
"installmentNumber": 5,
"dueDate": "2026-02-01",
"interestValue": "3095.00",
"principalValue": "0.00",
"totalValue": "3095.00",
"status": "PAID"
}
}async function processEventAsync(event: {
type: string;
id: string;
data: Record<string, unknown>;
}) {
// Idempotência: verificar se já processou este evento
const alreadyProcessed = await checkEventProcessed(event.id);
if (alreadyProcessed) return;
switch (event.type) {
case "payment.completed":
await handlePaymentCompleted(event.data);
break;
case "tranche.status_changed":
await handleTrancheStatusChanged(event.data);
break;
case "settlement.processed":
await handleSettlementProcessed(event.data);
break;
default:
console.log(`Evento desconhecido: ${event.type}`);
}
// Marcar evento como processado
await markEventProcessed(event.id);
}Eventos disponíveis
| Evento | Descrição | API |
|---|---|---|
| payment.completed | Parcela paga com sucesso | Tokenização |
| payment.overdue | Parcela em atraso | Tokenização |
| tranche.status_changed | Status da série alterado | Tokenização |
| tranche.unit_price_updated | PU recalculado | Tokenização |
| settlement.processed | Lote de liquidação processado | TIDC v1 |
| settlement.failed | Falha no processamento de liquidação | TIDC v1 |
| credit_batch.submitted | Lote de crédito submetido | TIDC v1 |
| credit_batch.approved | Lote de crédito aprovado | TIDC v1 |
| order.filled | Ordem executada (total ou parcial) | CaaS |
| order.cancelled | Ordem cancelada | CaaS |
| deposit.confirmed | Depósito confirmado | CaaS |
| withdrawal.completed | Saque processado | CaaS |
Boas práticas
- Responda 200 rapidamente — Processe o evento em background (fila, worker) e responda 200 imediatamente. A Liqi espera no maximo 5 segundos.
- Implemente idempotência — Armazene o
X-Webhook-Ide verifique antes de processar. Eventos podem ser entregues mais de uma vez. - Sempre valide a assinatura — Nunca processe um webhook sem validar a assinatura HMAC-SHA256.
- Use HTTPS — O endpoint de webhook deve estar acessível via HTTPS com certificado válido.
- Monitore falhas — Se a Liqi não receber 200, fara retentativas com backoff exponencial (1 min, 5 min, 30 min, 2h, 12h). Apos 5 falhas consecutivas, o webhook e desativado.
- Não dependa da ordem — Eventos podem chegar fora de ordem. Use timestamps e status para reconciliar.
Política de retentativa
| Tentativa | Intervalo | Ação |
|---|---|---|
| 1 | Imediata | Primeira entrega |
| 2 | 1 minuto | Primeira retentativa |
| 3 | 5 minutos | Segunda retentativa |
| 4 | 30 minutos | Terceira retentativa |
| 5 | 2 horas | Quarta retentativa |
| 6 | 12 horas | Última tentativa |
| - | - | Webhook desativado (notificação por email) |