Webhook Security – Don’t Let Hackers Spoof Your Data (Webhook Part 2)

5 min read

If your webhook URL is public (like myapp.com/webhook), anyone on the internet could send fake data. That could trigger wrong actions—like sending alerts or updating balances.

We need to move from “Blind Processing” → “Verified Processing.”

Trust = HMAC “Secret Handshake”

Professional services (Stripe, GitHub, Telegram) use HMAC. Think of it like a digital wax seal: if the seal is broken, you ignore the message.

How It Works

  1. Secret Key: You and the API share a private secret key.
  2. Signature: The API hashes the message with the secret and sends it in the HTTP Header.
  3. Verification: Your server hashes the message with your secret and compares it to the signature.
    • Match → Friend
    • Mismatch → Foe

Only trusted sources can trigger your webhook.

Role of crypto in Node.js

In Node.js, the built-in crypto module handles HMAC hashing, which is the core of this security:

  1. On the API sidecrypto hashes the payload using the shared secret to create a signature.
  2. On your servercrypto hashes the incoming payload with the same secret and compares it to the signature.

If the hashes match, the message is authentic. If not, it’s blocked. Think of it as a digital wax seal: only someone with the secret can make a valid seal.


Example: Secure Webhook Receiver in Node.js

const express =require('express');
const crypto =require('crypto');
const app =express();

// Shared secret (keep this private in .env in production)
constWEBHOOK_SECRET ="my_super_secure_key_123";

app.use(express.json());

app.post('/weather-webhook',(req, res) => {
// 1️⃣ Grab the signature sent by the API
const providedSignature = req.headers['x-webhook-signature'];

// 2️⃣ Hash the incoming data using our secret
const calculatedHash = crypto
        .createHmac('sha256',WEBHOOK_SECRET)
        .update(JSON.stringify(req.body))
        .digest('hex');

// 3️⃣ Compare the signatures
if (providedSignature === calculatedHash) {
console.log("✅ Verified: The data is real!");
        res.status(200).send('Verified');// safe to process data
    }else {
console.log("❌ Blocked: Unauthorized request detected!");
        res.status(401).send('Invalid Signature');
    }
});

app.listen(3000,() =>console.log('Secure Webhook Server listening...'));

Step-by-Step Flow

  1. API Sends Data: The provider sends a JSON payload with a signature in the headers.
  2. You Hash: Your server hashes the JSON using your shared secret.
  3. Compare: If the hash matches the signature → ✅ trusted.
  4. Mismatch: If it doesn’t match → ❌ block it.
Example of Secure Webhook
Example of Secure Webhook

This ensures only the real API can trigger your webhook, keeping hackers out.

In above we saw how to make our webhooks secure using HMAC and crypto. Once your webhooks are secure, the next challenge is handling high traffic to provide a smooth experience for your users—and to prevent your system from collapsing.

In a professional environment, you never process a webhook fully the moment it arrives.

  • Most API providers (Stripe, Shopify, GitHub, etc.) expect a 200 OK response within a tight window, usually 2–10 seconds.
  • If your server takes too long to process, the provider assumes it’s down and retries automatically.
  • This can quickly escalate into a “retry storm”, where thousands of duplicate webhooks hit your server at once, overwhelming your system.

The Solution: Ingest & Queue Pattern

To handle high traffic safely, we separate receiving from processing:

  1. Receiver: A tiny, lightning-fast service that only:
    • Verifies the webhook signature (crypto HMAC check).
    • Pushes the verified event into a queue.
    • Immediately responds 200 OK to the provider.
  2. Queue: A waiting room (Redis, RabbitMQ, SQS) that holds events safely until workers are ready.
  3. Worker: A background process that pulls events one by one from the queue and does the heavy processing (database updates, notifications, calculations).

Advanced Example: Node.js + BullMQ (Redis)

We use BullMQ, a powerful library for Node.js, to decouple our system.

1. The “Lightning Fast” Receiver

This server doesn’t do the “work”; it just “takes the message” and responds instantly.

const { Queue } = require('bullmq');
const express = require('express');

// Connect to Redis
 const webhookQueue = new Queue('webhook-tasks', {
    connection : { host: 'localhost', port: 6379 }
 });

const app = express();
app.use(express.json());

app.post('/api/webhook', async (req, res) => {
    // 1. (Optional) Quick Signature Validation (from Part 2)
    
    // 2. Push to Queue - Do NOT wait for processing!
    await webhookQueue.add('process_webhook', { 
        payload: req.body,
        receivedAt: new Date()
    });

    // 3. Respond immediately (202 means 'Accepted for processing')
    res.status(202).send('Accepted'); 
});

app.listen(3000);

2. The Background Worker

This runs as a separate process. It can take its time without making the API provider wait.


const { Worker } = require('bullmq');

const worker = new Worker('webhook-tasks', async (job) => {
    console.log('--- Processing Heavy Task ---');
    
    // Simulate heavy work (e.g., AI analysis, PDF generation, or legacy DB updates)
    await heavyTaskExecution(job.data.payload);
    
    console.log('Task Complete!');
}, { connection: { host: 'localhost', port: 6379 } });

The 2 Pillars of High-Scale Webhooks

1. Idempotency (The “No Double Work” Rule)

Networks are glitchy. A provider might send the exact same webhook twice. If your webhook handles payments, you don’t want to charge a customer twice.

  • The Fix: Use a unique ID (like a transaction_id or webhook_id). Check your database: “Have I processed this ID already?” If yes, skip it.

2. Dead Letter Queues (The Safety Net)

If a worker fails because your database is down, the webhook isn’t lost. It goes to a Dead Letter Queue (DLQ). Once you fix the issue, you can “replay” these messages so no data is ever missed.

We have traveled from the basics of solving the Polling Problem, to securing our data with HMAC, and finally building a Scalable Queue Architecture.

Webhooks are the nervous system of the modern web. By following these three steps, your application is now ready to handle real-time data efficiently, securely, and at massive scale.