Webhooks

Webhooks — visão geral

Webhooks avisam o seu servidor no instante em que algo acontece — sem você ficar consultando a API. É a forma recomendada de saber que uma cobrança foi paga.

Configuração

Cadastre a URL do seu endpoint no painel, em Desenvolvedor, e escolha os eventos que quer receber. Cada endpoint ganha um secret usado para assinar as entregas.

Eventos disponíveis

  • charge.paid — a cobrança foi paga. data contém a cobrança com status: "paid" e paid_at preenchido.
  • charge.expired — a cobrança venceu sem pagamento. data.status é "expired".

Formato do evento

O corpo é um JSON com id do evento (prefixo evt_), o event, o created_at e data — que é exatamente o objeto charge serializado.

POST no seu endpoint
{
  "id": "evt_9f8e7d6c5b4a3210fedcba98",
  "event": "charge.paid",
  "created_at": "2026-07-04T12:05:11.000Z",
  "data": {
    "id": "clx7a1b2c3d4e5f6g7h8i9j0k",
    "object": "charge",
    "status": "paid",
    "amount": 4990,
    "fee": 74,
    "net": 4916,
    "currency": "BRL",
    "description": "Plano Pro — mensal",
    "payment_method": "pix",
    "customer": { "name": "Ana Souza", "email": "ana@exemplo.com", "document": null },
    "pix": {
      "txid": "VRD7A1B2C3D4E5F6G7H8I9J0",
      "brcode": "00020126360014BR.GOV.BCB.PIX...6304AB1F",
      "qrcode_url": "https://api.verdin.com.br/api/v1/charges/clx7a1b2c3d4e5f6g7h8i9j0k/qrcode"
    },
    "payment_url": "https://api.verdin.com.br/pay/clx7a1b2c3d4e5f6g7h8i9j0k",
    "metadata": { "pedido_id": "A-1042" },
    "paid_at": "2026-07-04T12:05:11.000Z",
    "expires_at": "2026-07-04T13:00:00.000Z",
    "created_at": "2026-07-04T12:00:00.000Z"
  }
}

Recebendo o evento

Responda com um status 2xx assim que receber. Deixe o processamento pesado para depois (fila) — a Verdin espera no máximo 8 segundos por resposta.

Handler Express
import express from "express";

const app = express();

// receba o corpo BRUTO — necessário para verificar a assinatura
app.post("/webhooks/verdin", express.raw({ type: "application/json" }), (req, res) => {
  // 1. verifique a assinatura (veja "Verificando a assinatura")
  // 2. responda 2xx o mais rápido possível
  const evt = JSON.parse(req.body.toString("utf8"));

  switch (evt.event) {
    case "charge.paid":
      // libere o produto para evt.data.id — de forma idempotente
      break;
    case "charge.expired":
      // marque o pedido como expirado, se preciso
      break;
  }

  res.sendStatus(200);
});

Entrega e tentativas

Se o seu endpoint não responder 2xx (erro, timeout ou rede indisponível), a Verdin tenta de novo. São 3 tentativas com os intervalos:

  • 1ª tentativa: imediata (0s)
  • 2ª tentativa: após 5s
  • 3ª tentativa: após mais 25s

Cada tentativa tem timeout de 8 segundos.

Trate eventos de forma idempotente

A mesma cobrança pode gerar entregas repetidas (por causa dos retries). Use o data.id (id da cobrança) — ou o id do evento — como chave de idempotência: se já processou aquele pagamento, responda 2xx e não processe de novo.