Por que verificar
Qualquer pessoa na internet pode fazer POST para o seu endpoint. A verificação de assinatura garante que o evento veio realmente do kycert e não foi adulterado em trânsito.
Como funciona
O kycert assina cada requisição com HMAC-SHA256 usando o seu segredo de webhook. A assinatura é enviada no header kycert-signature:
kycert-signature: t=1718200818,v1=3f5e8a2b1c...
Parte Descrição tTimestamp Unix da requisição v1HMAC-SHA256 de ${t}.${payload}
Onde encontrar o segredo
No dashboard kycert, vá em Settings → Webhooks e copie o Webhook Secret (prefixo whsec_). Armazene como variável de ambiente — nunca em código.
Algoritmo de verificação
string_to_sign = timestamp + "." + raw_request_body
signature = HMAC-SHA256(webhook_secret, string_to_sign)
Comparar signature com v1 do header usando comparação de tempo constante.
Tolerância de tempo: rejeitar eventos com timestamp mais antigo que 5 minutos (300 segundos) em relação ao relógio atual. Isso protege contra ataques de replay.
Exemplos de implementação
import crypto from 'crypto'
function verifyKycertSignature (
rawBody : Buffer ,
signatureHeader : string ,
secret : string ,
) : boolean {
const parts = Object . fromEntries (
signatureHeader . split ( ',' ). map ( p => p . split ( '=' ) as [ string , string ])
)
const timestamp = parts [ 't' ]
const received = parts [ 'v1' ]
if ( ! timestamp || ! received ) return false
// Rejeitar eventos com mais de 5 minutos
if ( Math . abs ( Date . now () / 1000 - parseInt ( timestamp , 10 )) > 300 ) {
return false
}
const computed = crypto
. createHmac ( 'sha256' , secret )
. update ( ` ${ timestamp } . ${ rawBody . toString () } ` )
. digest ( 'hex' )
return crypto . timingSafeEqual (
Buffer . from ( computed , 'hex' ),
Buffer . from ( received , 'hex' ),
)
}
Uso em frameworks
Express
Next.js Route Handler
import express from 'express'
const app = express ()
app . post (
'/webhooks/kycert' ,
express . raw ({ type: 'application/json' }), // IMPORTANTE: raw body
( req , res ) => {
const signature = req . headers [ 'kycert-signature' ] as string
const valid = verifyKycertSignature (
req . body ,
signature ,
process . env . KYCERT_WEBHOOK_SECRET ! ,
)
if ( ! valid ) return res . status ( 401 ). json ({ error: 'invalid signature' })
res . sendStatus ( 200 ) // responder imediatamente
const event = JSON . parse ( req . body . toString ())
setImmediate (() => processEvent ( event ))
}
)
Use o body raw, não parseado. Middlewares como express.json() transformam o body antes que você possa lê-lo como string — a assinatura vai falhar. Configure express.raw() ou equivalente antes de qualquer parser JSON.
Erros comuns
Problema Causa Solução Assinatura sempre inválida Body parseado antes da verificação Usar middleware raw body Erro de tolerância de tempo Relógio do servidor desatualizado Sincronizar com NTP timingSafeEqual falhaTamanhos diferentes de buffer Garantir que ambos os buffers são hex strings do mesmo tamanho Header ausente Endpoint não é HTTPS em produção Certificar TLS no endpoint