Guides
Webhook Security
Every webhook Blogree sends is signed with HMAC-SHA256 using your site's unique webhook secret. This guide explains how to verify signatures, rotate secrets, and protect against replay attacks.
1. How Signature Verification Works
When Blogree sends a webhook to your endpoint, it includes an X-Blogree-Signature header containing a SHA-256 HMAC of the raw request body, signed with your webhook secret.
// Header sent with every webhook delivery:
X-Blogree-Signature: sha256=3b5d7f9a2c1e4b8d6f0a2e4c8b1d5f3a7e9c2b4d6f8a0c2e4b6d8f0a2c4e6b8
// The signature is: HMAC-SHA256(rawBody, webhookSecret)
⚠️Always verify the signature BEFORE processing the payload. Never trust unauthenticated webhook requests.
2. Verifying in Node.js / TypeScript
import crypto from 'crypto';
export function verifyBlogreeSignature(
rawBody: Buffer | string,
signature: string,
secret: string
): boolean {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(typeof rawBody === 'string' ? rawBody : rawBody.toString('utf8'));
const digest = 'sha256=' + hmac.digest('hex');
// Use timingSafeEqual to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(digest),
Buffer.from(signature)
);
}
// In your Next.js App Router route handler:
export async function POST(request: Request) {
const rawBody = await request.text();
const signature = request.headers.get('x-blogree-signature') || '';
if (!verifyBlogreeSignature(rawBody, signature, process.env.BLOGREE_WEBHOOK_SECRET!)) {
return new Response('Unauthorized', { status: 401 });
}
const payload = JSON.parse(rawBody);
// Process payload safely...
return new Response('OK', { status: 200 });
}
3. Verifying in Python
import hmac, hashlib
def verify_blogree_signature(raw_body: bytes, signature: str, secret: str) -> bool:
expected = 'sha256=' + hmac.new(
secret.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest()
# Use compare_digest to prevent timing attacks
return hmac.compare_digest(expected, signature)
# Flask example
from flask import request, abort
@app.route('/webhooks/blogree', methods=['POST'])
def blogree_webhook():
signature = request.headers.get('X-Blogree-Signature', '')
if not verify_blogree_signature(request.get_data(), signature, WEBHOOK_SECRET):
abort(401)
payload = request.get_json()
# Process...
return '', 200
4. Verifying in PHP
<?php
function verifyBlogreeSignature(string $rawBody, string $signature, string $secret): bool {
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
return hash_equals($expected, $signature);
}
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_BLOGREE_SIGNATURE'] ?? '';
if (!verifyBlogreeSignature($rawBody, $signature, getenv('BLOGREE_WEBHOOK_SECRET'))) {
http_response_code(401);
exit('Unauthorized');
}
$payload = json_decode($rawBody, true);
// Process...
http_response_code(200);
?>
5. Replay Attack Prevention
Blogree includes a X-Blogree-Timestamp header with every webhook. Use it to reject requests older than 5 minutes to prevent replay attacks.
export async function POST(request: Request) {
const rawBody = await request.text();
const signature = request.headers.get('x-blogree-signature') || '';
const timestamp = request.headers.get('x-blogree-timestamp') || '';
// Reject requests older than 5 minutes
const requestTime = parseInt(timestamp, 10);
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - requestTime) > 300) {
return new Response('Request too old', { status: 400 });
}
// Verify signature (include timestamp in signed content)
if (!verifyBlogreeSignature(`${timestamp}.${rawBody}`, signature, secret)) {
return new Response('Unauthorized', { status: 401 });
}
// Process...
}
6. Rotating Webhook Secrets
Rotate your webhook secret periodically or immediately if you suspect compromise. Go to Sites → your site → Settings → Rotate Webhook Secret.
⚠️After rotating, update BLOGREE_WEBHOOK_SECRET in your site's environment variables. Old deliveries during the rotation window will fail until the new secret is deployed.
7. IP Allowlist (Enterprise)
Enterprise customers can restrict webhook deliveries to Blogree's published IP ranges. Contact support for the current IP range list and allowlist configuration guidance.