https://app.slimpay.com.br/api/v1. Cobranças, saldo, saques e webhooks.Webhooks
Em vez de consultar a API em loop, receba uma requisição HTTP quando algo acontece.
Configurar
Cadastre a URL de destino no painel, em Webhooks. Ela precisa usar HTTPS e responder 2xx em até 15 segundos.
Ao cadastrar, você recebe um segredo de assinatura. Guarde-o como variável de ambiente: é com ele que você comprova que a requisição veio de nós.
Formato do evento
{
"id": "evt_01j9x5t2q4n7s9v6x8z1b3d5",
"type": "charge.paid",
"created_at": "2026-07-25T14:32:11Z",
"data": {
"id": "chg_01j9x2m4k8p3q7r5t1v6w8y0",
"status": "paid",
"amount": 14990,
"fee_amount": 373,
"net_amount": 14617,
"reference_id": "1042",
"paid_at": "2026-07-25T14:32:09Z"
}
}O campo data contém o recurso no estado em que ele estava quando o evento foi gerado.
SlimPay-Signature: t=1784995931,v1=5f2b8c1e9a...
SlimPay-Event-Id: evt_01j9x5t2q4n7s9v6x8z1b3d5
SlimPay-Event-Type: charge.paidEventos disponíveis
| Evento | Quando é disparado |
|---|---|
| charge.paid | O PIX foi confirmado e o saldo foi creditado. |
| charge.expired | A cobrança venceu sem pagamento. |
| charge.canceled | Você cancelou a cobrança. |
| payout.completed | O saque caiu na conta de destino. |
| payout.failed | O saque falhou e o valor voltou ao saldo. |
Assine apenas os eventos que você usa. Novos tipos podem ser adicionados no futuro, então trate tipos desconhecidos com um 200 silencioso em vez de erro.
Verificar a assinatura
Este é o passo que não pode ser pulado. Sua URL é pública: sem verificação, qualquer pessoa pode enviar um charge.paid falso e liberar um pedido que ninguém pagou.
O cabeçalho SlimPay-Signature traz o timestamp t e o HMAC-SHA256 v1, calculado sobre a string {t}.{corpo bruto} usando o seu segredo.
import crypto from "node:crypto";
const TOLERANCE_SECONDS = 300;
export function verifySignature(
rawBody: string,
header: string,
secret: string,
): boolean {
const parts = new Map(
header.split(",").map((part) => part.split("=") as [string, string]),
);
const timestamp = parts.get("t");
const received = parts.get("v1");
if (!timestamp || !received) return false;
// Reject old signatures so a captured request cannot be replayed later.
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(age) || age > TOLERANCE_SECONDS) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(received, "hex");
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}Três detalhes importam: use o corpo bruto, compare em tempo constante e rejeite timestamps antigos. Cada um cobre um ataque diferente.
Um handler completo
export async function POST(request: Request) {
// The signature covers the exact bytes we sent. Read the raw body first:
// parsing and re-serializing the JSON changes it and breaks verification.
const rawBody = await request.text();
const signature = request.headers.get("slimpay-signature");
if (!signature || !verifySignature(rawBody, signature, process.env.SLIMPAY_WEBHOOK_SECRET!)) {
return new Response("invalid signature", { status: 401 });
}
const event = JSON.parse(rawBody);
// Acknowledge before doing the slow work, so a timeout on our side does not
// turn into a duplicate delivery on yours.
await enqueue(event);
return new Response(null, { status: 200 });
}Trate eventos duplicados
O mesmo evento pode chegar mais de uma vez — é assim que garantimos entrega. Guarde os id já processados e ignore repetições, idealmente com uma constraint de unicidade no banco em vez de uma checagem em memória.
A ordem também não é garantida. Um charge.expired pode chegar depois de um charge.paid por atraso de rede: use o created_at do evento e o status atual do recurso para decidir, nunca a ordem de chegada.
Retentativas
Se a sua URL não responder 2xx, repetimos por até 24 horas com espera exponencial: 30s, 1min, 5min, 30min, 2h e 6h. Depois disso o evento é marcado como não entregue.
O painel lista todas as tentativas, com corpo enviado e resposta recebida, e permite reenviar qualquer evento manualmente. Falhas persistentes suspendem o endpoint e notificam você por e-mail.
Boas práticas
- Responda rápido e processe em fila. Trabalho pesado dentro do handler gera timeout e entrega duplicada.
- Trate o webhook como gatilho, não como fonte da verdade. Em operações críticas, confirme o valor consultando a cobrança.
- Não confie no IP de origem como autenticação. A assinatura é a única garantia.
- Registre os eventos recebidos. Quando algo divergir, esse log é o que vai explicar o porquê.