Pular para o conteúdo principal

Conferir a assinatura do webhook

A função recebe o segredo, os headers X-Etech-Timestamp e X-Etech-Signature e o corpo cru. Ela devolve true só quando o timestamp está na janela de 5 minutos e a assinatura confere. As regras estão em Webhook de venda.

Use o corpo cru

Calcule a assinatura sobre os bytes que chegaram, antes de qualquer parse. Um JSON lido e serializado de novo muda espaços e a ordem dos campos, e a assinatura deixa de conferir.

using System.Security.Cryptography;
using System.Text;

static class AssinaturaWebhook
{
static readonly TimeSpan Janela = TimeSpan.FromMinutes(5);

public static bool Valida(string segredo, string? timestamp, string corpoCru, string? assinatura, DateTimeOffset agora)
{
if (timestamp is null || assinatura is null || !long.TryParse(timestamp, out var segundos))
return false;

var enviadoEm = DateTimeOffset.FromUnixTimeSeconds(segundos);
if ((agora - enviadoEm).Duration() > Janela)
return false;

var hash = HMACSHA256.HashData(
Encoding.UTF8.GetBytes(segredo),
Encoding.UTF8.GetBytes(timestamp + "." + corpoCru));
var esperada = "v1=" + Convert.ToHexString(hash).ToLowerInvariant();

return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(esperada),
Encoding.UTF8.GetBytes(assinatura));
}
}

No ASP.NET Core, leia o corpo cru antes do model binding: using var leitor = new StreamReader(Request.Body, Encoding.UTF8); var corpoCru = await leitor.ReadToEndAsync();.

Vetor de teste​

Use estes valores para conferir a sua implementação antes de testar com a ETech. Com o relógio em 1792681260 (60 s depois do timestamp), a função tem de devolver true. Com um espaço a mais no corpo, ou com o relógio 6 minutos adiante, tem de devolver false.

EntradaValor
SegredodGVzdGUtZGUtYXNzaW5hdHVyYS1ldGVjaA
X-Etech-Timestamp1792681200
X-Etech-Signaturev1=cd609fc3145c09b4edd66a62938898209c8abf10255d169fced6604d96bc0d74

Corpo, exatamente como abaixo, numa linha só e sem quebra de linha no fim:

{"id":"0f7c2d1e-4b9a-4c3e-8d21-6a5b7c8d9e01","type":"integration.ping","sequence":1,"occurredAt":"2026-10-22T15:00:00+00:00","organizationId":2,"data":{"message":"ping"}}

O segredo é usado como texto, sem decodificar o base64url.