Java
El ejemplo en Java utiliza el framework Spring Boot para el endpoint y las clases nativas de java.security para la validación.
1. Recepción de la Notificación (Spring Boot Controller)
package com.example.sypagovalidator;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class WebhookController {
private final SignatureValidationService validationService;
public WebhookController(SignatureValidationService validationService) {
this.validationService = validationService;
}
@PostMapping("/webhook-sypago")
public ResponseEntity<Map<String, String>> handleWebhook(
@RequestBody String payload,
@RequestHeader("X-Signature") String signature,
@RequestHeader("X-Signature-Nonce") String nonce) {
try {
// 2. Validar la firma
validationService.validateSyPagoSignature(signature, payload, nonce);
// 3. La firma es válida. Procesar la notificación.
System.out.println("Firma válida. Procesando notificación...");
// Aquí iría la lógica para procesar el payload.
return ResponseEntity.ok(Map.of("message", "Firma válida y notificación recibida"));
} catch (Exception e) {
// La firma no es válida. Descartar la petición.
System.err.println("Error al validar la firma: " + e.getMessage());
return ResponseEntity.status(401).build();
}
}
}
2. Validación de la Firma
package com.example.sypagovalidator;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
@Service
public class SignatureValidationService {
public void validateSyPagoSignature(String signatureB64, String payload, String nonce) throws Exception {
// --- Obtención de Secretos ---
// El `operationSecret` DEBE ser recuperado de su base de datos.
// Lo guardó cuando inició la transacción con SyPago.
String operationSecret = "9f4aaf08-8d04-4007-a097-c0e95eddad5e"; // ¡EJEMPLO! Reemplazar.
// 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-----\n" +
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9hXXl4g886loHmI10dLrJWFHEB8r\n" +
"cyqD1hBdIM3ekQkb5YTpOShAu+7xk0fyL/0IBjLEKwSd7rsKrYGtIgJj0w==\n" +
"-----END PUBLIC KEY-----"; // ¡EJEMPLO! Reemplazar.
// --- Proceso de Verificación ---
// 1. Reconstruir el mensaje
String stringToVerify = payload + "." + nonce + "." + operationSecret;
byte[] dataToVerify = stringToVerify.getBytes(StandardCharsets.UTF_8);
// 2. Limpiar y decodificar la clave pública PEM
String publicKeyBase64 = publicKeyPEM
.replace("-----BEGIN PUBLIC KEY-----", "")
.replaceAll("\\s+", "") // Usar "\\s+" para remover todos los espacios en blanco, incluyendo saltos de línea
.replace("-----END PUBLIC KEY-----", "");
byte[] keyBytes = Base64.getDecoder().decode(publicKeyBase64);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("EC");
PublicKey publicKey = kf.generatePublic(spec);
// 3. Decodificar la firma de Base64
byte[] signatureBytes = Base64.getDecoder().decode(signatureB64);
// 4. Inicializar el objeto Signature y verificar
// "SHA256withECDSA" es el algoritmo estándar que manejará el hash y la verificación ASN.1
Signature sig = Signature.getInstance("SHA256withECDSA");
sig.initVerify(publicKey);
sig.update(dataToVerify);
boolean isSignatureValid = sig.verify(signatureBytes);
if (!isSignatureValid) {
throw new SecurityException("La firma no es válida");
}
// La firma es válida
}
}