B4J Question I need help calculating correct X-Webhook-Signature

Gabino A. de la Gala

Well-Known Member
Licensed User
Longtime User
I'm trying to verify the signature received from a webhook, but I can't.

I've asked chatgpt for help, but I can't get it to compile what you suggested.

I'll paste it here anyway.
Thanks.

B4X:
For added security, you can define a secret value when creating a webhook. This secret is used to generate a digital signature (HMAC with the SHA-256 algorithm) that is included in each notification, within the X-Webhook-Signature HTTP header.

This signature allows the webhook recipient to verify that:

The message was sent through our API
The content was not modified during transmission

To verify the signature:

You must obtain the exact body of the received message (unaltered).
Use the same secret associated with the webhook to calculate an HMAC-SHA256 signature (in hexadecimal).
Compare the generated signature with the one contained in the X-Webhook-Signature header.

If both signatures match, you can trust that the message is authentic.


B4X:
Sub ValidateWebhookSignature(body As String, headerSignature As String, secret As String)
    Dim mac As Mac
    mac.Initialise("HMACSHA256", secret.GetBytes("UTF8"))

    Dim data() As Byte = body.GetBytes("UTF8")
    Dim computed() As Byte = bc.(mac.Sign(data)

    Dim bc As ByteConverter
    Dim computedHex As String = bc.HexFromBytes(computed).ToLowerCase

    If computedHex = headerSignature.ToLowerCase Then
        Log("✅ Firma válida")
    Else
        Log("❌ Firma inválida")
    End If
End Sub
 

TILogistic

Expert
Licensed User
Longtime User
see: (ver)

note:
remove the "sha256=" from the body to compare
 
Upvote 0

TILogistic

Expert
Licensed User
Longtime User
OTHER B4X.
B4X:
' Reemplaza "NombreDelModulo" con el nombre de tu módulo
Sub Process_Globals
    Private BC As ByteConverter ' Necesita la librería ByteConverter
End Sub

' Inicializa ByteConverter en Sub Globals o un lugar apropiado
' BC.Initialize

' Función para calcular el HMAC-SHA256 (necesario para la verificación)
' Retorna la firma calculada en formato Hexadecimal (ajustar si es Base64)
Private Sub CalculateHmacSha256(Message As String, Key As String) As String
    ' Se requiere la librería Encryption (o acceso a java.security.*)

    Dim kg As KeyGenerator
    Dim m As Mac
   
    ' 1. Inicializar KeyGenerator con el algoritmo HMACSHA256
    ' El secreto debe convertirse a bytes (generalmente UTF8)
    kg.Initialize("HmacSHA256")
    kg.KeyFromBytes(Key.GetBytes("UTF8"))
   
    ' 2. Inicializar Mac con la clave generada
    m.Initialise("HmacSHA256", kg.Key)
   
    ' 3. Calcular el hash del mensaje (cuerpo RAW del webhook)
    ' El mensaje debe convertirse a bytes (generalmente UTF8)
    m.Update(Message.GetBytes("UTF8"))
   
    ' 4. Obtener la firma como array de bytes
    Dim SignatureBytes() As Byte = m.Sign
   
    ' 5. Convertir el array de bytes a una cadena (Hexadecimal es común)
    Return BC.HexFromBytes(SignatureBytes).ToLowerCase
End Sub

' Función principal de validación
' SignatureHeader: El valor completo del encabezado X-Webhook-Signature
' Payload: El cuerpo RAW de la solicitud HTTP (sin parsing previo)
' Secret: La clave secreta de firma proporcionada por el servicio
Public Sub ValidateWebhookSignature(SignatureHeader As String, Payload As String, Secret As String) As Boolean
   
    ' 1. Extraer la firma real del encabezado
    ' El formato del encabezado puede variar (ej: t=timestamp,v1=signature)
    ' **AJUSTA ESTA PARTE según el formato EXACTO de tu proveedor**
   
    Dim ExpectedSignature As String ' La firma extraída del encabezado
   
    ' Asumiremos por simplicidad un formato simple "X-Webhook-Signature: <firma>"
    ' Si el formato es complejo (ej: Stripe, GitHub), esta lógica debe extraer
    ' la parte de la firma y el timestamp, y el payload a firmar puede incluir el timestamp.
   
    ExpectedSignature = SignatureHeader ' Si es solo la firma en el encabezado
   
    ' --- EJEMPLO para un encabezado con prefijo 'sha256=' (común) ---
    If SignatureHeader.StartsWith("sha256=") Then
        ExpectedSignature = SignatureHeader.SubString(7) ' Elimina el prefijo "sha256="
    Else If SignatureHeader.StartsWith("v1=") Then
        ' Asume que la firma sin prefijo es la que hay que comparar
        ' **REVISA TU DOCUMENTACIÓN**
        ExpectedSignature = SignatureHeader.SubString(3) ' Elimina el prefijo "v1="
    End If
    ' -------------------------------------------------------------------
   
    ' 2. Calcular la firma local
    Dim CalculatedSignature As String
   
    ' **IMPORTANTE**:
    ' Algunos servicios (ej: Stripe) firman: timestamp + "." + payload.
    ' Otros solo firman el payload RAW.
    ' Debes usar EXACTAMENTE el string que tu proveedor de webhooks firma.
   
    ' Si solo se firma el payload:
    CalculatedSignature = CalculateHmacSha256(Payload, Secret)
   
    ' Si se firma timestamp + "." + payload (ajusta la extracción de timestamp en el paso 1)
    ' Dim TimeStamp As String ' (extraído del encabezado)
    ' CalculatedSignature = CalculateHmacSha256(TimeStamp & "." & Payload, Secret)
   
    ' 3. Comparar las firmas
    ' Se usa String.EqualsIgnoreCase para ser más flexible, pero la comparación de Hex debe ser exacta.
    ' Se recomienda una comparación a tiempo constante para prevenir ataques de tiempo (aunque no es trivial en B4A).
    Return CalculatedSignature.EqualsIgnoreCase(ExpectedSignature)
   
End Sub

' Ejemplo de uso:
' Dim IsValid As Boolean = ValidateWebhookSignature(ReceivedSignatureHeader, RawRequestBody, "your_webhook_secret_key")
' If IsValid Then
'    Log("Firma Válida")
' Else
'    Log("Firma Inválida")
' End If
 
Upvote 0

Gabino A. de la Gala

Well-Known Member
Licensed User
Longtime User
OTHER B4X.
B4X:
' Reemplaza "NombreDelModulo" con el nombre de tu módulo
Sub Process_Globals
    Private BC As ByteConverter ' Necesita la librería ByteConverter
End Sub

' Inicializa ByteConverter en Sub Globals o un lugar apropiado
' BC.Initialize

' Función para calcular el HMAC-SHA256 (necesario para la verificación)
' Retorna la firma calculada en formato Hexadecimal (ajustar si es Base64)
Private Sub CalculateHmacSha256(Message As String, Key As String) As String
    ' Se requiere la librería Encryption (o acceso a java.security.*)

    Dim kg As KeyGenerator
    Dim m As Mac
  
    ' 1. Inicializar KeyGenerator con el algoritmo HMACSHA256
    ' El secreto debe convertirse a bytes (generalmente UTF8)
    kg.Initialize("HmacSHA256")
    kg.KeyFromBytes(Key.GetBytes("UTF8"))
  
    ' 2. Inicializar Mac con la clave generada
    m.Initialise("HmacSHA256", kg.Key)
  
    ' 3. Calcular el hash del mensaje (cuerpo RAW del webhook)
    ' El mensaje debe convertirse a bytes (generalmente UTF8)
    m.Update(Message.GetBytes("UTF8"))
  
    ' 4. Obtener la firma como array de bytes
    Dim SignatureBytes() As Byte = m.Sign
  
    ' 5. Convertir el array de bytes a una cadena (Hexadecimal es común)
    Return BC.HexFromBytes(SignatureBytes).ToLowerCase
End Sub

' Función principal de validación
' SignatureHeader: El valor completo del encabezado X-Webhook-Signature
' Payload: El cuerpo RAW de la solicitud HTTP (sin parsing previo)
' Secret: La clave secreta de firma proporcionada por el servicio
Public Sub ValidateWebhookSignature(SignatureHeader As String, Payload As String, Secret As String) As Boolean
  
    ' 1. Extraer la firma real del encabezado
    ' El formato del encabezado puede variar (ej: t=timestamp,v1=signature)
    ' **AJUSTA ESTA PARTE según el formato EXACTO de tu proveedor**
  
    Dim ExpectedSignature As String ' La firma extraída del encabezado
  
    ' Asumiremos por simplicidad un formato simple "X-Webhook-Signature: <firma>"
    ' Si el formato es complejo (ej: Stripe, GitHub), esta lógica debe extraer
    ' la parte de la firma y el timestamp, y el payload a firmar puede incluir el timestamp.
  
    ExpectedSignature = SignatureHeader ' Si es solo la firma en el encabezado
  
    ' --- EJEMPLO para un encabezado con prefijo 'sha256=' (común) ---
    If SignatureHeader.StartsWith("sha256=") Then
        ExpectedSignature = SignatureHeader.SubString(7) ' Elimina el prefijo "sha256="
    Else If SignatureHeader.StartsWith("v1=") Then
        ' Asume que la firma sin prefijo es la que hay que comparar
        ' **REVISA TU DOCUMENTACIÓN**
        ExpectedSignature = SignatureHeader.SubString(3) ' Elimina el prefijo "v1="
    End If
    ' -------------------------------------------------------------------
  
    ' 2. Calcular la firma local
    Dim CalculatedSignature As String
  
    ' **IMPORTANTE**:
    ' Algunos servicios (ej: Stripe) firman: timestamp + "." + payload.
    ' Otros solo firman el payload RAW.
    ' Debes usar EXACTAMENTE el string que tu proveedor de webhooks firma.
  
    ' Si solo se firma el payload:
    CalculatedSignature = CalculateHmacSha256(Payload, Secret)
  
    ' Si se firma timestamp + "." + payload (ajusta la extracción de timestamp en el paso 1)
    ' Dim TimeStamp As String ' (extraído del encabezado)
    ' CalculatedSignature = CalculateHmacSha256(TimeStamp & "." & Payload, Secret)
  
    ' 3. Comparar las firmas
    ' Se usa String.EqualsIgnoreCase para ser más flexible, pero la comparación de Hex debe ser exacta.
    ' Se recomienda una comparación a tiempo constante para prevenir ataques de tiempo (aunque no es trivial en B4A).
    Return CalculatedSignature.EqualsIgnoreCase(ExpectedSignature)
  
End Sub

' Ejemplo de uso:
' Dim IsValid As Boolean = ValidateWebhookSignature(ReceivedSignatureHeader, RawRequestBody, "your_webhook_secret_key")
' If IsValid Then
'    Log("Firma Válida")
' Else
'    Log("Firma Inválida")
' End If
I'll try it tomorrow.
Thanks.
 
Upvote 0
Top