Webhook Signatures
BoomFi implements signed webhooks using asymmetric cryptography and timestamps to ensure the security and authenticity of webhook requests. By verifying these signatures, you can confirm that webhook requests are genuinely from BoomFi and protect against replay attacks.
Verifying Webhook Signatures
When BoomFi sends a webhook request, it includes a digital signature and a timestamp in the request headers. The signature is generated using BoomFi's private key, and you can verify it using the corresponding public key available in your Merchant Dashboard. This verification process ensures:
- Authenticity: Confirms that the webhook is from BoomFi.
- Integrity: Ensures the payload has not been tampered with.
- Protection against replay attacks: Validates the freshness of the request using the timestamp.
How It Works
- Signing: BoomFi signs the concatenation of the timestamp and the payload using its private key.
- Headers: The
X-BoomFi-Timestamp
andX-BoomFi-Signature
headers are added to the webhook request. - Verification: You use the public key to verify the signature against the received payload and timestamp.
Managing Your Webhook Public Key
You can view and manage your webhook signing public key in the BoomFi Merchant Dashboard. Click the rotate icon next to your public key to rotate your keypair.
Security
Store your public key securely and rotate it periodically or if you suspect any compromise.
Verification Steps
To verify the webhook signature, follow these steps:
-
Retrieve Headers:
X-BoomFi-Timestamp
: The UNIX timestamp when the webhook was sent.X-BoomFi-Signature
: The Base64-encoded digital signature.
-
Validate Timestamp: Check that the timestamp is recent (e.g., within the last 5 minutes). Discard the request if the timestamp is stale to prevent replay attacks.
-
Construct the Message: Concatenate the timestamp, a period (
.
), and the raw request body (as a string).- Format:
message = timestamp + "." + body
- Format:
-
Hash the Message: Compute the SHA-256 hash of the constructed message.
-
Verify the Signature: Decode the public key using base64 and verify the RSA signature of the hashed message matches the public key.
Code Examples
Below are Python, TypeScript, and Go code examples to help you implement the verification process.
import base64
import hashlib
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.exceptions import InvalidSignature
def verify_signature(public_key_pem: str, message: bytes, signature_base64: str) -> None:
try:
public_key = serialization.load_pem_public_key(public_key_pem.encode('utf-8'))
signature = base64.b64decode(signature_base64)
public_key.verify(
signature,
message,
padding.PKCS1v15(),
hashes.SHA256()
)
except InvalidSignature:
raise ValueError("Invalid signature")
except Exception as e:
raise ValueError(f"Verification failed: {e}")
import * as crypto from 'crypto';
function verifySignature(publicKeyPEM: string, message: Buffer, signatureBase64: string): void {
try {
const publicKey = crypto.createPublicKey(publicKeyPEM);
const signature = Buffer.from(signatureBase64, 'base64');
const isVerified = crypto.verify(
'sha256',
message,
publicKey,
signature
);
if (!isVerified) {
throw new Error("Invalid signature");
}
} catch (err) {
throw new Error(`Verification failed: ${err.message}`);
}
}
package main
import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
)
func VerifySignature(publicKeyPEM string, message []byte, signatureBase64 string) error {
block, _ := pem.Decode([]byte(publicKeyPEM))
if block == nil || block.Type != "PUBLIC KEY" {
return errors.New("failed to decode PEM block containing public key")
}
publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return fmt.Errorf("failed to parse public key: %w", err)
}
rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
if !ok {
return errors.New("not an RSA public key")
}
signature, err := base64.StdEncoding.DecodeString(signatureBase64)
if err != nil {
return fmt.Errorf("failed to decode signature: %w", err)
}
h := sha256.New()
h.Write(message)
hashedMessage := h.Sum(nil)
return rsa.VerifyPKCS1v15(rsaPublicKey, crypto.SHA256, hashedMessage, signature)
}
Updated 13 days ago