O que são webhooks assinados da Payora
Os webhooks assinados da Payora são pedidos HTTP que incluem uma assinatura digital, permitindo que o seu endpoint PHP confirme que o evento veio mesmo da Payora e não foi alterado durante o envio. Se procura saber como verificar webhooks da Payora em PHP, o fluxo básico é simples: a Payora envia um evento, o seu servidor lê o payload bruto e o código verifica a assinatura antes de confiar no conteúdo do pedido.
Esta verificação é importante porque qualquer cliente que conheça o seu URL pode enviar um webhook. Um agente malicioso pode copiar o endpoint dos registos e enviar um pedido POST com um evento de pagamento falso e um corpo JSON bem formatado. A assinatura impede esse tipo de fraude.
O evento só é útil depois de verificado. Antes disso, é apenas texto.
Pense numa senha numerada à entrada de um local. O corpo é a mensagem, a assinatura é o carimbo e o seu código PHP decide se os dois correspondem. Se corresponderem, processa o evento. Caso contrário, rejeita-o de imediato.
Se já tem um fluxo de pagamentos no site, esta mesma prática também se aplica a outras partes da infraestrutura. Quem consultar um guia sobre gateways de pagamento em criptomoedas para comércio eletrónico encontrará o mesmo princípio: aceitar menos, verificar mais e só depois atualizar a encomenda.
Requisitos para um endpoint de webhook em PHP
Não precisa de uma framework enorme para isto. Basta um endpoint em PHP 8.x; o PHP 7.4 também pode funcionar em alguns projetos. O importante é que o código consiga aceder ao corpo bruto do pedido e aos cabeçalhos enviados pela Payora. Por isso, a configuração do servidor deve permitir o acesso normal aos pedidos HTTP, sem uma camada personalizada que reescreva ou elimine cabeçalhos.
Use HTTPS. Sem TLS, o webhook é transmitido à vista de todos, e uma assinatura, por si só, não resolve os problemas de segurança de uma ligação desprotegida. Um certificado, uma configuração correta do host virtual e um URL público acessível são requisitos básicos.
Na prática, são importantes duas extensões do PHP: a extensão hash, para trabalhar com HMAC, e as funções JSON nativas, caso processe JSON. Também pode usar cURL para fazer testes do seu lado, mas o endpoint de webhook não precisa dessa extensão.
Também precisa do segredo ou da chave de assinatura da Payora, correspondente à configuração do webhook na sua conta Payora. Guarde esse valor fora da raiz pública do site e não o inclua no Git. Um segredo num repositório público é uma forma rápida de transformar a verificação numa mera encenação.
Mais uma coisa: o endpoint deve aceitar apenas pedidos POST. Os pedidos GET podem devolver o código 405. Esta pequena restrição facilita a resolução de problemas, pois reduz o tráfego que precisa de analisar e ajuda a perceber como proteger endpoint de webhook contra pedidos inesperados.
Recolher o payload bruto e os cabeçalhos do webhook
A verificação da assinatura depende dos bytes exatos enviados pela Payora. Em PHP, leia o corpo bruto do pedido com file_get_contents('php://input'). Não reconstrua o payload a partir de $_POST nem de JSON descodificado, pois qualquer uma dessas operações pode alterar os espaços, a ordem das chaves ou a codificação.
A mesma regra aplica-se aos cabeçalhos. Precisa do cabeçalho da assinatura. Use getallheaders() quando estiver disponível ou leia variáveis do servidor, como $_SERVER['HTTP_X_...'], se o seu ambiente não disponibilizar essa função. Pequenas diferenças entre serviços de alojamento podem fazer toda a diferença.
Imagine um corpo de webhook com 412 bytes. Se o seu código o descodificar e voltar a codificá-lo, os campos podem parecer iguais, mas os bytes já não serão idênticos. Basta essa diferença para a verificação falhar.
Guarde este hábito: primeiro recolha, depois analise. Comece por guardar a string bruta. Em seguida, verifique os cabeçalhos. Só depois deve processar a estrutura do payload.
Este mesmo hábito também poupa tempo quando testa fluxos de pagamento antes do lançamento. O artigo sobre como testar um pagamento em criptomoedas aplica a mesma lógica aos eventos de sandbox, em que a fidelidade do payload é essencial para que o teste seja útil.
Recriar a assinatura esperada em PHP
O método mais comum é calcular um HMAC com o payload bruto e um segredo partilhado. Para validar assinatura de webhook em PHP, comece pelo algoritmo indicado na documentação dos webhooks da Payora. Se a documentação especificar SHA-256, use SHA-256. Se indicar um prefixo, uma etiqueta de versão ou um formato de data e hora, inclua exatamente essa estrutura.
Um formato comum consiste em criar a mensagem assinada a partir da data e hora e do corpo bruto e, depois, executar hash_hmac() com o segredo partilhado e o algoritmo de hash indicado. Consoante as especificações, o resultado pode ter de ser codificado em hexadecimal ou em base64. Não adivinhe o formato: consulte as definições do webhook.
Eis um exemplo simples em estilo PHP, com marcadores de posição para os casos em que o formato da Payora possa ser diferente:
$rawBody = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_PAYORA_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_PAYORA_SIGNATURE'] ?? '';
$secret = getenv('PAYORA_WEBHOOK_SECRET');
$signedPayload = $timestamp . '.' . $rawBody;
$expected = hash_hmac('sha256', $signedPayload, $secret);Este exemplo ilustra apenas a estrutura geral; não é uma garantia de que corresponda ao formato da Payora. Se a Payora assinar apenas o payload, remova a data e hora da string. Se assinar uma versão canónica do JSON, siga exatamente esse método, em vez de usar o que preferir. Um delimitador incorreto fará com que todos os pedidos falhem.
Para os comerciantes que também se preocupam com regras de faturação e casos especiais, o artigo sobre limites de faturas para pagamentos em criptomoedas é um complemento útil, pois o tratamento dos webhooks e as políticas de faturação cruzam-se frequentemente no ciclo de vida de uma encomenda.
Comparar a assinatura calculada com a assinatura da Payora
Depois de calcular a assinatura esperada, compare-a com a assinatura recebida usando uma comparação em tempo constante. Na maioria dos casos, em PHP, isso significa usar hash_equals(). As comparações simples com == ou === não são adequadas neste caso, pois podem revelar informações através do tempo de execução e são menos seguras perante diferenças subtis.
Antes da comparação, normalize o formato exatamente como a Payora espera. Se o cabeçalho estiver em hexadecimal, compare valores hexadecimais. Se estiver em base64, compare valores em base64. Se a assinatura recebida tiver um prefixo como sha256=, remova-o ou inclua-o exatamente como indicado na documentação.
Exemplo de fluxo:
- Leia o corpo bruto.
- Leia o cabeçalho da assinatura.
- Calcule a assinatura esperada com a fórmula indicada na documentação.
- Compare as duas strings com
hash_equals(). - Rejeite o pedido com o código 401 ou 400 se as assinaturas não corresponderem.
O código 401 é adequado quando a assinatura está incorreta. O código 400 é apropriado quando faltam cabeçalhos obrigatórios. Em qualquer dos casos, a resposta deve ser breve. Não dê pistas adicionais.
Uma frase basta: nunca imprima o segredo. É um erro raro, mas catastrófico.
Tratar a data e hora, a proteção contra repetição e as regras de tolerância
Se a Payora incluir uma data e hora ou um nonce, verifique esse valor antes de aceitar o evento. Isso bloqueia ataques de repetição, em que um atacante reenvia mais tarde um webhook válido para tentar levar a sua aplicação a cobrar ou expedir uma encomenda duas vezes. O intervalo de tolerância deve seguir as regras da Payora, não uma estimativa sua.
Uma prática comum é comparar a data e hora do webhook com a hora do servidor e rejeitar pedidos fora do intervalo de tolerância. Se a documentação indicar 5 minutos, use 5 minutos. Se indicar 300 segundos, guarde esse valor numa definição com um nome claro, em vez de codificar um número misterioso em três ficheiros.
Uma diferença entre relógios pode fazer falhar pedidos legítimos. Um servidor com sete minutos de atraso pode rejeitar um pedido válido. Sincronize os servidores com NTP e mantenha os fusos horários definidos de forma consistente. O fuso horário não altera as marcas temporais Unix, mas definições locais incorretas podem dificultar a leitura dos registos.
Quando existem, os nonces são ainda mais simples de tratar. Guarde cada nonce uma única vez, marque-o como utilizado e rejeite qualquer repetição. Uma pequena cache ou tabela de base de dados pode resolver o problema. Um nonce, uma aceitação.
Se também aceitar preços fixos nas páginas de pagamento, é aqui que as políticas se tornam importantes. Um artigo separado sobre se deve usar um preço fixo pode ajudar a alinhar as regras de câmbio com o momento de receção do webhook, evitando confirmar o montante errado após um evento atrasado.
Processar eventos válidos e devolver a resposta HTTP adequada
Depois de a verificação ser aprovada, analise o payload e encaminhe o processamento de acordo com o tipo de evento. Um evento payment.succeeded não deve seguir o mesmo percurso de código que um evento refund.updated. Separe-os claramente. Assim, cada rotina tem uma única função e os registos ficam mais fáceis de ler.
A lógica de negócio deve ser executada antes de enviar uma resposta 2xx. Se marcar uma encomenda como paga, atualize primeiro a base de dados. Se tiver de colocar um email numa fila, faça-o primeiro. Se qualquer uma destas operações falhar, devolva um erro 500 e deixe a Payora tentar novamente mais tarde.
Esta ordem é importante porque uma resposta 2xx indica normalmente à Payora que o evento foi aceite. Se devolver 200 demasiado cedo e a escrita na base de dados falhar depois, pode haver uma divergência entre a Payora e a sua aplicação. Este tipo de erro pode sair caro às duas da manhã.
Uma resposta curta é suficiente. “OK” serve. Melhor ainda, não devolva qualquer corpo e mantenha o código de estado claro. Não devolva o payload na resposta.
Para as equipas que também faturam clientes a partir de eventos de pagamento, o artigo sobre gateways de pagamento em criptomoedas para freelancers mostra como um webhook pode transformar um evento técnico numa ação de faturação concreta, sem acrescentar etapas desnecessárias.
Resolver problemas de verificação de assinaturas
A maioria das falhas tem uma de cinco causas: o corpo bruto foi alterado, o nome do cabeçalho está incorreto, o segredo está errado, a codificação não corresponde ou a verificação da data e hora excedeu a tolerância. Comece pelo corpo. Se tiver registado o payload bruto e a assinatura calculada, compare-os antes de mexer na base de dados.
É comum o corpo ser alterado. O middleware da framework pode remover espaços em branco, descodificar JSON ou normalizar quebras de linha. Basta uma quebra de linha a mais. Se a Payora assinou {"a":1} e a sua aplicação lê {"a":1}\n, o HMAC não vai corresponder.
A incompatibilidade dos cabeçalhos é outra causa frequente. Alguns servidores convertem hífenes em sublinhados. Algumas camadas de proxy removem cabeçalhos personalizados, a menos que sejam explicitamente permitidos. Se o cabeçalho da assinatura não aparecer em PHP, verifique a configuração do servidor Web e do proxy, não apenas o código.
Os problemas de codificação são difíceis de detetar. UTF-8 e caracteres Unicode escapados podem parecer iguais nos registos e, ainda assim, gerar bytes diferentes. Se a aplicação reescrever o JSON, a assinatura pode deixar de corresponder, mesmo que o payload pareça idêntico no navegador.
Usar o segredo errado é a falha mais simples e aquela que muitas pessoas só verificam em último lugar. Confirme separadamente a variável de ambiente, o segredo de staging e o segredo de produção. Um segredo copiado da conta errada pode custar-lhe uma tarde inteira.
Uma diferença entre relógios provoca um sintoma diferente: a assinatura corresponde, mas o pedido é rejeitado porque a data e hora é demasiado antiga. Se isso acontecer, verifique o NTP, os relógios dos contentores e qualquer proxy inverso que registe a sua própria hora. Uma pequena diferença na infraestrutura pode parecer uma assinatura inválida.
Se a sua instalação também estiver integrada com o WordPress ou com a faturação de subscrições, o artigo sobre como aceitar pagamentos em USDT é útil, pois explica como o tratamento dos webhooks, o controlo de acessos e a confirmação do pagamento se articulam num único fluxo.
Há um último teste que pode ajudar bastante: envie o mesmo payload duas vezes. O primeiro pedido deve ser aceite. O segundo deve ser rejeitado se guardar e bloquear nonces reutilizados; caso contrário, só deve ser aceite se o seu webhook não usar proteção contra repetição baseada em nonce. Esta duplicação permite perceber se a sua proteção funciona mesmo.




Comments