Logo

Eventos de webhook

Receba, verifique a assinatura HMAC e processe os eventos enviados pela Paggpix.

Após criar um webhook, os eventos do objeto subscrito são enviados para sua URL de callback via POST. Todos os payloads são assinados com HMAC usando o verify_token como chave.

Requisição de evento

POST https://yourdomain.com/webhook
Content-Type: application/json
X-Paggpix-Signature: sha256=<payload-signature>

{
  "event": "PAYMENT",
  "timestamp": "2023-07-01T18:46:13Z",
  "data": {
    "pix_id": "ba5ce1d5cd58ff77d99b36cee2224e37",
    "external_id": "3215158",
    "status": "DONE",
    "description": "Pagamento referente ao pedido #99999",
    "value": "1000.00",
    "paggpix_fee_percentage": "0.50",
    "paggpix_fee_value": "5",
    "partner_fee_percentage": "0.40",
    "partner_fee_value": "4",
    "created": "2023-07-01T18:45:06Z",
    "paid": "2023-07-01T18:46:13Z"
  }
}

Verificando a assinatura

A assinatura no header X-Paggpix-Signature é gerada com HMAC-SHA256 usando o verify_token. Recalcule a assinatura e compare-as.

Calcule o HMAC sobre o corpo bruto da requisição, exatamente como ele chegou. Não use o JSON já parseado e re-serializado: a reserialização pode alterar espaçamento, ordem de chaves ou escape de caracteres e invalidar a assinatura.

import crypto from 'crypto';
import express from 'express';

const app = express();
const verifyToken = process.env.PAGGPIX_VERIFY_TOKEN; // Mesmo token usado na criação

// express.raw() entrega req.body como Buffer, preservando os bytes originais.
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const received = req.headers['x-paggpix-signature'];

  // Sem assinatura não há o que comparar. Recuse antes de tocar no payload.
  if (typeof received !== 'string') {
    return res.status(401).send('Missing signature');
  }

  const hmac = crypto.createHmac('sha256', verifyToken);
  const expected = `sha256=${hmac.update(req.body).digest('hex')}`;

  const receivedBuffer = Buffer.from(received);
  const expectedBuffer = Buffer.from(expected);

  // timingSafeEqual lança se os buffers tiverem tamanhos diferentes,
  // então o tamanho precisa ser checado antes da comparação.
  if (
    receivedBuffer.length !== expectedBuffer.length ||
    !crypto.timingSafeEqual(receivedBuffer, expectedBuffer)
  ) {
    return res.status(403).send('Invalid signature');
  }

  const event = JSON.parse(req.body.toString('utf8'));
  console.log('Webhook event received:', event);
  res.status(200).send('Event received');
});

app.listen(3000);

Se sua aplicação usa express.json() globalmente, registre a rota do webhook com express.raw() antes desse middleware, ou o corpo bruto não estará mais disponível.

Evento PAYMENT

Disparado quando um pagamento é criado, efetuado, expirado ou cancelado.

Dados do evento (data)

Prop

Type

O campo paid é enviado apenas quando o pagamento foi concluído.

On this page