Validar a assinatura do webhook (HMAC SHA-256)
Confirme que cada entrega de webhook veio mesmo do GS Engage recalculando a assinatura HMAC SHA-256 a partir do corpo cru e do seu secret.
Validar a assinatura do webhook
Antes de confiar em qualquer aviso que chega na sua URL, confirme que ele veio mesmo do GS Engage.
Você configurou um webhook e agora o GS Engage envia um POST para a sua URL toda vez que algo acontece — uma prospecção foi ganha, uma ligação terminou, uma atividade foi concluída. Um webhook é só isso: um aviso automático que o GS Engage manda para o seu sistema.
O problema é que a sua URL fica exposta na internet. Qualquer pessoa que descobrir o endereço pode mandar um POST fingindo ser o GS Engage. Se o seu sistema confiar cegamente, alguém poderia inventar uma "venda ganha" que nunca existiu.
A validação de assinatura resolve isso. Cada entrega chega assinada, e só quem tem o seu secret consegue produzir uma assinatura válida. Você recalcula essa assinatura do seu lado e compara. Se bater, o aviso é autêntico. Se não bater, você descarta.
Onde isso se encaixa
Este guia é o passo seguinte depois de você criar o webhook. Se ainda não criou, comece em Criar e assinar um webhook e volte aqui com o seu secret em mãos.
HMAC (do inglês Hash-based Message Authentication Code, código de autenticação de mensagem) é uma forma de "carimbar" um texto usando uma senha secreta compartilhada.
Pense assim:
- O GS Engage pega o corpo da mensagem (o JSON que ele vai te enviar) e o seu
secret. - Ele passa os dois por uma função matemática — o SHA-256 — que produz um código curto e único: a assinatura.
- Esse código viaja junto com a entrega, dentro de um cabeçalho.
Do seu lado, você faz exatamente a mesma conta: pega o corpo que chegou, junta o seu secret e gera a sua própria assinatura. Como só você e o GS Engage conhecem o secret, só vocês dois conseguem produzir o mesmo código.
graph LR
A["Corpo da entrega (JSON cru)"] --> C["HMAC SHA-256"]
B["Seu secret"] --> C
C --> D["Assinatura calculada"]
E["Assinatura recebida no cabeçalho"] --> F{"São iguais?"}
D --> F
F -->|Sim| G["Entrega autêntica ✅"]
F -->|Não| H["Rejeitar 🚨"]De onde vem o secret
O secret é devolvido uma única vez, na resposta de criação do webhook (POST /api/v1/webhooks). Ele não aparece em nenhuma listagem depois. Se você não guardou, apague o webhook e crie outro para receber um secret novo.
Cada entrega é um POST com este formato de corpo:
{
"id": "evt_9f3c...",
"test": false,
"event": "prospection.won",
"data": { },
"retries": 0,
"manualRetries": 0,
"createdAt": "2026-07-17T14:02:11.000Z"
}A assinatura viaja em um cabeçalho da requisição HTTP, separada do corpo. É esse valor que você vai comparar com o que calcular.
Os exemplos abaixo fazem três coisas: leem o corpo cru, recalculam a assinatura com o seu secret e comparam em tempo constante. Escolha a sua linguagem.
Este exemplo usa o Express. O ponto crucial é capturar o corpo como texto puro, antes de qualquer parse de JSON.
const express = require("express");
const crypto = require("crypto");
const app = express();
// Guarda o corpo CRU (Buffer) para poder recalcular a assinatura.
app.use(
express.json({
verify: (req, res, buf) => {
req.rawBody = buf;
},
})
);
const WEBHOOK_SECRET = process.env.GSENGAGE_WEBHOOK_SECRET;
function assinaturaConfere(rawBody, assinaturaRecebida) {
// Recalcula HMAC SHA-256 sobre o corpo cru + o seu secret.
const esperada = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(rawBody)
.digest("hex");
const a = Buffer.from(esperada);
const b = Buffer.from(assinaturaRecebida || "");
// Comparação em tempo constante: evita vazar dicas pelo tempo de resposta.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post("/webhooks/gsengage", (req, res) => {
// Substitua pelo nome exato do cabeçalho de assinatura da entrega.
const assinaturaRecebida = req.get("<cabecalho-de-assinatura>");
if (!assinaturaConfere(req.rawBody, assinaturaRecebida)) {
// Não bateu: descarte sem processar.
return res.status(401).send("assinatura inválida");
}
const evento = req.body;
console.log("Entrega autêntica:", evento.event, evento.id);
// Responda rápido; processe o resto em segundo plano se for pesado.
res.status(200).send("ok");
});
app.listen(3000);Este exemplo usa Flask. Repare que ele lê request.get_data() (o corpo cru) e não request.json.
import hmac
import hashlib
import os
from flask import Flask, request
app = Flask(__name__)
WEBHOOK_SECRET = os.environ["GSENGAGE_WEBHOOK_SECRET"]
def assinatura_confere(raw_body: bytes, assinatura_recebida: str) -> bool:
# Recalcula HMAC SHA-256 sobre o corpo cru + o seu secret.
esperada = hmac.new(
WEBHOOK_SECRET.encode("utf-8"),
raw_body,
hashlib.sha256,
).hexdigest()
# Comparação em tempo constante: evita vazar dicas pelo tempo de resposta.
return hmac.compare_digest(esperada, assinatura_recebida or "")
@app.post("/webhooks/gsengage")
def receber_webhook():
raw_body = request.get_data() # corpo CRU, em bytes
# Substitua pelo nome exato do cabeçalho de assinatura da entrega.
assinatura_recebida = request.headers.get("<cabecalho-de-assinatura>")
if not assinatura_confere(raw_body, assinatura_recebida):
# Não bateu: descarte sem processar.
return "assinatura inválida", 401
evento = request.get_json()
print("Entrega autêntica:", evento["event"], evento["id"])
# Responda rápido; processe o resto depois se for pesado.
return "ok", 200Se a assinatura bater, você tem certeza de que o aviso é do GS Engage e pode processar o evento com segurança — atualizar o CRM, disparar uma notificação, o que fizer sentido para o seu negócio.
Use o corpo CRU, nunca o JSON reparseado
A assinatura é calculada sobre os bytes exatos que chegaram. Se você transformar o corpo em objeto e depois em texto de novo (JSON.stringify, json.dumps), o resultado quase sempre fica diferente do original — a ordem das chaves, os espaços e a formatação mudam. Aí a conta não bate mesmo sendo uma entrega legítima. Sempre valide contra o corpo cru (req.rawBody, request.get_data()) e só depois faça o parse.
Se não bater, rejeite — sem exceção
Uma assinatura que não confere significa uma de duas coisas: a mensagem foi adulterada no caminho, ou não veio do GS Engage. Nos dois casos, responda com 401 e não processe nada. Nunca "deixe passar por via das dúvidas": um único evento falso aceito pode sujar seus dados ou disparar uma ação indevida.
Antes de ir para produção
0/5A assinatura dos webhooks passou a ser calculada com SHA-256, no lugar do antigo SHA-1. O SHA-256 é o algoritmo recomendado hoje: mais robusto e sem as fraquezas conhecidas do SHA-1.
- Use
sha256na função de HMAC, exatamente como nos exemplos acima.
- Não deixe código antigo calculando
sha1— as assinaturas nunca vão bater e você vai rejeitar entregas legítimas.
Migrando um verificador antigo
Se o seu integrador já validava com SHA-1, a mudança é de uma linha: troque o algoritmo para sha256. O resto da lógica — corpo cru, comparação em tempo constante, rejeição em caso de falha — continua igual.
Perdi o meu secret. Como recupero?
Não há como recuperar: o secret só aparece na resposta de criação do webhook. Apague o webhook atual com DELETE /api/v1/webhooks/{webhookId} e crie um novo com POST /api/v1/webhooks para receber um secret novo.
A conta nunca bate, mesmo com o secret certo. O que testar primeiro?
Na quase totalidade dos casos é o corpo. Confirme que você está passando o corpo cru para o HMAC, e não o JSON reparseado. Um console.log do rawBody ajuda a ver se algum middleware está transformando o corpo antes de você validá-lo.
O que é o campo test do envelope?
Quando test é true, a entrega foi disparada como teste (não corresponde a um evento real de negócio). A assinatura é calculada da mesma forma — valide igual e trate o conteúdo como um ensaio.
Preciso de tudo isso se eu uso Zapier ou Make?
Não. As plataformas no-code que consomem os gatilhos de webhook (como o Zapier) cuidam do recebimento por você. A validação de assinatura entra em cena quando você recebe o webhook no seu próprio servidor, com código.
Este artigo foi útil?
Ser avisado quando ganho ou perco uma venda
Crie um webhook no GS Engage para receber os eventos prospection.won e prospection.lost em tempo real, sem precisar ficar consultando a API.
Extrair prospecções e atividades para um dashboard
Puxe suas prospecções e atividades do GS Engage de forma paginada para montar um dashboard de funil e calcular sua taxa de conversão.