Webhooks

Os webhooks são uma forma eficiente de receber notificações em tempo real sobre eventos específicos que ocorrem no sistema Mercos.

Geralmente, em até 3 minutos, o evento configurado será enviado para o destino desejado, fornecendo uma atualização automática dos dados desejados.

Para cada evento, um payload em formato JSON é enviado para a URL configurada. A seguir, detalhamos a estrutura de dados retornada para os quatro eventos disponíveis atualmente.

💡

Para mais informações sobre como configurar os Webhooks através do sistema Mercos, confira a Central de Ajuda🔗

Validação de autenticidade [/webhooks/autenticacao]

Ao configurar um webhook com uma chave de validação, cada requisição enviada pela Mercos incluirá o header X-Hub-Signature-256 contendo uma assinatura HMAC-SHA256 do payload. Com isso, é possível verificar que a requisição foi realmente enviada pelo Mercos e que o conteúdo não foi alterado em trânsito.

Obs: Se nenhuma chave de validação estiver configurada, o header X-Hub-Signature-256 não será enviado.

💡

Lembrando que a validação não é obrigatória, mas altamente recomendada.

Chave de validação

A chave de validação é uma string hexadecimal de 64 caracteres, configurada no momento da criação ou atualização do webhook. Ela é utilizada como segredo compartilhado entre o Mercos e o seu servidor.

🔐

Boas práticas de segurança:

  • Armazene a chave de forma segura no seu servidor (ex: variável de ambiente, cofre de segredos)
  • Nunca exponha a chave no frontend, em logs ou em repositórios de código
  • Em caso de comprometimento, atualize a chave imediatamente via API

Como validar a assinatura

Ao receber uma requisição de webhook, siga estes passos antes de processar o payload:

  1. Extraia o valor do header X-Hub-Signature-256 da requisição
  2. Remova o prefixo sha256= do valor obtido — o restante é o digest hexadecimal enviado pelo Mercos
  3. Recalcule o HMAC-SHA256 utilizando:
    • Chave: sua chave de validação, convertida de hexadecimal para bytes
    • Mensagem: o body raw da requisição (os bytes exatos recebidos, sem deserializar o JSON)
    • Algoritmo: SHA-256
  4. Compare o digest recebido com o digest calculado utilizando uma função de comparação em tempo constante (resistente a timing attacks)
  5. Se os digests forem iguais, a requisição é autêntica — prossiga com o processamento. Se forem diferentes, rejeite a requisição (ex: retorne HTTP 401)

Obs: Validar a assinatura antes de deserializar o JSON permite rejeitar requisições inválidas sem consumir recursos com o processamento do payload.

Exemplo para verificação

Utilize os valores abaixo para validar sua implementação:

  • Chave de validação: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
  • Body raw: {"evento":"pedido.gerado","dados":{"pedido_id":12345}}
  • Header esperado: X-Hub-Signature-256: sha256=9295ddcc76d83b5905ba63c27093c2b482ef0b1c7382d80a08bebe5a938f3175

Exemplos de implementação

Python

import hmac
import hashlib

def validar_assinatura(chave_validacao: str, header_assinatura: str, body: bytes) -> bool:
    digest_recebido = header_assinatura.removeprefix("sha256=")
    digest_calculado = hmac.new(
        bytes.fromhex(chave_validacao), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(digest_recebido, digest_calculado)

JavaScript (Node.js)

const crypto = require("crypto");

function validarAssinatura(chaveValidacao, headerAssinatura, body) {
  const digestRecebido = headerAssinatura.replace("sha256=", "");
  const digestCalculado = crypto
    .createHmac("sha256", Buffer.from(chaveValidacao, "hex"))
    .update(body)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(digestRecebido),
    Buffer.from(digestCalculado)
  );
}

Go

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "strings"
)

func validarAssinatura(chaveValidacao string, headerAssinatura string, body []byte) bool {
    digestRecebido := strings.TrimPrefix(headerAssinatura, "sha256=")

    chaveBytes, _ := hex.DecodeString(chaveValidacao)
    hash := hmac.New(sha256.New, chaveBytes)
    hash.Write(body)
    digestCalculado := hex.EncodeToString(hash.Sum(nil))

    return hmac.Equal([]byte(digestRecebido), []byte(digestCalculado))
}

Java

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.util.HexFormat;

public class WebhookValidator {
    public static boolean validarAssinatura(
        String chaveValidacao, String headerAssinatura, byte[] body
    ) throws Exception {
        String digestRecebido = headerAssinatura.replace("sha256=", "");

        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(
            HexFormat.of().parseHex(chaveValidacao), "HmacSHA256"
        ));
        String digestCalculado = HexFormat.of().formatHex(mac.doFinal(body));

        return MessageDigest.isEqual(
            digestRecebido.getBytes(), digestCalculado.getBytes()
        );
    }
}