Webhooks
Seja avisado no seu servidor quando um pagamento é confirmado.
Um webhook é uma chamada que a WysePay faz para uma URL sua quando algo acontece, como um pagamento confirmado. É o jeito certo de saber que foi pago: chega em segundos e você não precisa consultar a API em loop.
Cadastrar
- No painel, vá em Desenvolvedores → Webhooks.
- Informe a URL (use
https) e marque os eventos. - Guarde o segredo que aparece: ele valida a assinatura.
- Use Testar para conferir se a sua URL responde.
Endpoints de teste (cadastrados com o modo teste ligado) só recebem eventos de teste, e os de produção só os reais.
Eventos
| Evento | Quando | Corpo |
|---|---|---|
transaction.created | Um Pix foi gerado (API, painel ou link). | Transação |
transaction.paid | O pagamento foi confirmado. Libere o pedido aqui. | Transação |
transaction.expired | O Pix venceu sem pagamento. Cancele o pedido ou gere outro Pix. | Transação |
dispute.created | O pagador contestou o pagamento pelo banco (MED). | MED |
dispute.responded | A sua defesa do MED foi enviada. | MED |
dispute.won | A contestação foi negada: o valor volta a ficar disponível. | MED |
dispute.lost | A devolução foi confirmada: o valor voltou ao pagador. | MED |
withdrawal.created | Um saque foi pedido, pelo painel ou pela API. | Saque |
withdrawal.paid | O saque chegou ao destino. | Saque |
withdrawal.partially_paid | Parte do saque não foi concluída e voltou ao saldo. | Saque |
withdrawal.failed | O saque não foi feito e o valor voltou ao saldo. | Saque |
webhook.test | Você clicou em Testar no painel. | { message, sentAt } |
Os eventos de transação mandam a mesma transação que a API devolve, incluindo o seu metadata. Use-o para achar o pedido no seu sistema.
O que chega
Um POST com o corpo em JSON e estes headers:
| Header | O que é |
|---|---|
wysepay-event | O tipo do evento, como transaction.paid. |
wysepay-signature | A assinatura, para você conferir que veio da WysePay. |
wysepay-timestamp | Quando foi assinado, em milissegundos. |
wysepay-delivery-id | O id da entrega. É o mesmo nas novas tentativas automáticas. |
wysepay-attempt | O número da tentativa, de 1 a 6. |
Validar a assinatura
A assinatura é um HMAC-SHA256, em hexadecimal, feito com o segredo do endpoint sobre o texto {wysepay-timestamp}.{corpo}. Calcule o mesmo no seu servidor e compare. Se não bater, recuse com 401.
Use o corpo exatamente como chegou
Assine o texto bruto da requisição, antes de transformar em objeto. Reescrever o JSON muda espaços e a ordem dos campos, e a assinatura deixa de bater.
import crypto from "node:crypto";
import express from "express";
const app = express();
// O corpo cru (Buffer), não o objeto já interpretado.
app.post("/webhooks/wysepay", express.raw({ type: "application/json" }), (req, res) => {
const timestamp = req.header("wysepay-timestamp");
const signature = req.header("wysepay-signature") ?? "";
const expected = crypto
.createHmac("sha256", process.env.WYSEPAY_WEBHOOK_SECRET)
.update(`${timestamp}.${req.body}`)
.digest("hex");
const valid =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!valid) return res.sendStatus(401);
const event = req.header("wysepay-event");
const data = JSON.parse(req.body.toString("utf8"));
if (event === "transaction.paid") {
// Libere o pedido data.metadata.orderId (uma vez só: veja "Entregas repetidas").
}
res.sendStatus(200);
});Responda rápido
Responda com qualquer status 2xx em até 8 segundos. Se o trabalho for demorado (e-mail, nota fiscal), guarde o evento, responda 200 e processe depois.
Novas tentativas
Se a sua URL não responder 2xx a tempo, a WysePay tenta de novo depois de 1 minuto, 5 minutos, 30 minutos, 2 horas e 6 horas: até 6 tentativas no total. Em Webhooks, no painel, você vê cada entrega e a resposta do seu servidor, e pode usar Reenviar evento: ele cria uma entrega nova, com outro wysepay-delivery-id e uma assinatura nova.
Se todas as tentativas falharem, você pode ser avisado por e-mail (ligue em Configurações → Notificações).
Entregas repetidas
O mesmo evento pode chegar mais de uma vez (por exemplo, se a sua resposta se perdeu no caminho). Trate os eventos de forma idempotente: antes de liberar, confira se o pedido daquela transação (id) já foi liberado. O wysepay-delivery-id ajuda a reconhecer tentativas repetidas, mas um reenvio manual chega com um id novo.