Growth Station
DocumentaçãoAPI (Desenvolvedores)Guias por caso de usoValidar a assinatura do webhook (HMAC SHA-256)

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", 200

Se 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/5

A 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.

Fazer
  • Use sha256 na função de HMAC, exatamente como nos exemplos acima.
Não fazer
  • 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?

Nesta página

Última atualização