Wompi: como gerar a assinatura de integridade e verificar webhooks, com código

Como se calcula a assinatura de integridade do checkout da Wompi e como comprovar que um evento realmente vem da Wompi, com funções em Python e JavaScript testadas contra o exemplo da sua documentação.

Neste artigo
Em 30 segundos

Com a Wompi você assina o pagamento no seu servidor com um SHA-256 de referência, valor em centavos, moeda e o seu segredo de integridade, e verifica cada evento recalculando um SHA-256 com as propriedades que o evento declara, o seu timestamp e o seu segredo de eventos.

1. Duas assinaturas diferentes, dois segredos diferentes#

Integrar a Wompi em um site implica duas verificações que se confundem com facilidade. A assinatura de integridade protege o pagamento que o seu servidor entrega ao checkout: evita que alguém altere o valor no navegador. A verificação do evento protege o seu servidor: comprova que a notificação de uma mudança de estado vem da Wompi e não de um terceiro.

Cada uma usa um segredo diferente, que não é nem a chave pública nem a privada. Segundo a documentação da Wompi, ambos estão no painel de comerciantes, na seção de segredos para integração técnica, e não devem sair do seu servidor.

Documentação: Wompi: Widget e Checkout Web (assinatura de integridade, espanhol) ↗ · Wompi: Eventos (verificação do checksum, espanhol) ↗

2. A assinatura de integridade do checkout#

A documentação indica que a assinatura é o SHA-256, em hexadecimal, da concatenação da referência, do valor em centavos, da moeda e do segredo de integridade. Se o pagamento tem data de expiração, essa data vai entre a moeda e o segredo. O valor vai em centavos: 95.000 pesos são enviados como 9500000, e a única moeda disponível é COP.

DadoValorNota
Referênciask8-438k4-xmxm392-sn2mO seu identificador único do pagamento
Valor2490000$24.900 COP em centavos
MoedaCOPA única disponível hoje
Segredoprod_integrity_…Nunca no navegador
Textosk8-438k4-xmxm392-sn2m2490000COPprod_integrity_…Aplica-se SHA-256 em hexadecimal

A tabela reconstrói o exemplo da documentação: uma referência, um valor de 24.900 pesos (2490000 centavos) e o segredo de exemplo. O código a seguir produz exatamente o hash que a documentação publica para esse texto.

3. Verificar um evento (webhook)#

Para verificar um evento, a documentação indica recalcular o checksum assim: ler a lista signature.properties do próprio evento; buscar cada caminho dentro do campo data e concatenar os valores nessa ordem; acrescentar o timestamp do evento e, por último, o segredo de eventos; aplicar SHA-256 e comparar com o checksum recebido, sem distinguir maiúsculas de minúsculas. Se não coincidirem, ignore o evento.

A lista de propriedades pode variar, então não a escreva fixa no seu código: leia-a do evento, como fazem as funções abaixo. As funções comparam em tempo constante.

python
import hashlib
import hmac

def assinatura_integridade(referencia, valor_em_centavos, moeda, segredo, expiracao=""):
    """Assinatura do checkout: SHA-256 de referência + valor + moeda [+ expiração] + segredo."""
    texto = f"{referencia}{valor_em_centavos}{moeda}{expiracao}{segredo}"
    return hashlib.sha256(texto.encode()).hexdigest()

def valor_em(data, caminho):
    """Lê um caminho como 'transaction.status' dentro do campo data do evento."""
    atual = data
    for parte in caminho.split("."):
        atual = atual[parte]
    return str(atual)

def evento_autentico(evento, segredo_eventos):
    """Recalcula o checksum com as propriedades que o próprio evento declara."""
    firma = evento["signature"]
    valores = "".join(valor_em(evento["data"], p) for p in firma["properties"])
    texto = f"{valores}{evento['timestamp']}{segredo_eventos}"
    esperado = hashlib.sha256(texto.encode()).hexdigest()
    return hmac.compare_digest(esperado.lower(), firma["checksum"].lower())

# Exemplo da documentação da Wompi: $24.900 COP são 2490000 centavos
print(assinatura_integridade("sk8-438k4-xmxm392-sn2m", 2490000, "COP", "prod_integrity_Z5mMke9x0k8gpErbDqwrJXMqsI6SFli6"))

# Evento de teste com valores e segredo inventados
segredo = "test_events_segredo_de_exemplo"
evento = {
    "data": {"transaction": {"id": "T-0001", "status": "APPROVED", "amount_in_cents": 9500000}},
    "timestamp": 1760000000,
    "signature": {"properties": ["transaction.id", "transaction.status", "transaction.amount_in_cents"]},
}
texto = "T-0001" + "APPROVED" + "9500000" + "1760000000" + segredo
evento["signature"]["checksum"] = hashlib.sha256(texto.encode()).hexdigest().upper()
print(evento_autentico(evento, segredo), evento_autentico(evento, "outro_segredo"))
Python 3, só biblioteca padrão. A primeira linha é o hash do exemplo da documentação; a segunda, um evento de teste válido e o mesmo evento com outro segredo.
javascript
import { createHash, timingSafeEqual } from "node:crypto";

const sha256 = texto => createHash("sha256").update(texto).digest("hex");

function assinaturaIntegridade(referencia, valorEmCentavos, moeda, segredo, expiracao = "") {
  return sha256(`${referencia}${valorEmCentavos}${moeda}${expiracao}${segredo}`);
}

function valorEm(data, caminho) {
  return String(caminho.split(".").reduce((atual, parte) => atual[parte], data));
}

function eventoAutentico(evento, segredoEventos) {
  const { properties, checksum } = evento.signature;
  const valores = properties.map(p => valorEm(evento.data, p)).join("");
  const esperado = Buffer.from(sha256(`${valores}${evento.timestamp}${segredoEventos}`));
  const recebido = Buffer.from(checksum.toLowerCase());
  return esperado.length === recebido.length && timingSafeEqual(esperado, recebido);
}

console.log(assinaturaIntegridade("sk8-438k4-xmxm392-sn2m", 2490000, "COP", "prod_integrity_Z5mMke9x0k8gpErbDqwrJXMqsI6SFli6"));

const segredo = "test_events_segredo_de_exemplo";
const evento = {
  data: { transaction: { id: "T-0001", status: "APPROVED", amount_in_cents: 9500000 } },
  timestamp: 1760000000,
  signature: { properties: ["transaction.id", "transaction.status", "transaction.amount_in_cents"] },
};
evento.signature.checksum = sha256("T-0001" + "APPROVED" + "9500000" + "1760000000" + segredo).toUpperCase();
console.log(eventoAutentico(evento, segredo), eventoAutentico(evento, "outro_segredo"));
JavaScript (Node.js, módulo ES). Imprime o mesmo hash e true false.

Um detalhe importante: ao recalcular o exemplo de eventos da documentação com os valores que ela lista, não obtivemos o checksum que publica. Por isso o evento de teste deste guia usa valores e segredo inventados, e recomendamos validar a sua implementação com eventos reais do ambiente de testes (sandbox) antes de ir para produção.

Documentação: Python: hashlib ↗ · Node.js: crypto ↗

4. O que fazer depois de verificar#

  • Responda rápido ao webhook e faça o trabalho pesado depois, para não esgotar o tempo de espera de quem envia o evento.
  • Trate os eventos como idempotentes: guarde o id da transação e não aplique duas vezes o mesmo estado.
  • Compare o valor e a referência do evento com o seu pedido, não confie só no estado.
  • Considere rejeitar eventos com um timestamp muito antigo; é uma medida adicional sua, não um requisito da documentação.
  • Registre os eventos descartados sem guardar os segredos.

Para se aprofundarFilas, retries e idempotência: evite processar duas vezes

5. Erros frequentes#

  • Calcular a assinatura de integridade no navegador e expor o segredo.
  • Enviar o valor em pesos em vez de centavos.
  • Misturar o segredo de integridade com o de eventos.
  • Escrever a lista de propriedades do evento no código em vez de lê-la do evento.
  • Comparar o checksum com == em vez de uma comparação de tempo constante.

Para se aprofundarVariáveis de ambiente e DATABASE_URL em produção: guia completo

6. Mantenha seus números em dia#

A integração é descrita como estava a documentação da Wompi em 18 de outubro de 2026. Os campos, as moedas disponíveis ou o algoritmo podem mudar; revise a documentação oficial e teste no sandbox antes de implantar.

  • Revise a documentação da Wompi antes de cada mudança de integração.
  • Teste com transações de sandbox aprovadas, recusadas e anuladas.
  • Troque os segredos se suspeitar que vazaram.

Fontes e escopo

Documentação consultada em 18 de outubro de 2026. Os exemplos e critérios de decisão são propostas editoriais; adapte-os ao contrato da sua aplicação e valide-os no seu ambiente de testes autorizado.

Compare estas opções

Compare bancos de dados

Confira preços, limites, condições e fontes de cada opção.

Abrir comparador
Consultoria

Seu caso é diferente?

Conte seu contexto e as opções que você está avaliando. Respondemos com critérios e fontes, sem assinaturas automáticas.

Pedir consultoria