Docs Entrar na plataforma

Webhooks

Verificar assinatura

Confirme que cada entrega veio da Oneos e não foi alterada, com HMAC-SHA256 e o seu signing secret.

O header

header
x-oneos-signature: t=1789482001,v1=5f0c3b…e19a2e
ParteSignifica
tMomento da tentativa de entrega, em segundos Unix.
v1HMAC-SHA256, em hexadecimal, de t + . + corpo bruto, com o signing secret como chave.

Passo a passo

  1. Leia o corpo bruto

    Pegue os bytes exatamente como chegaram, antes de converter para JSON. Reformatar o JSON muda os bytes e a assinatura não bate.

  2. Separe t e v1

    Divida o header por vírgula e cada parte pelo primeiro =.

  3. Calcule o HMAC

    HMAC-SHA256(secret, t + "." + corpo), em hexadecimal. A chave é o secret inteiro, com o prefixo whsec_.

  4. Compare em tempo constante

    Use a comparação segura da sua linguagem (timingSafeEqual, hmac.compare_digest, hash_equals) e confira o tamanho antes.

  5. Recuse eventos velhos

    Recomendamos rejeitar quando |agora − t| passar de 5 minutos. A regra é do seu lado: ela protege contra alguém reenviar uma entrega antiga capturada.

Código

Verificação
import express from 'express'
import { createHmac, timingSafeEqual } from 'node:crypto'

const SECRET = process.env.ONEOS_WEBHOOK_SECRET // whsec_...
const TOLERANCE_SECONDS = 300

function isValidSignature(rawBody, header) {
  const parts = Object.fromEntries(header.split(',').map((part) => part.split('=')))
  const timestamp = Number(parts.t)
  if (!Number.isInteger(timestamp) || !parts.v1) return false
  if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) return false

  const expected = createHmac('sha256', SECRET).update(`${timestamp}.${rawBody}`).digest()
  const received = Buffer.from(parts.v1, 'hex')
  return expected.length === received.length && timingSafeEqual(expected, received)
}

const app = express()

// corpo BRUTO: o HMAC é sobre os bytes exatos que chegaram
app.post('/webhooks/oneos', express.raw({ type: 'application/json' }), (req, res) => {
  const rawBody = req.body.toString('utf8')
  if (!isValidSignature(rawBody, req.get('x-oneos-signature') ?? '')) {
    return res.sendStatus(400)
  }

  const event = JSON.parse(rawBody)
  res.sendStatus(200) // responda rápido: o limite é 10 s

  enqueue(event) // processe fora do request, deduplicando por event.id
})

Teste aqui

Interativo

Cole o secret, o header e o corpo bruto que o seu endpoint recebeu. O cálculo acontece no seu navegador — nada sai desta página.

Assinatura esperada
Assinatura recebida
Idade do evento

Calculando…

Detalhes que evitam dor de cabeça

  • Cada tentativa tem t e assinatura novos, sobre o mesmo corpo. Um reenvio não repete a assinatura anterior.
  • Rotacionar o secret vale na hora, inclusive para entregas que ainda estão na fila. Atualize o seu sistema logo depois de rotacionar.
  • Responda 400 ou 401 para assinatura inválida: a Oneos retenta e a falha aparece no histórico de entregas.