Con Wompi firmas el pago en tu servidor con un SHA-256 de referencia, monto en centavos, moneda y tu secreto de integridad, y verificas cada evento recalculando un SHA-256 con las propiedades que el evento declara, su timestamp y tu secreto de eventos.
1. Dos firmas distintas, dos secretos distintos#
Integrar Wompi en un sitio implica dos comprobaciones que se confunden con facilidad. La firma de integridad protege el pago que tu servidor le entrega al checkout: evita que alguien cambie el monto en el navegador. La verificación del evento protege tu servidor: comprueba que la notificación de un cambio de estado viene de Wompi y no de un tercero.
Cada una usa un secreto distinto, que no es ni la llave pública ni la privada. Según la documentación de Wompi, ambos están en el panel de comercios, en la sección de secretos para integración técnica, y no deben salir de tu servidor.
Documentación: Wompi: Widget y Checkout Web (firma de integridad) ↗ · Wompi: Eventos (verificación del checksum) ↗
2. La firma de integridad del checkout#
La documentación indica que la firma es el SHA-256, en hexadecimal, de la concatenación de la referencia, el monto en centavos, la moneda y el secreto de integridad. Si el pago tiene fecha de expiración, esa fecha va entre la moneda y el secreto. El monto va en centavos: 95.000 pesos se envían como 9500000, y la única moneda disponible es COP.
| Dato | Valor | Nota |
|---|---|---|
| Referencia | sk8-438k4-xmxm392-sn2m | Tu identificador único del pago |
| Monto | 2490000 | $24.900 COP en centavos |
| Moneda | COP | La única disponible hoy |
| Secreto | prod_integrity_… | Nunca en el navegador |
| Cadena | sk8-438k4-xmxm392-sn2m2490000COPprod_integrity_… | Se le aplica SHA-256 en hexadecimal |
La tabla reconstruye el ejemplo de la documentación: una referencia, un monto de 24.900 pesos (2490000 centavos) y el secreto de ejemplo. El código siguiente produce exactamente el hash que la documentación publica para esa cadena.
3. Verificar un evento (webhook)#
Para comprobar un evento, la documentación indica recalcular el checksum así: leer la lista signature.properties del propio evento; buscar cada ruta dentro del campo data y concatenar los valores en ese orden; añadir el timestamp del evento y, al final, el secreto de eventos; aplicar SHA-256 y comparar con el checksum recibido, sin distinguir mayúsculas de minúsculas. Si no coinciden, ignora el evento.
La lista de propiedades puede variar, así que no la escribas fija en tu código: léela del evento, como hacen las funciones de abajo. Las funciones comparan en tiempo constante.
import hashlib
import hmac
def firma_integridad(referencia, monto_en_centavos, moneda, secreto, expiracion=""):
"""Firma del checkout: SHA-256 de referencia + monto + moneda [+ expiración] + secreto."""
cadena = f"{referencia}{monto_en_centavos}{moneda}{expiracion}{secreto}"
return hashlib.sha256(cadena.encode()).hexdigest()
def valor_en(data, ruta):
"""Lee una ruta como 'transaction.status' dentro del campo data del evento."""
actual = data
for parte in ruta.split("."):
actual = actual[parte]
return str(actual)
def evento_autentico(evento, secreto_eventos):
"""Recalcula el checksum con las propiedades que declara el propio evento."""
firma = evento["signature"]
valores = "".join(valor_en(evento["data"], p) for p in firma["properties"])
cadena = f"{valores}{evento['timestamp']}{secreto_eventos}"
esperado = hashlib.sha256(cadena.encode()).hexdigest()
return hmac.compare_digest(esperado.lower(), firma["checksum"].lower())
# Ejemplo de la documentación de Wompi: $24.900 COP son 2490000 centavos
print(firma_integridad("sk8-438k4-xmxm392-sn2m", 2490000, "COP", "prod_integrity_Z5mMke9x0k8gpErbDqwrJXMqsI6SFli6"))
# Evento de prueba con valores y secreto inventados
secreto = "test_events_secreto_de_ejemplo"
evento = {
"data": {"transaction": {"id": "T-0001", "status": "APPROVED", "amount_in_cents": 9500000}},
"timestamp": 1760000000,
"signature": {"properties": ["transaction.id", "transaction.status", "transaction.amount_in_cents"]},
}
cadena = "T-0001" + "APPROVED" + "9500000" + "1760000000" + secreto
evento["signature"]["checksum"] = hashlib.sha256(cadena.encode()).hexdigest().upper()
print(evento_autentico(evento, secreto), evento_autentico(evento, "otro_secreto"))import { createHash, timingSafeEqual } from "node:crypto";
const sha256 = texto => createHash("sha256").update(texto).digest("hex");
function firmaIntegridad(referencia, montoEnCentavos, moneda, secreto, expiracion = "") {
return sha256(`${referencia}${montoEnCentavos}${moneda}${expiracion}${secreto}`);
}
function valorEn(data, ruta) {
return String(ruta.split(".").reduce((actual, parte) => actual[parte], data));
}
function eventoAutentico(evento, secretoEventos) {
const { properties, checksum } = evento.signature;
const valores = properties.map(p => valorEn(evento.data, p)).join("");
const esperado = Buffer.from(sha256(`${valores}${evento.timestamp}${secretoEventos}`));
const recibido = Buffer.from(checksum.toLowerCase());
return esperado.length === recibido.length && timingSafeEqual(esperado, recibido);
}
console.log(firmaIntegridad("sk8-438k4-xmxm392-sn2m", 2490000, "COP", "prod_integrity_Z5mMke9x0k8gpErbDqwrJXMqsI6SFli6"));
const secreto = "test_events_secreto_de_ejemplo";
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" + secreto).toUpperCase();
console.log(eventoAutentico(evento, secreto), eventoAutentico(evento, "otro_secreto"));Un detalle importante: al recalcular el ejemplo de eventos de la documentación con los valores que lista, no obtuvimos el checksum que publica. Por eso el evento de prueba de esta guía usa valores y secreto inventados, y te recomendamos validar tu implementación con eventos reales del entorno de pruebas (sandbox) antes de pasar a producción.
Documentación: Python: hashlib ↗ · Node.js: crypto ↗
4. Qué hacer después de verificar#
- Responde rápido al webhook y haz el trabajo pesado después, para no agotar el tiempo de espera de quien envía el evento.
- Trata los eventos como idempotentes: guarda el id de la transacción y no apliques dos veces un mismo estado.
- Compara el monto y la referencia del evento con tu pedido, no confíes solo en el estado.
- Considera rechazar eventos con un timestamp muy antiguo; es una medida adicional tuya, no un requisito de la documentación.
- Registra los eventos descartados sin guardar los secretos.
Para profundizarColas, reintentos e idempotencia: evita procesar dos veces
5. Errores frecuentes#
- Calcular la firma de integridad en el navegador y exponer el secreto.
- Enviar el monto en pesos en vez de centavos.
- Mezclar el secreto de integridad con el de eventos.
- Escribir la lista de propiedades del evento en el código en lugar de leerla del evento.
- Comparar el checksum con == en vez de una comparación de tiempo constante.
Para profundizarVariables de entorno y DATABASE_URL en producción: guía completa
6. Mantén tus cifras al día#
La integración se describe como estaba la documentación de Wompi el 18 de octubre de 2026. Pueden cambiar los campos, las monedas disponibles o el algoritmo; revisa la documentación oficial y prueba en sandbox antes de desplegar.
- Revisa la documentación de Wompi antes de cada cambio de integración.
- Prueba con transacciones de sandbox aprobadas, rechazadas y anuladas.
- Rota los secretos si sospechas que se filtraron.
Fuentes y alcance
Documentación consultada el 18 de octubre de 2026. Los ejemplos y criterios de decisión son propuestas editoriales; adáptalos al contrato de tu aplicación y valídalos en tu entorno de pruebas autorizado.