Pular para o conteúdo
xFlow Payments

Blog

Webhook Pix: eventos, assinatura HMAC e boas práticas

Webhook Pix é o aviso que a API envia para a sua URL quando algo acontece com uma cobrança ou um saque, como o pagamento confirmado. Na xFlow, cada aviso é um POST JSON assinado com HMAC-SHA256, reenviado até 3 vezes até você responder 2xx.

Atualizado em · Equipe xFlow Payments · 6 min de leitura

Um webhook Pix avisa o seu sistema, sem você perguntar, que uma cobrança foi paga, um estorno concluiu ou um saque falhou. Na API Pix da xFlow, você cadastra uma URL https:// pública e, a cada evento, recebe um POST com corpo JSON, o header x-webhook-event com o nome do evento e o header x-webhook-signature com a assinatura HMAC-SHA256 do corpo. Se a sua URL não responder 2xx, a entrega é reenviada até 3 vezes.

Por que usar webhook em vez de polling

Sem webhook, o seu sistema precisa consultar GET /api/v1/charges/{id} repetidas vezes até o status virar paid. Isso gasta requisições, conta no limite por conta (429 rate_limited) e atrasa a baixa do pedido entre uma consulta e outra. Com o webhook, a xFlow avisa quando o pagamento é confirmado, e a confirmação é revalidada na adquirente antes de o valor cair no saldo. A documentação recomenda o webhook e deixa o polling como alternativa.

Como cadastrar o endpoint

Você pode cadastrar pelo painel, em Integrações > Webhooks, ou pela própria API. A API aceita chave de produção e de teste no mesmo endpoint:

bash
curl -X POST https://app.xflowpayments.com/api/v1/webhooks \
  -u "pk_live_...:sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"url":"https://seusistema.com.br/webhooks","events":["transaction.paid","transaction.refunded","transaction.refund_failed"]}'

A resposta 201 traz o campo secret, o segredo de assinatura do endpoint. Ele aparece uma única vez. Guarde num cofre de segredos ou numa variável de ambiente.

As outras operações:

  • GET /api/v1/webhooks lista os endpoints, com o segredo mascarado.
  • PATCH /api/v1/webhooks/{id} com { "active": false } pausa o endpoint, e true reativa.
  • DELETE /api/v1/webhooks/{id} remove.

Regras: a URL precisa ser https:// e pública (um endereço interno retorna 400 invalid_url), os eventos precisam ser da lista oficial e cada conta pode ter até 20 endpoints (400 endpoint_limit).

Eventos disponíveis

Evento Quando chega
transaction.pending A cobrança foi criada e aguarda pagamento
transaction.paid O Pix foi pago
transaction.refunded A devolução ao pagador concluiu; a cobrança passa a refunded
transaction.refund_failed O provedor recusou o estorno; a cobrança segue paid e o valor retido volta ao saldo
withdrawal.processing O saque está em processamento
withdrawal.completed O saque foi concluído
withdrawal.failed O saque falhou
dispute.opened Uma contestação de Pix foi aberta; a defesa é feita no painel, em Disputas
dispute.accepted A defesa foi aceita
dispute.rejected A defesa foi rejeitada

Os eventos dispute.* só chegam quando a sua conta tem o recurso de disputas ativo. Não existe evento para cobrança expirada: para saber disso, consulte o status pela API.

O que vem no corpo

Toda entrega tem o formato { event, data, createdAt }. Nos eventos transaction.*, o data segue o mesmo desenho da consulta de cobrança:

json
{
  "event": "transaction.paid",
  "data": {
    "id": "clx...",
    "livemode": true,
    "amount": 5000,
    "net": 4670,
    "fee": 330,
    "method": "pix",
    "status": "paid",
    "external_reference": "pedido-1234",
    "description": "Pedido #1234",
    "customer": { "name": "Cliente Exemplo", "email": "cliente@exemplo.com", "document": "00000000000" },
    "payer": { "name": "Fulano de Tal", "document": "10900327570", "bank": "NU PAGAMENTOS - IP" },
    "pix": { "endToEnd": "E18236120202609130426s1894658118" },
    "paidAt": "2026-09-13T04:26:13.927Z"
  },
  "createdAt": "2026-09-13T04:26:14.100Z"
}

Três detalhes que evitam bug:

  • No webhook o valor vem em amount (centavos). Na resposta da API de cobranças, o campo se chama amountCents.
  • net e fee só existem em transaction.paid.
  • Em transaction.pending, payer e pix.endToEnd vêm null. Eles são preenchidos pelo provedor na confirmação e servem para conciliar com o extrato. Veja o que é o End-to-end ID.

Como validar a assinatura

O header chega assim: x-webhook-signature: sha256=<hex>. O valor é o HMAC-SHA256 do corpo cru da requisição, calculado com o segredo do endpoint. Calcule do seu lado e compare. Duas regras importam:

  1. Use o corpo cru, exatamente como chegou. Se você fizer JSON.parse e serializar de novo, os bytes mudam e a assinatura não bate.
  2. Compare em tempo constante, para não vazar informação pelo tempo de resposta.

Em Node.js, com Express:

js
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

const app = express();

function isValid(raw, header, secret) {
  const expected = "sha256=" + createHmac("sha256", secret).update(raw).digest("hex");
  const a = Buffer.from(expected), b = Buffer.from(header ?? "");
  return a.length === b.length && timingSafeEqual(a, b);
}

// express.raw mantém o corpo como Buffer, sem parse
app.post("/webhooks/xflow", express.raw({ type: "application/json" }), async (req, res) => {
  if (!isValid(req.body, req.get("x-webhook-signature"), process.env.XFLOW_WEBHOOK_SECRET)) {
    return res.sendStatus(401);
  }
  const { event, data } = JSON.parse(req.body.toString("utf8"));

  if (event === "transaction.paid" && data.livemode) {
    await darBaixaNoPedido(data.external_reference, data); // a sua regra de negócio
  }
  res.sendStatus(200);
});

Em Python, com Flask:

python
import hashlib
import hmac
import json
import os

from flask import Flask, request

app = Flask(__name__)
SEGREDO = os.environ["XFLOW_WEBHOOK_SECRET"]

def assinatura_valida(raw: bytes, header: str) -> bool:
    esperado = "sha256=" + hmac.new(SEGREDO.encode(), raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(esperado, header or "")

@app.post("/webhooks/xflow")
def webhook():
    raw = request.get_data()  # corpo cru
    if not assinatura_valida(raw, request.headers.get("x-webhook-signature", "")):
        return "", 401
    evento = json.loads(raw)
    if evento["event"] == "transaction.paid" and evento["data"]["livemode"]:
        dar_baixa_no_pedido(evento["data"]["external_reference"], evento["data"])
    return "", 200

Mais exemplos completos estão em API Pix com Node.js e API Pix com Python.

Reenvios e boas práticas

A xFlow reenvia cada evento até 3 vezes e considera a entrega confirmada quando recebe 2xx. Disso saem as regras do seu handler:

  • Seja idempotente. O mesmo evento pode chegar mais de uma vez. Antes de dar baixa, confira se o pedido já foi processado. Use a combinação de data.id com event como chave para descartar repetições. O mesmo raciocínio vale para as chamadas que você faz à API: veja idempotência em API de pagamento.
  • Responda rápido. Valide a assinatura, grave o evento e responda 2xx. Deixe envio de e-mail, nota fiscal e outras tarefas para uma fila.
  • Filtre o modo de teste. Eventos do sandbox chegam com livemode: false (e payer: null). Nunca libere um pedido real com base neles.
  • Use a sua referência. O external_reference volta em todo evento da cobrança. É o jeito mais simples de achar o pedido no seu banco.
  • Tenha um plano B. Se o seu servidor ficar fora do ar por mais tempo que os reenvios cobrem, recupere o estado pela listagem, com GET /api/v1/charges?status=paid&from=...&to=....
  • Trate os estornos. transaction.refunded confirma a devolução. transaction.refund_failed avisa que a cobrança continua paga e o valor voltou ao saldo. O fluxo completo está em estorno Pix por API.

Como testar o webhook sem dinheiro real

Crie uma chave de teste, cadastre o endpoint, crie uma cobrança com a chave de teste e chame POST /api/v1/test/charges/{id}/pay. O evento transaction.paid chega na sua URL com livemode: false e a taxa calculada com as taxas reais da sua conta. O passo a passo está em API Pix sandbox, e a referência completa dos eventos fica na documentação de webhooks. Para ver todos os recursos para quem integra, acesse Desenvolvedores.

Perguntas frequentes

Quantas vezes a xFlow reenvia um webhook?

Até 3 vezes. A entrega é confirmada quando a sua URL responde com um status 2xx. Como o mesmo evento pode chegar mais de uma vez, processe cada um de forma idempotente.

Como validar a assinatura do webhook Pix?

Calcule o HMAC-SHA256 do corpo cru da requisição com o segredo do endpoint, em hexadecimal, prefixe com sha256= e compare com o header x-webhook-signature usando comparação de tempo constante.

O webhook funciona no ambiente de teste?

Sim. Ao simular um pagamento com a chave de teste, o evento transaction.paid é disparado com livemode: false no data. Filtre por esse campo antes de dar baixa em pedidos reais.

Posso usar uma URL local ou interna?

Não. A URL precisa começar com https:// e ser pública. Um endereço interno retorna 400 invalid_url. Para testar localmente, use um túnel HTTPS público apontando para a sua máquina.

Continue lendo

SoluçãoAPI PixA API Pix da xFlow Payments é uma API REST para criar cobranças Pix com QR Code e copia e cola, estornar, sacar e receber webhooks assinados. Uma única integração conecta a sua operação a 11 adquirentes, com roteamento por regras e retentativa automática.DesenvolvedoresA documentação da API de pagamentos Pix da xFlow é aberta e fica em app.xflowpayments.com/docs, com uma versão em texto para assistentes de IA no llms.txt. Esta página reúne o caminho para integrar: chaves, sandbox, webhooks, logs e guias técnicos.BlogAPI Pix sandbox: como testar sem mover dinheiroO sandbox da API Pix da xFlow funciona com chaves pk_test_ e sk_test_: mesmos endpoints, mesmas respostas com livemode: false, nenhum dinheiro real e um endpoint para simular o pagamento que dispara o webhook transaction.paid.BlogIdempotência em API de pagamento: retry sem duplicarIdempotência é a garantia de que repetir a mesma requisição não repete o efeito. Na API da xFlow, você envia o header Idempotency-Key ao criar cobranças e saques: num retry com a mesma chave, a API devolve a mesma cobrança ou o mesmo saque, sem gerar outro Pix nem debitar o saldo de novo.BlogEstorno Pix por APINa API da xFlow, o estorno Pix é um POST /api/v1/charges/{id}/refund que devolve ao pagador o valor total de uma cobrança paga, uma vez por cobrança e até 90 dias após o pagamento. A devolução é assíncrona: a resposta 202 diz que o PSP aceitou o pedido, e a conclusão chega pelo webhook transaction.refunded.