Webhooks

Receba cada mudança de status de um pagamento no seu servidor.

Quando um pagamento muda de status, a XPayZ faz um POST JSON no endpoint que você cadastrou. É o jeito confiável de saber que um pedido foi pago: não dependa do retorno do pagador para a back_url, ele pode fechar a aba antes.

Cadastre o endpoint

curl -X POST "$XPAYZ_URL/webhook" \
  -H "Content-Type: application/json" \
  -H "X-Secret-ID: $XPAYZ_SECRET_ID" \
  -H "X-Secret-Key: $XPAYZ_SECRET_KEY" \
  -d '{
    "endpoint": "https://sualoja.com/webhooks/xpayz",
    "topics": ["event.completed", "event.rejected", "event.expired", "event.refunded"]
  }'

Você só recebe os tópicos assinados. Para mudar os tópicos, exclua o webhook e cadastre de novo.

Tópicos

TópicoQuando
event.createdUm pagamento foi iniciado (ex. QR Code Pix gerado).
event.completedO pagamento foi concluído. Libere o pedido aqui.
event.rejectedO pagamento foi recusado.
event.cancelledO pagamento foi cancelado.
event.expiredO pagamento expirou sem ser concluído.
event.refundedO estorno foi concluído.
event.refund_rejectedA solicitação de estorno foi recusada.

Payload

Todos os tópicos têm o mesmo formato:

{
  "topic": "event.completed",
  "data": {
    "external_id": "0d4ba8f5-bbc6-4cbb-ae65-13de2f903271",
    "amount": "100",
    "currency": "BRL",
    "status": "done",
    "method": "credit_card",
    "payer": {
      "name": "John Doe",
      "email": "john@example.com",
      "document": "***.123.456-**"
    },
    "link": {
      "external_id": "f9ce38d3-d6e2-4d3e-b9f3-0ebb396ef780",
      "external_ref": "order-123"
    }
  }
}

Prop

Tipo

Valide a assinatura

Cada entrega tem o header X-Signature: o HMAC-SHA256 do corpo bruto, em hexadecimal, com o secret do webhook como chave. Pegue o secret em GET /webhook.

Validar garante que o evento veio da XPayZ e não foi alterado no caminho. Sem isso, qualquer pessoa que descubra sua URL pode forjar um event.completed.

  1. Leia o corpo exatamente como chegou, antes de fazer parse do JSON.
  2. Calcule o HMAC-SHA256 dele com o secret.
  3. Compare com X-Signature usando comparação de tempo constante.
  4. Se não bater, responda 401 e descarte.

Não faça JSON.parse e JSON.stringify antes de validar: qualquer diferença de espaço ou ordem de campos muda o hash.

server.ts
import crypto from 'node:crypto';
import express from 'express';

const app = express();
const secret = process.env.XPAYZ_WEBHOOK_SECRET!;

app.post('/webhooks/xpayz', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = crypto.createHmac('sha256', secret).update(req.body).digest('hex');
  const received = req.header('X-Signature') ?? '';

  const valid =
    received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!valid) return res.sendStatus(401);

  const event = JSON.parse(req.body.toString('utf8'));
  // enfileire o processamento e responda rápido
  res.sendStatus(200);
});

Responda rápido e com 2xx

Qualquer resposta 2xx confirma a entrega. Qualquer outra coisa (4xx, 5xx, erro de conexão ou mais de 30 segundos sem resposta) conta como falha. Processe o evento em segundo plano e responda logo.

Reenvios

Uma entrega que falha é tentada de novo até 5 vezes, com intervalo crescente:

TentativaQuando
1ªNa hora do evento
2ª1 min depois da falha
3ª2 min depois
4ª3 min depois
5ª4 min depois
6ª5 min depois

Se a última falhar, o evento fica failed. Acompanhe as entregas em GET /webhook/{id}/events e reenvie um evento com POST .../retrigger depois de corrigir seu endpoint.

Trate duplicados

Por causa dos reenvios, o mesmo evento pode chegar mais de uma vez. Use data.external_id junto com topic como chave e ignore o que você já processou.

Por padrão, todos os webhooks da conta recebem os eventos de todos os links. Para mandar os eventos de um link para um só deles, passe webhook_url na criação do link com exatamente o mesmo endpoint de um webhook cadastrado.

webhook_url filtra, não cadastra. Se nenhum webhook da conta tiver esse endpoint, os eventos do link não são entregues a ninguém.

Nesta página