C# (.NET)
El ejemplo en C# se divide en dos partes: un endpoint de API en ASP.NET Core que recibe la notificación, y una clase de servicio para la validación criptográfica.
1. Recepción de la Notificación (ASP.NET Core Minimal API):
Este código muestra cómo configurar un endpoint para recibir la notificación de SyPago. Es importante configurar la lectura del cuerpo de la solicitud como un string o byte[] crudo para no alterar su contenido.
// En Program.cs o donde configures tus endpoints
using Microsoft.AspNetCore.Mvc;
using System.Security.Cryptography;
using System.Text;
// ...
app.MapPost("/webhook-sypago", async (HttpRequest request, [FromServices] ISignatureValidationService validationService) =>
{
// 1. Extraer los componentes de la notificación
if (!request.Headers.TryGetValue("X-Signature", out var signature))
{
return Results.BadRequest("Cabecera X-Signature es requerida");
}
if (!request.Headers.TryGetValue("X-Signature-Nonce", out var nonce))
{
return Results.BadRequest("Cabecera X-Signature-Nonce es requerida");
}
string payload;
using (var reader = new StreamReader(request.Body, Encoding.UTF8))
{
payload = await reader.ReadToEndAsync();
}
// 2. Validar la firma
try
{
await validationService.ValidateSyPagoSignatureAsync(signature.First()!, payload, nonce.First()!);
}
catch (Exception ex)
{
// La firma no es válida. Descartar la petición.
Console.WriteLine($"Error al validar la firma: {ex.Message}");
return Results.Unauthorized();
}
// 3. La firma es válida. Procesar la notificación.
Console.WriteLine("Firma válida. Procesando notificación...");
// Aquí iría la lógica para procesar el payload de la notificación.
// Por ejemplo, deserializar el JSON del payload y actualizar la base de datos.
return Results.Ok(new { message = "Firma válida y notificación recibida" });
});
2. Validación de la Firma
Esta clase de servicio contiene la lógica de validación.
using System.Security.Cryptography;
using System.Text;
public interface ISignatureValidationService
{
Task ValidateSyPagoSignatureAsync(string signatureB64, string payload, string nonce);
}
public class SignatureValidationService : ISignatureValidationService
{
public async Task ValidateSyPagoSignatureAsync(string signatureB64, string payload, string nonce)
{
// --- Obtención de Secretos ---
// El `operationSecret` DEBE ser recuperado de su base de datos.
// Lo guardó cuando inició la transacción con SyPago y recibió el `transaction_id`.
string operationSecret = "9f4aaf08-8d04-4007-a097-c0e95eddad5e"; // ¡EJEMPLO! Reemplazar con su lógica de obtención.
// La `publicKeyPEM` DEBE ser obtenida del endpoint GET /api/v1/user/key
// usando su token JWT. Es recomendable cachear esta clave.
string publicKeyPEM = @"-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9hXXl4g886loHmI10dLrJWFHEB8r
cyqD1hBdIM3ekQkb5YTpOShAu+7xk0fyL/0IBjLEKwSd7rsKrYGtIgJj0w==
-----END PUBLIC KEY-----"; // ¡EJEMPLO! Reemplazar con la clave real obtenida.
// --- Proceso de Verificación ---
// 1. Reconstruir el mensaje a verificar
string stringToVerify = $"{payload}.{nonce}.{operationSecret}";
byte[] dataToVerify = Encoding.UTF8.GetBytes(stringToVerify);
// 2. Calcular el hash SHA-256 del mensaje
byte[] hash = SHA256.HashData(dataToVerify);
// 3. Decodificar la firma de Base64
byte[] signatureBytes = Convert.FromBase64String(signatureB64);
// 4. Cargar la clave pública y verificar la firma
using (var ecdsa = ECDsa.Create())
{
ecdsa.ImportFromPem(publicKeyPEM);
// 5. Verificar la firma del hash usando el formato ASN.1 DER
// DSASignatureFormat.Rfc3279DerSequence es el formato que usa SyPago y es el default para VerifyHash
bool isSignatureValid = ecdsa.VerifyHash(hash, signatureBytes, DSASignatureFormat.Rfc3279DerSequence);
if (!isSignatureValid)
{
throw new CryptographicException("La firma no es válida");
}
}
// La firma es válida
}
}