Back to Documentation

Webhook Security & Verification

Learn how to secure your webhook endpoints and verify that requests are genuinely from Ranklite.

Critical Security Notice

Always verify webhook signatures before processing payloads. Without verification, malicious actors could send fake webhooks to your endpoint.

How Webhook Signing Works

Ranklite signs every webhook with a secret key using HMAC-SHA256. Each webhook request includes an X-Ranklite-Signature header containing the signature.

Verification Process:

  1. Ranklite creates a signature using your webhook secret
  2. The signature is sent in the request header
  3. Your server recreates the signature using the same secret
  4. If signatures match, the webhook is authentic

Implementing Signature Verification

Node.js / Express

import crypto from 'crypto';
import express from 'express';

const app = express();

// Important: Use raw body for signature verification
app.use('/api/webhooks', express.raw({ type: 'application/json' }));

function verifyWebhookSignature(rawBody, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  const digest = 'sha256=' + hmac.update(rawBody).digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(digest)
  );
}

app.post('/api/webhooks/ranklite', (req, res) => {
  const signature = req.headers['x-ranklite-signature'];
  const secret = process.env.RANKLITE_WEBHOOK_SECRET;
  
  if (!signature || !verifyWebhookSignature(req.body, signature, secret)) {
    console.error('Invalid signature');
    return res.status(401).send('Unauthorized');
  }
  
  // Parse the verified body
  const event = JSON.parse(req.body.toString());
  
  // Process the webhook
  console.log('Verified webhook:', event.type);
  
  res.status(200).send('OK');
});

Python / Flask

import hmac
import hashlib
import os
from flask import Flask, request, abort

app = Flask(__name__)

def verify_signature(payload, signature, secret):
    expected_sig = 'sha256=' + hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(expected_sig, signature)

@app.route('/api/webhooks/ranklite', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Ranklite-Signature')
    secret = os.environ.get('RANKLITE_WEBHOOK_SECRET')
    
    if not signature or not verify_signature(request.data, signature, secret):
        abort(401)
    
    event = request.get_json()
    print(f"Verified webhook: {event['type']}")
    
    return 'OK', 200

PHP

<?php
function verifyWebhookSignature($payload, $signature, $secret) {
    $expectedSignature = 'sha256=' . hash_hmac('sha256', $payload, $secret);
    return hash_equals($expectedSignature, $signature);
}

$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_RANKLITE_SIGNATURE'];
$secret = getenv('RANKLITE_WEBHOOK_SECRET');

if (!verifyWebhookSignature($payload, $signature, $secret)) {
    http_response_code(401);
    die('Unauthorized');
}

$event = json_decode($payload, true);
error_log("Verified webhook: " . $event['type']);

http_response_code(200);
echo 'OK';
?>

Security Best Practices

Always Use HTTPS

Ranklite only sends webhooks to HTTPS endpoints to prevent man-in-the-middle attacks

Store Secrets Securely

Never hardcode webhook secrets. Use environment variables or secure secret management

Use Timing-Safe Comparison

Use constant-time string comparison to prevent timing attacks

Verify Before Processing

Always verify the signature before processing any webhook data

Implement Rate Limiting

Protect your endpoint from abuse with rate limiting

Log Security Events

Log all verification failures for security monitoring

Rotating Webhook Secrets

If you suspect your webhook secret has been compromised, rotate it immediately:

  1. 1

    Generate New Secret

    Go to Settings → Webhooks → Edit webhook → Regenerate Secret

  2. 2

    Update Your Endpoint

    Update your server with the new secret before the old one expires

  3. 3

    Test Verification

    Use the "Test Webhook" button to verify the new secret works

The old secret remains valid for 24 hours after rotation to prevent service disruption.

Testing Signature Verification

You can test your signature verification with this example payload and secret:

Secret: whsec_test_secret_12345
Payload: {"id":"evt_test","type":"article.published","created":1734134400}
Expected Signature: sha256=9c5b94b1e7f3d6c4a8f2e1d3b5c7a9e2f4d6c8b0a1e3c5d7e9b2a4c6d8e0f2