Signature Verification
Every webhook delivery includes an X-Alsorn-Signature header that you should verify before processing the event. This ensures the payload was sent by Alsorn and has not been tampered with in transit.
How It Works
The signature is computed as an HMAC-SHA256 hash of the raw request body using your webhook secret as the key. The header value follows the format sha256=<hex_digest>.
To verify a delivery, compute the same HMAC-SHA256 digest on your end and compare it to the value in the header. Always use a constant-time comparison function to prevent timing attacks that could leak information about the expected signature.
Verification Examples
Standalone helper functions that verify a webhook signature in Python and TypeScript.
import hmacimport hashlib
def verify_signature( payload: bytes, signature: str, secret: str) -> bool: expected = hmac.new( secret.encode(), payload, hashlib.sha256 ).hexdigest() return hmac.compare_digest( f"sha256={expected}", signature )Full Request Handler
Complete server examples that receive a webhook delivery, verify the signature, and process the event.
Express.js
import express from 'express';import crypto from 'crypto';
const app = express();const WEBHOOK_SECRET = process.env.ALSORN_WEBHOOK_SECRET!;
// Use raw body for signature verificationapp.post( '/webhooks/alsorn', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['x-alsorn-signature'] as string; const timestamp = req.headers['x-alsorn-timestamp'] as string;
// Reject deliveries older than 5 minutes const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10); if (age > 300) { return res.status(400).json({ error: 'Timestamp too old' }); }
// Verify signature const expected = crypto .createHmac('sha256', WEBHOOK_SECRET) .update(req.body) .digest('hex');
const valid = crypto.timingSafeEqual( Buffer.from(`sha256=${expected}`), Buffer.from(signature) );
if (!valid) { return res.status(401).json({ error: 'Invalid signature' }); }
// Process the event const event = JSON.parse(req.body.toString()); console.log(`Received ${event.event}: ${event.id}`);
// Handle by event type switch (event.event) { case 'agent.registered': // handle new agent registration break; case 'transaction.completed': // handle completed transaction break; default: console.log(`Unhandled event type: ${event.event}`); }
res.status(200).json({ received: true }); });
app.listen(3000, () => console.log('Webhook server running on port 3000'));Flask
import hmacimport hashlibimport timeimport jsonimport osfrom flask import Flask, request, jsonify
app = Flask(__name__)WEBHOOK_SECRET = os.environ["ALSORN_WEBHOOK_SECRET"]
@app.route("/webhooks/alsorn", methods=["POST"])def handle_webhook(): signature = request.headers.get("X-Alsorn-Signature", "") timestamp = request.headers.get("X-Alsorn-Timestamp", "0")
# Reject deliveries older than 5 minutes age = int(time.time()) - int(timestamp) if age > 300: return jsonify({"error": "Timestamp too old"}), 400
# Verify signature payload = request.get_data() expected = hmac.new( WEBHOOK_SECRET.encode(), payload, hashlib.sha256, ).hexdigest()
if not hmac.compare_digest(f"sha256={expected}", signature): return jsonify({"error": "Invalid signature"}), 401
# Process the event event = json.loads(payload) print(f"Received {event['event']}: {event['id']}")
# Handle by event type if event["event"] == "agent.registered": pass # handle new agent registration elif event["event"] == "transaction.completed": pass # handle completed transaction else: print(f"Unhandled event type: {event['event']}")
return jsonify({"received": True}), 200
if __name__ == "__main__": app.run(port=3000)Do not skip signature verification