Webhooks
Validar la firma

Validar la firma

Para garantizar que tu servidor solo procese notificaciones enviadas por Propago, y que no hayan sido manipuladas en el camino, validá la firma antes de procesar el contenido.

Propago usa el secretKey que indicaste al suscribirte para generar una firma distinta en cada solicitud. Vos aplicás la misma función de hash sobre lo que recibiste y verificás que coincida.

Encabezados

Cada notificación llega con estos dos encabezados:

EncabezadoDescripción
X-Signature-V1La firma: HMAC-SHA256 en hexadecimal minúscula.
X-Signature-TimestampMomento de la solicitud, en Unix Time (segundos). Entra en el cálculo de la firma.

Cómo se calcula

  1. Concatená el timestamp, un punto, y el cuerpo de la solicitud tal como llegó:

    {X-Signature-Timestamp}.{payload}

    El payload es el JSON en formato string, en UTF-8.

  2. Tomá el secretKey que guardaste en tu servidor al suscribirte.

  3. Aplicá HMAC con SHA-256 sobre esa cadena, usando el secretKey como clave.

  4. Compará el resultado con el valor del encabezado X-Signature-V1. Si no coinciden, descartá la notificación.

Ejemplo de implementación

Podés usar el lenguaje que prefieras; lo único que importa es que el mensaje, la clave y la codificación sean los mismos.

CryptographyUtils.cs
using System;
using System.Security.Cryptography;
using System.Text;
 
public static class CryptographyUtils
{
    public static string GenerateHMACSHA256(long timestamp, string payload, string secretKey)
    {
        return GenerateHMACSHA256($"{timestamp}.{payload}", secretKey);
    }
 
    public static string GenerateHMACSHA256(string message, string secretKey)
    {
        byte[] keyBytes = Encoding.UTF8.GetBytes(secretKey);
        byte[] msgBytes = Encoding.UTF8.GetBytes(message);
 
        using var hmac = new HMACSHA256(keyBytes);
        byte[] hashBytes = hmac.ComputeHash(msgBytes);
 
        return FormatBytes(hashBytes);
    }
 
    private static string FormatBytes(byte[] bytes)
    {
        StringBuilder builder = new StringBuilder();
        foreach (byte b in bytes)
        {
            builder.Append(b.ToString("x2"));
        }
        return builder.ToString();
    }
}

Rechazar solicitudes viejas

⚠️

Validá también el timestamp, no solo la firma. Si no lo hacés, quedás expuesto a un ataque de repetición: alguien intercepta una notificación válida con su firma y la retransmite más tarde. La firma sigue siendo correcta, porque el contenido no cambió.

El timestamp está incluido en el cálculo del hash, así que un atacante no puede alterarlo sin invalidar la firma. Por eso alcanza con que compares: si la firma es válida pero el timestamp es demasiado viejo para tu tolerancia, rechazá la solicitud.

Recomendaciones

  • Firmá sobre el cuerpo crudo. Verificá que nada modifique el payload ni los encabezados antes de la validación. Si tenés un proxy o un balanceador de carga adelante, asegurate de que no reescriban el cuerpo — cualquier cambio, incluso reordenar o reformatear el JSON, invalida la firma.
  • Manejá el payload como UTF-8. Si tu lenguaje o tu servidor permiten especificar la codificación, fijala explícitamente antes de generar el hash.
  • Compará en tiempo constante si tu lenguaje ofrece una función para eso, en vez de comparar los strings con ==.