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

  1. Vai a /dashboard/settings/webhook.
  2. Define a URL (deve começar por https://).
  3. Activa o toggle.
  4. Clica Testar webhook para confirmar que o endpoint responde.

Eventos suportados

EventDescrição
payment.successPagamento confirmado pelo banco
payment.failedPagamento falhou (cartão recusado, MB WAY rejeitado)
payment.cancelledCliente cancelou ou transacção expirou
webhook.testEnviado 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

HeaderValor
Content-Typeapplication/json
X-VorkPay-Signaturesha256=<hmac>
X-VorkPay-Eventpayment.success · payment.failed · …
User-AgentVorkPay-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.id para 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.