Pular para o conteúdo
m MARX / docs

/ Notificações

Validar assinatura (HMAC)

Qualquer pessoa que descubra a URL do seu webhook pode enviar requisições para ela. Para ter certeza de que uma notificação veio da marx e não foi alterada no caminho, ative a assinatura HMAC e valide cada requisição.

Ativando

No painel, em Integração → Webhooks, ligue Assinar com HMAC e salve (a senha é pedida). Depois, em Segredo de assinatura → Ver, copie o segredo e guarde no seu servidor.

A partir daí, todo webhook chega com dois cabeçalhos extras:

Cabeçalho Exemplo Significado
X-Marx-Timestamp 1759708800 Momento do envio (Unix, segundos).
X-Marx-Signature t=1759708800,v1=5f2b…c9 Timestamp + assinatura HMAC-SHA256 em hexadecimal.

Como a assinatura é calculada

TEXT
assinatura = HMAC_SHA256(segredo, "<timestamp>.<corpo bruto da requisição>")
  • timestamp é o valor de t= (o mesmo de X-Marx-Timestamp);
  • o corpo bruto é exatamente o texto recebido, byte a byte. Valide antes de fazer o parse do JSON: reformatar o JSON muda a assinatura.

Passo a passo da validação

  1. Leia X-Marx-Signature e separe t e v1.
  2. Recuse se t estiver a mais de 5 minutos do seu relógio (evita reenvio de requisições antigas).
  3. Calcule o HMAC-SHA256 de "<t>.<corpo bruto>" com o seu segredo.
  4. Compare com v1 usando comparação em tempo constante.
  5. Só então processe o evento e responda 2xx.

Node.js (Express)

JavaScript
import crypto from 'node:crypto'
import express from 'express'

const app = express()
const SECRET = process.env.MARX_WEBHOOK_SECRET

app.post('/webhooks/marx', express.raw({ type: '*/*' }), (req, res) => {
  const header = req.get('X-Marx-Signature') || ''
  const parts = Object.fromEntries(header.split(',').map((kv) => kv.split('=')))
  const age = Math.abs(Date.now() / 1000 - Number(parts.t))
  const expected = crypto.createHmac('sha256', SECRET).update(`${parts.t}.${req.body}`).digest('hex')

  const valid = parts.v1?.length === expected.length && age < 300 &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
  if (!valid) return res.status(401).end()

  const event = JSON.parse(req.body)
  // ... processe o evento (de forma idempotente) ...
  res.status(204).end()
})

Python (Flask)

Python
import hmac, hashlib, os, time
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["MARX_WEBHOOK_SECRET"].encode()

@app.post("/webhooks/marx")
def marx_webhook():
    raw = request.get_data()  # corpo bruto
    parts = dict(p.split("=", 1) for p in request.headers.get("X-Marx-Signature", "").split(",") if "=" in p)
    expected = hmac.new(SECRET, f"{parts.get('t')}.".encode() + raw, hashlib.sha256).hexdigest()
    if abs(time.time() - int(parts.get("t", 0))) > 300 or not hmac.compare_digest(expected, parts.get("v1", "")):
        abort(401)
    event = request.get_json()
    # ... processe o evento ...
    return "", 204

Ruby (Rails)

Ruby
class MarxWebhooksController < ActionController::API
  def create
    raw = request.raw_post
    parts = request.headers['X-Marx-Signature'].to_s.split(',').to_h { |kv| kv.split('=', 2) }
    expected = OpenSSL::HMAC.hexdigest('SHA256', ENV.fetch('MARX_WEBHOOK_SECRET'), "#{parts['t']}.#{raw}")

    fresh = (Time.now.to_i - parts['t'].to_i).abs < 300
    return head :unauthorized unless fresh && ActiveSupport::SecurityUtils.secure_compare(expected, parts['v1'].to_s)

    event = JSON.parse(raw)
    # ... processe o evento ...
    head :no_content
  end
end

PHP

PHP
<?php
$raw = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_MARX_SIGNATURE'] ?? ''), $parts);
$expected = hash_hmac('sha256', ($parts['t'] ?? '') . '.' . $raw, getenv('MARX_WEBHOOK_SECRET'));

if (abs(time() - (int)($parts['t'] ?? 0)) > 300 || !hash_equals($expected, $parts['v1'] ?? '')) {
    http_response_code(401);
    exit;
}
$event = json_decode($raw, true);
// ... processe o evento ...
http_response_code(204);

Trocando o segredo

Em Segredo de assinatura → Gerar novo (avisos, frase GERAR NOVO SEGREDO e senha). Os webhooks seguintes já saem assinados com o novo segredo. Para trocar sem perder notificações, aceite temporariamente os dois segredos no seu servidor e remova o antigo depois da troca.

Dica

Use Integração → Enviar teste: a marx envia um evento ping assinado para a sua URL e mostra o código HTTP e o tempo de resposta. É a forma mais rápida de testar a sua validação.

Dúvidas sobre a integração? Fale com o time marx pelo seu canal de suporte.

© 2026 marx