Saltar al contenido principal

Verificación de la firma del webhook

La notificación de pago llega a una dirección que solo conocen tu tienda y nuestro servicio. Pero la dirección puede filtrarse —desde los logs, la configuración o el historial de desarrollo. Para distinguir una notificación genuina de una falsificación, cada solicitud se firma con el secreto del webhook.

Verifica la firma antes de entregar el pedido o cambiar su estado.

Encabezados de la firma

EncabezadoValor
X-TimestampHora de envío, unix time en segundos
X-SignatureEl prefijo sha256= y el HMAC-SHA256 en hexadecimal

La cadena firmada es la hora de envío, un punto y el cuerpo de la solicitud:

1756901234.{"wallet":{...},"project":{...},"invoice":{...},"payment":{...}}

La clave de firma es el campo Secreto del webhook (Webhook secret) en la configuración del proyecto. Ahí puedes volver a emitirlo si el valor se ve comprometido.

Pasos de la verificación

  1. Toma el cuerpo de la solicitud sin procesar, antes de analizar el JSON.
  2. Concatena el valor de X-Timestamp, un punto y el cuerpo.
  3. Calcula el HMAC-SHA256 con el secreto del webhook.
  4. Compara el resultado con X-Signature usando una comparación en tiempo constante.
  5. Rechaza la solicitud si X-Timestamp se desvía demasiado de la hora actual.

Ejemplos de implementación

<?php
$secret = 'webhook secret from your project settings';

$body = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';

// Guards against a replayed request
if (abs(time() - (int)$timestamp) > 300) {
http_response_code(400);
exit;
}

$expected = 'sha256='.hash_hmac('sha256', $timestamp.'.'.$body, $secret);

// hash_equals compares in constant time
if (!hash_equals($expected, $signature)) {
http_response_code(403);
exit;
}

$data = json_decode($body, true);

// Handle the order

http_response_code(200);

A tener en cuenta

Se firma el cuerpo sin procesar. Si analizas el JSON y lo reconstruyes, el orden de las claves y los espacios cambian —la firma no coincidirá. Calcula el HMAC antes de analizar.

Cada reintento tiene su propia firma. La hora de envío es nueva en cada reintento, así que la firma también difiere. No guardes una firma para compararla con solicitudes futuras.

El secreto puede volver a emitirse. El secreto antiguo deja de funcionar inmediatamente después de la reemisión, así que actualiza el valor en tu tienda al mismo tiempo.

La verificación de la firma no sustituye la idempotencia. La firma demuestra que la solicitud es auténtica, no que la veas por primera vez. Un reintento llega con una firma válida —comprueba contra invoice.id.