GO
El ejemplo en Go se divide en dos partes: el manejador de la petición HTTP que recibe la notificación y la función de validación criptográfica.
1. Recepción de la Notificación (Framework Gin):
Este código muestra cómo configurar un endpoint en su servidor usando el popular framework Gin para recibir la notificación de SyPago, extraer los datos necesarios y llamar a la función de validación.
package main
import (
"errors"
"fmt"
"io"
"net/http"
"github.com/gin-gonic/gin"
)
// WebhookHandler procesa las notificaciones de SyPago
func WebhookHandler(c *gin.Context) {
// 1. Extraer los componentes de la notificación
signature := c.Request.Header.Get("X-Signature")
if signature == "" {
c.AbortWithError(http.StatusBadRequest, errors.New("cabecera X-Signature es requerida"))
return
}
nonce := c.Request.Header.Get("X-Signature-Nonce")
if nonce == "" {
c.AbortWithError(http.StatusBadRequest, errors.New("cabecera X-Signature-Nonce es requerida"))
return
}
payload, err := io.ReadAll(c.Request.Body)
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
// 2. Validar la firma
err = ValidateSyPagoSignature(signature, payload, nonce)
if err != nil {
// La firma no es válida. Descartar la petición.
fmt.Printf("Error al validar la firma: %v\n", err)
c.AbortWithError(http.StatusUnauthorized, err)
return
}
// 3. La firma es válida. Procesar la notificación.
fmt.Println("Firma válida. Procesando notificación...")
// Aquí iría la lógica para procesar el payload de la notificación.
// Por ejemplo, actualizar el estado de una orden en la base de datos.
c.JSON(http.StatusOK, gin.H{"message": "Firma válida y notificación recibida"})
}
2. Validación de la Firma
Esta función contiene la lógica criptográfica principal para verificar la firma. Toma los datos extraídos de la notificación y los valida contra su clave pública y el secreto de la operación.
package main
import (
"crypto/ecdsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
)
// ValidateSyPagoSignature verifica la firma de una notificación de SyPago.
func ValidateSyPagoSignature(signatureB64 string, payload []byte, nonce string) error {
// --- 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`.
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 para no solicitarla en cada notificación.
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)
stringToVerify := fmt.Sprintf("%s.%s.%s", string(payload), nonce, operationSecret)
dataToVerify := []byte(stringToVerify)
// 2. Calcular el hash SHA-256 del mensaje
hash := sha256.Sum256(dataToVerify)
// 3. Decodificar la firma de Base64
signatureBytes, err := base64.StdEncoding.DecodeString(signatureB64)
if err != nil {
return fmt.Errorf("error decodificando la firma desde base64: %v", err)
}
// 4. Parsear la clave pública en formato PEM
block, _ := pem.Decode([]byte(publicKeyPEM))
if block == nil {
return fmt.Errorf("error decodificando el bloque PEM de la clave pública")
}
genericPublicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return fmt.Errorf("error parseando la clave pública: %v", err)
}
// 5. Convertir a una clave pública ECDSA
publicKey, ok := genericPublicKey.(*ecdsa.PublicKey)
if !ok {
return fmt.Errorf("la clave pública no es de tipo ECDSA")
}
// 6. Verificar la firma usando el estándar ASN.1 (formato RFC3279 DER Sequence)
isSignatureValid := ecdsa.VerifyASN1(publicKey, hash[:], signatureBytes)
if !isSignatureValid {
return fmt.Errorf("la firma no es válida")
}
// La firma es válida
return nil
}