Signature Generation and Verification
Signature Generation Algorithm
The signature is computed using HMAC-SHA256 with the following steps:
- Construct the message: Concatenate the timestamp and request body with a period (
.) separator:message = timestamp + "." + request_body - Decode the secret key: Your webhook secret is Base64 URL-encoded. Decode it to get the raw bytes.
- Compute HMAC-SHA256: Use the decoded secret key to compute the HMAC-SHA256 of the message.
- Hex encode: Convert the resulting bytes to a lowercase hexadecimal string.
Verification Steps
To validate the signature:
- Extract the
X-Velora-Timestampheader value - Extract the
X-Velora-Signatureheader value and remove thesha256=prefix - Get the raw request body as a string (preserve exact formatting)
- Compute your own signature using the algorithm above
- Compare your computed signature with the received signature using a constant-time comparison
Code Examples
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HexFormat;
public class WebhookValidator {
public static String computeSignature(String base64Secret, String timestamp, String body) {
try {
String message = timestamp + "." + body;
// Decode the Base64 URL-encoded secret
byte[] keyBytes = Base64.getUrlDecoder().decode(base64Secret);
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "HmacSHA256");
mac.init(keySpec);
byte[] rawHmac = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(rawHmac);
} catch (Exception ex) {
throw new RuntimeException("Unable to compute signature", ex);
}
}
public static boolean verifySignature(String base64Secret, String timestamp,
String body, String receivedSignature) {
String expectedSignature = computeSignature(base64Secret, timestamp, body);
// Use constant-time comparison to prevent timing attacks
return java.security.MessageDigest.isEqual(
expectedSignature.getBytes(StandardCharsets.UTF_8),
receivedSignature.getBytes(StandardCharsets.UTF_8)
);
}
}const crypto = require('crypto');
function computeSignature(base64Secret, timestamp, body) {
const message = `${timestamp}.${body}`;
// Decode Base64 URL-encoded secret
const secretBuffer = Buffer.from(base64Secret, 'base64url');
const hmac = crypto.createHmac('sha256', secretBuffer);
hmac.update(message, 'utf8');
return hmac.digest('hex');
}
function verifySignature(base64Secret, timestamp, body, receivedSignature) {
const expectedSignature = computeSignature(base64Secret, timestamp, body);
// Use constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'utf8'),
Buffer.from(receivedSignature, 'utf8')
);
}
// Express.js middleware example
function webhookMiddleware(req, res, next) {
const timestamp = req.headers['x-velora-timestamp'];
const signature = req.headers['x-velora-signature']?.replace('sha256=', '');
const body = req.rawBody; // Ensure you capture raw body
if (!verifySignature(process.env.WEBHOOK_SECRET, timestamp, body, signature)) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
}import hmac
import hashlib
import base64
def compute_signature(base64_secret: str, timestamp: str, body: str) -> str:
message = f"{timestamp}.{body}"
# Decode Base64 URL-encoded secret
secret_bytes = base64.urlsafe_b64decode(base64_secret)
signature = hmac.new(
secret_bytes,
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signature
def verify_signature(base64_secret: str, timestamp: str, body: str, received_signature: str) -> bool:
expected_signature = compute_signature(base64_secret, timestamp, body)
# Use constant-time comparison
return hmac.compare_digest(expected_signature, received_signature)
# Flask example
from flask import request, abort
@app.before_request
def verify_webhook():
timestamp = request.headers.get('X-Velora-Timestamp')
signature = request.headers.get('X-Velora-Signature', '').replace('sha256=', '')
body = request.get_data(as_text=True)
if not verify_signature(WEBHOOK_SECRET, timestamp, body, signature):
abort(401, 'Invalid signature')<?php
function computeSignature(string $base64Secret, string $timestamp, string $body): string {
$message = $timestamp . '.' . $body;
// Decode Base64 URL-encoded secret
$secretBytes = base64_decode(strtr($base64Secret, '-_', '+/'));
$signature = hash_hmac('sha256', $message, $secretBytes);
return $signature;
}
function verifySignature(string $base64Secret, string $timestamp, string $body, string $receivedSignature): bool {
$expectedSignature = computeSignature($base64Secret, $timestamp, $body);
// Use constant-time comparison
return hash_equals($expectedSignature, $receivedSignature);
}
// Usage example
$timestamp = $_SERVER['HTTP_X_VELORA_TIMESTAMP'] ?? '';
$signature = str_replace('sha256=', '', $_SERVER['HTTP_X_VELORA_SIGNATURE'] ?? '');
$body = file_get_contents('php://input');
if (!verifySignature(WEBHOOK_SECRET, $timestamp, $body, $signature)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}using System;
using System.Security.Cryptography;
using System.Text;
public class WebhookValidator
{
public static string ComputeSignature(string base64Secret, string timestamp, string body)
{
var message = $"{timestamp}.{body}";
// Decode Base64 URL-encoded secret
var secretBytes = Convert.FromBase64String(
base64Secret.Replace('-', '+').Replace('_', '/'));
using var hmac = new HMACSHA256(secretBytes);
var hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
return Convert.ToHexString(hashBytes).ToLowerInvariant();
}
public static bool VerifySignature(string base64Secret, string timestamp,
string body, string receivedSignature)
{
var expectedSignature = ComputeSignature(base64Secret, timestamp, body);
// Use constant-time comparison
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expectedSignature),
Encoding.UTF8.GetBytes(receivedSignature));
}
}Updated 2 months ago
Did this page help you?
