JavaScript (Node.js)
El ejemplo en Node.js utiliza el framework Express para el servidor y el módulo nativo crypto para la validación.
1. Recepción de la Notificación (Express)
Es crucial usar un middleware como express.raw para asegurarse de que el cuerpo de la solicitud se lee como un buffer sin ser modificado.
const express = require('express');
const { validateSyPagoSignature } = require('./signature-validator');
const app = express();
// Middleware para leer el body como un buffer crudo para las rutas de webhook
app.use('/webhook-sypago', express.raw({ type: 'application/json' }));
app.post('/webhook-sypago', async (req, res) => {
try {
// 1. Extraer los componentes de la notificación
const signature = req.headers['x-signature'];
if (!signature) {
return res.status(400).send('Cabecera X-Signature es requerida');
}
const nonce = req.headers['x-signature-nonce'];
if (!nonce) {
return res.status(400).send('Cabecera X-Signature-Nonce es requerida');
}
// El payload es el buffer crudo del body (req.body)
const payload = req.body;
// 2. Validar la firma
await validateSyPagoSignature(signature, payload, nonce);
// 3. La firma es válida. Procesar la notificación.
console.log("Firma válida. Procesando notificación...");
// Aquí iría la lógica para procesar el payload.
// const notificationData = JSON.parse(payload.toString('utf8'));
res.status(200).json({ message: 'Firma válida y notificación recibida' });
} catch (error) {
// La firma no es válida. Descartar la petición.
console.error(`Error al validar la firma: ${error.message}`);
res.status(401).send(error.message);
}
});
// ... iniciar el servidor
// const PORT = 3000;
// app.listen(PORT, () => console.log(`Servidor escuchando en el puerto ${PORT}`));
2. Validación de la Firma
Este módulo exporta la función de validación.
const crypto = require('crypto');
async function validateSyPagoSignature(signatureB64, payloadBuffer, 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`.
const 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.
const 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 (payload.nonce.operationSecret)
// Es importante usar el buffer del payload directamente
const stringToVerify = Buffer.concat([
payloadBuffer,
Buffer.from(`.${nonce}.${operationSecret}`)
]);
// 2. Crear un objeto de verificación con el algoritmo SHA-256
const verify = crypto.createVerify('SHA256');
// 3. Cargar el mensaje en el objeto
verify.update(stringToVerify);
verify.end();
// 4. Verificar la firma usando la clave pública y la firma en Base64
// El formato de la firma es ASN.1 DER, que es el estándar que `crypto` espera.
const isSignatureValid = verify.verify(publicKeyPEM, signatureB64, 'base64');
if (!isSignatureValid) {
throw new Error('La firma no es válida');
}
// La firma es válida
return true;
}
module.exports = { validateSyPagoSignature };