Verifying Signatures
Swivell includes a X-Webhook-Signature
header on each webhook call containing a hex-encoded HMAC-SHA256 signature of the POST body. To verify the authenticity of the request:
- Compute a local copy of the signature using the
signing_key
returned when creating the webhook. - Compare the local signature to
X-Webhook-Signature
; if they match, the request is authenticated.
Please note that an authenticating payload does not prevent replay attacks. To make sure an event is processed once, please keep track of the event id
in the payload and discard subsequent calls with the same ID. Another approach is to discard events with a time
older than 2 minutes.
HMAC-SHA256 Signature Verification
Below are some code examples for verify HMAC-SHA256 signatures in your webhook endpoints.
package main
import (
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
)
func verifyHmacSignature(payload, signature, signingKey string) bool {
// Decode hex secret
secretBytes, err := hex.DecodeString(signingKeyHex)
if err != nil {
return false
}
// Create HMAC hash
mac := hmac.New(sha256.New, secretBytes)
mac.Write([]byte(payload))
expectedSignature := hex.EncodeToString(mac.Sum(nil))
// Use ConstantTimeCompare to prevent timing attacks
return subtle.ConstantTimeCompare(
[]byte(signature),
[]byte(expectedSignature),
) == 1
}
import hmac
import hashlib
def verify_hmac_signature(payload: str, signature: str, signing_key: str) -> bool:
"""
Verify HMAC-SHA256 signature
Args:
payload: The raw payload string
signature: The hex signature to verify
secret_hex: The hex-encoded secret key
Returns:
True if signature is valid, False otherwise
"""
# Convert hex secret to bytes
secret_bytes = bytes.fromhex(signing_key)
# Create expected signature
expected_signature = hmac.new(
secret_bytes,
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Use compare_digest to prevent timing attacks
return hmac.compare_digest(signature, expected_signature)
import * as crypto from 'crypto';
function verifyHmacSignature(
payload: string,
signature: `0x{string}`,
signingKey: `0x{string}`
): boolean {
// Convert hex secret to buffer
const secretBuffer = Buffer.from(signingKey.slice(2), 'hex');
// Create HMAC hash of the payload
const expectedSignature = crypto
.createHmac('sha256', secretBuffer)
.update(payload, 'utf8')
.digest('hex');
// Use timingSafeEqual to prevent timing attacks
const sigBuffer = Buffer.from(signature.slice(2), 'hex');
const expectedBuffer = Buffer.from(expectedSignature, 'hex');
if (sigBuffer.length !== expectedBuffer.length) {
return false;
}
return crypto.timingSafeEqual(sigBuffer, expectedBuffer);
}
Updated 6 days ago