Webhooks
Os webhooks são a forma mais fiável de saberes quando uma transacção muda de estado. Em vez de fazeres polling em GET /api/v1/payments/status, configura um endpoint no teu servidor e o VorkPay envia-te um POST sempre que algo acontece.
Configurar o endpoint
- Vai a /dashboard/settings/webhook.
- Define a URL (deve começar por
https://). - Activa o toggle.
- Clica Testar webhook para confirmar que o endpoint responde.
Eventos suportados
| Event | Descrição |
|---|---|
payment.success | Pagamento confirmado pelo banco |
payment.failed | Pagamento falhou (cartão recusado, MB WAY rejeitado) |
payment.cancelled | Cliente cancelou ou transacção expirou |
webhook.test | Enviado quando clicas "Testar webhook" no dashboard |
Payload
{
"event": "payment.success",
"timestamp": "2026-05-10T14:32:00Z",
"data": {
"id": "txn_abc123",
"externalOrderId": "ORDER-456",
"amount": 49.90,
"currency": "EUR",
"paymentMethod": "MBWAY",
"status": "paid",
"paidAt": "2026-05-10T14:32:00Z"
}
}Headers
| Header | Valor |
|---|---|
Content-Type | application/json |
X-VorkPay-Signature | sha256=<hmac> |
X-VorkPay-Event | payment.success · payment.failed · … |
User-Agent | VorkPay-Webhook/1.0 |
Verificar autenticidade (HMAC-SHA256)
Todos os webhooks são assinados com o teu webhookSecret (visível em Webhook). Verifica sempre a assinatura antes de processar — caso contrário um atacante pode simular eventos.
import crypto from "crypto"
export function POST(req) {
const raw = await req.text()
const signature = req.headers.get("x-vorkpay-signature") || ""
const expected = "sha256=" + crypto
.createHmac("sha256", process.env.VORKPAY_WEBHOOK_SECRET)
.update(raw)
.digest("hex")
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return new Response("Invalid signature", { status: 401 })
}
const event = JSON.parse(raw)
if (event.event === "payment.success") {
// Marca o pedido como pago na tua BD
await markOrderPaid(event.data.externalOrderId)
}
return Response.json({ ok: true })
}Sistema de retry
Se o teu endpoint não responder com 2xx em 8 segundos, retentamos:
- 1ª tentativa imediata
- 2ª tentativa após 200ms
- 3ª tentativa após 1s
- 4ª tentativa após 5s
Após 4 tentativas falhadas o webhook é marcado como não-entregue. Vê o estado em Transacções → drawer da transacção.
Boas práticas
- Idempotência: usa o
data.idpara deduplicar — o mesmo evento pode chegar mais que uma vez. - Responde rápido: processamento pesado deve ser assíncrono (queue). Responde 200 em <1s.
- HTTPS: só aceitamos URLs com
https://. - Verifica a assinatura antes de qualquer parsing.
