LiqiDevelopers
Intermediário 15 min

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

Seu endpoint deve responder com status 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.

Node.js / Express
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"));
Python / Flask
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}), 200

2Registrar 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
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"
  }'
Resposta
{
  "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

O campo 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:

Formato
{webhook_id}.{timestamp}.{body}
Node.js
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)
  );
}
Python
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

Além da assinatura, valide que o timestamp no header não tem mais de 5 minutos de diferença do horário atual. Isso protege contra replay attacks.

4Processar eventos

O payload do webhook segue um formato padrão:

Payload
{
  "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"
  }
}
Node.js — Processamento por tipo
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

EventoDescriçãoAPI
payment.completedParcela paga com sucessoTokenização
payment.overdueParcela em atrasoTokenização
tranche.status_changedStatus da série alteradoTokenização
tranche.unit_price_updatedPU recalculadoTokenização
settlement.processedLote de liquidação processadoTIDC v1
settlement.failedFalha no processamento de liquidaçãoTIDC v1
credit_batch.submittedLote de crédito submetidoTIDC v1
credit_batch.approvedLote de crédito aprovadoTIDC v1
order.filledOrdem executada (total ou parcial)CaaS
order.cancelledOrdem canceladaCaaS
deposit.confirmedDepósito confirmadoCaaS
withdrawal.completedSaque processadoCaaS

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-Id e 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

TentativaIntervaloAção
1ImediataPrimeira entrega
21 minutoPrimeira retentativa
35 minutosSegunda retentativa
430 minutosTerceira retentativa
52 horasQuarta retentativa
612 horasÚltima tentativa
--Webhook desativado (notificação por email)

Próximos passos