Valide a assinatura antes de usar os dados de qualquer webhook. A StackZap gera X-StackZap-Signature com HMAC-SHA256 usando o segredo configurado e os bytes do corpo JSON enviado. Se você reserializar o JSON antes de calcular o HMAC, espaços ou ordem de campos podem mudar e produzir uma assinatura diferente.
Exemplo em Node.js
O servidor precisa preservar os bytes originais do corpo em req.rawBody antes de interpretar o JSON:
import { createHmac, timingSafeEqual } from "node:crypto";
function validarWebhook(req, secret) {
const recebido = Buffer.from(req.get("X-StackZap-Signature") || "", "hex");
const esperado = createHmac("sha256", secret).update(req.rawBody).digest();
return recebido.length === esperado.length && timingSafeEqual(recebido, esperado);
}
Adapte a captura de rawBody ao framework HTTP que você usa. O exemplo mostra a comparação criptográfica; ele não instala middleware para preservar o corpo.
Valide antes de processar
- Leia o corpo bruto da requisição.
- Recupere o segredo associado ao webhook cadastrado.
- Calcule HMAC-SHA256 sobre os bytes recebidos.
- Compare os valores em tempo constante e rejeite assinaturas ausentes ou inválidas.
- Só então interprete o JSON e encaminhe o evento para sua lógica.
Os headers X-StackZap-Event, X-StackZap-Delivery e X-StackZap-Timestamp ajudam a identificar a entrega, mas não substituem a validação do HMAC. Use o ID de entrega para evitar duplicidade e defina sua própria política de idade máxima se o seu cenário exigir proteção contra repetição.
Nunca inclua o segredo nos logs nem o envie em uma resposta de erro. Faça um teste pelo painel ou endpoint documentado e compare a assinatura calculada com o header recebido. Consulte a documentação técnica da StackZap para conferir o comportamento da versão em uso.
O que o HMAC protege
HMAC é um código de autenticação calculado a partir de uma mensagem e de um segredo conhecido pelas duas partes. No webhook, ele ajuda a detectar se os bytes recebidos correspondem ao corpo assinado por quem possui o segredo. Se alguém alterar o conteúdo sem conhecer o segredo, a assinatura calculada diverge. Isso permite rejeitar payload adulterado antes de acionar sua lógica de negócio.
A assinatura não criptografa o corpo: use HTTPS para proteger dados em trânsito. Ela também não significa que o evento deva ser aceito sem outras validações. Ainda é necessário validar schema, tamanho, evento permitido, identificador de entrega e regras de negócio. Uma assinatura válida indica integridade segundo o segredo compartilhado; a autorização para executar uma ação específica continua sendo responsabilidade da aplicação.
Preserve os bytes originais
Frameworks web frequentemente analisam JSON automaticamente. Isso pode transformar o texto original em objeto e, se depois serializado de novo, alterar espaços, escapes, quebras de linha ou ordem das propriedades. HMAC é calculado sobre bytes, então duas representações equivalentes de JSON podem produzir assinaturas diferentes. Capture os bytes originais antes do parser ou use o recurso rawBody suportado pelo framework.
Não remova bytes, normalize quebras de linha nem recodifique texto antes da verificação. Se houver proxy ou gateway entre a StackZap e seu endpoint, confirme que ele encaminha o corpo sem transformação. Uma falha sistemática de assinatura costuma indicar segredo diferente, corpo modificado ou algoritmo/formato interpretado incorretamente.
Entenda os headers da entrega
Além de X-StackZap-Signature, o envio pode conter headers de evento, entrega e timestamp. Use X-StackZap-Event para escolher um handler permitido, X-StackZap-Delivery para correlacionar ou deduplicar a entrega e timestamp para diagnóstico. Consulte o contrato atual para saber exatamente como são codificados e quais valores participam do HMAC. Não presuma que esses headers integrem a entrada da assinatura se a documentação diz que somente o corpo é assinado.
O ID de entrega é útil porque webhooks podem ser repetidos após falhas. Armazene IDs processados e torne a lógica idempotente. Um evento pode chegar novamente, e uma assinatura válida não significa que ele ainda não foi tratado. Para proteção contra replay, estabeleça uma política de idade usando timestamp apenas se sua semântica, unidade e formato forem conhecidos; compare com relógio sincronizado e aceite uma margem operacional definida.
Gere um HMAC local para depuração
Para conferir o cálculo com payload sintético, gere o HMAC em ferramenta local confiável e compare com a assinatura recebida. Este exemplo imprime somente a assinatura:
import { createHmac } from "node:crypto";
const body = Buffer.from('{"event":"example","data":{}}', "utf8");
const expected = createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(body)
.digest("hex");
console.log(expected);
O corpo usado na depuração deve ser exatamente o transmitido, inclusive espaços e ordem dos campos. Em produção, a validação acontece no handler antes de efeitos colaterais; não basta calcular uma assinatura manualmente uma vez. Proteja o acesso ao processo e não passe o segredo como argumento que fique no histórico do terminal.
Compare em tempo constante
Não compare assinaturas secretas usando igualdade de strings comum se sua biblioteca criptográfica oferece comparação de tempo constante. Primeiro valide se os valores têm o mesmo tamanho e formato esperado. Depois converta a representação documentada, por exemplo hexadecimal, para bytes e use a função apropriada. Isso reduz um canal de vazamento baseado na duração de comparações parciais.
Rejeite assinatura ausente, malformada ou de tamanho incorreto antes do processamento. Não tente corrigir automaticamente valores truncados, remover prefixos arbitrários ou aceitar algoritmo alternativo sem especificação oficial. Um handler estrito é mais fácil de auditar e evita aceitar payloads inesperados.
Investigue uma assinatura inválida
Verifique nesta ordem: o segredo configurado corresponde ao webhook do ambiente; o corpo bruto foi preservado; o algoritmo é HMAC-SHA256; o digest foi convertido para o formato documentado; o header certo foi lido; nenhum middleware alterou o body. Compare bytes localmente com um evento sintético e evite imprimir o segredo. Se necessário, registre somente tamanhos, hash não sensível do corpo e resultado da verificação.
Uma chave de staging não valida eventos de produção, mesmo que os endpoints pareçam iguais. No StackZap Cloud, as configurações são específicas do ambiente. No self-hosted, o operador deve conferir o segredo e a rota receptora. Se houver rotação, alinhe as duas pontas conforme o procedimento documentado para evitar uma janela em que nenhum webhook seja aceito.
Resposta e processamento depois da validação
Depois de validar, confira o evento e persista a entrega antes de responder sucesso. Uma assinatura inválida deve ser rejeitada sem executar ações. Uma assinatura válida com JSON inválido também precisa ser tratada como entrada não processável; não marque como concluída sem guardar informação suficiente para investigação segura.
Compatibilidade do middleware
Ao atualizar Node.js ou framework HTTP, confirme que o callback ainda recebe Buffer e que a captura ocorre antes de qualquer transformação. Uma mudança de parser pode alterar o momento em que rawBody fica disponível. Inclua um teste de integração que envie os mesmos bytes com assinatura correta e incorreta pelo caminho HTTP real, não apenas uma chamada direta à função de validação.
Se a infraestrutura termina TLS num proxy, verifique compressão, limite de tamanho e transferência em chunks. O proxy pode alterar encoding ou rejeitar o corpo antes da aplicação. Uma assinatura inválida em todos os eventos geralmente aponta para configuração compartilhada; falha num evento só pode indicar que um handler usa corpo diferente ou segredo incorreto.
Mensagem para atendimento
Quando pedir ajuda, descreva ambiente, hora, rota, header de evento e ID de entrega sanitizado, status HTTP e se a falha começou após mudança de código. Não envie header de assinatura completo, segredo ou corpo original com dados de contato. Se precisar comparar, prepare payload sintético reproduzível e explique quais bytes foram assinados.
Evite responder com detalhes que ajudem terceiros a adivinhar segredo ou formato. Retorne o status definido para sua integração e registre internamente uma categoria de falha. Coloque trabalho numa fila durável para processar com retries controlados e deduplicação, mantendo o handler de entrada curto.
Checklist de HMAC
- Segredo distinto por ambiente e protegido em configuração restrita.
- HTTPS habilitado no endpoint de callback.
- Bytes brutos capturados antes de analisar JSON.
- HMAC-SHA256 e encoding implementados como na referência.
- Comparação segura, com tamanho e formato validados.
- Assinatura conferida antes de qualquer efeito colateral.
- ID de entrega usado para deduplicação.
- Timestamp tratado conforme semântica documentada.
- Logs não contêm segredo, assinatura completa ou payload pessoal.
- Testes cobrem body alterado, header ausente, replay e entrega duplicada.
Integração com Express
No Express, o middleware que lê JSON pode consumir o corpo antes do seu handler. Configure a captura do buffer bruto com o recurso da versão utilizada do parser, preservando-o junto do objeto analisado. O exemplo abaixo é conceitual: confira a opção oficial da biblioteca que você instalou e não registre o buffer em produção.
app.use(express.json({
verify(req, res, buffer) {
req.rawBody = Buffer.from(buffer);
},
}));
Quando o handler receber a requisição, primeiro verifique a presença e a forma de X-StackZap-Signature, depois compare HMAC com req.rawBody. Só depois use req.body. Um middleware global não deve transformar os bytes antes de capturá-los. Se houver vários parsers ou rotas, confirme qual deles atende o callback.
Separe autenticação de autorização do evento
Após a assinatura ser válida, confira se o tipo do evento é um dos que sua aplicação espera para aquele callback. Não use o nome enviado no header para chamar dinamicamente qualquer função ou classe. Mantenha uma tabela explícita entre eventos conhecidos e handlers autorizados. Valide também os campos necessários para cada tipo; um payload autenticado ainda pode ser incompatível com uma versão nova ou com dados incompletos.
Persista o ID de entrega com status inicial e mude para processado somente depois do tratamento durável. Se o processo cair no meio, um worker pode retomar a entrega. Use transação para gravar o evento e o estado relacionado sempre que possível. Se uma ação externa não puder participar da transação, mantenha uma caixa de saída/outbox ou registro de intenção para evitar inconsistência entre o banco e a chamada externa.
Teste os casos negativos
Além do caso correto, teste assinatura ausente, bytes alterados, segredo errado, encoding inválido, corpo vazio, payload malformado, ID repetido, timestamp fora da janela configurada e tamanho acima do limite. Cada teste deve verificar que nenhum efeito de negócio ocorreu. Testar apenas a função que calcula o HMAC não garante que o endpoint realmente execute a validação antes de processar.
Use clock sincronizado se a política de replay depender de timestamp. Registre divergência de relógio como alerta operacional; não aumente a janela indefinidamente para eliminar erros sem entender sua origem. O timestamp e seu uso devem seguir o contrato oficial para a versão em execução.
Migração e troca de segredo
Se alterar o secret, coordene a configuração da StackZap e da aplicação receptora. Confirme se o produto permite período de transição com dois segredos; não implemente essa tolerância localmente sem suporte documentado. Faça a atualização num horário conhecido, acompanhe a taxa de assinatura inválida e tenha um procedimento de retorno. Segredos distintos por ambiente impedem que teste altere a validação de produção.
Se uma assinatura inválida começar após um deploy, compare a versão da biblioteca, captura do raw body, encoding, configuração carregada e caminho do callback. Verifique se o proxy está recomprimindo ou reserializando JSON. Envie ao suporte somente horários, ambiente, status e IDs; nunca o segredo nem um payload real completo.
Como interpretar um pico de rejeições
Uma taxa alta de assinatura inválida pode surgir após rotação do segredo, atualização do parser, mudança de proxy ou deploy em ambiente com configuração errada. Compare quando a falha começou com o histórico de release e compare staging com produção. Se apenas um evento falha, examine o caminho daquele handler; se todos falham, comece pela captura do corpo e segredo configurado.
Não desabilite a validação para “manter o webhook funcionando”. Isso aceitaria conteúdo sem comprovar integridade. Se precisar restaurar o serviço, volte a configuração conhecida, reduza o escopo do callback e mantenha eventos rejeitados para diagnóstico seguro, conforme política de retenção.
Ao operar endpoints duplicados durante uma migração, aplique validação em cada um e retire a rota antiga somente depois de confirmar que não recebe entregas legítimas. Não encaminhe assinatura recebida para outro serviço como se isso substituísse a verificação local; valide antes de confiar nos campos.
Mantenha um alerta para aumento repentino de assinaturas inválidas e correlacione-o com deploys e troca de configuração. Durante uma falha, rejeite o evento sem executar efeitos e mantenha dados mínimos para investigação protegida. Depois de restaurar a configuração, valide uma entrega real de teste e confira que a deduplicação continua funcionando. Documente o resultado do incidente para que a próxima rotação não dependa de memória individual.
