Dev.to Security πŸ” Cybersecurity πŸ‘ 0 πŸ“– 3 min read

Securing Webhooks Between Netlify and a Home Automation Server

Tired of random webhook spam hitting your home-automation server? If you run an automation server at home to handle form submissions or data from a public-facing site like Netlify, you quickly realize that the open int

Tired of random webhook spam hitting your home-automation server?

If you run an automation server at home to handle form submissions or data from a public-facing site like Netlify, you quickly realize that the open internet is loud. Anyone can scan your public IP or guess your webhook URL and flood your logs with garbage dataβ€”or worse, trigger unintended routines.

Running my own website and automation server for Dream Futures Tech here in Pompano Beach, Florida, I needed a way to ensure that incoming HTTP requests actually originated from my Netlify deployment and not an automated bot scraping the web.

In my judgment, the most reliable lightweight solution without reaching for heavy enterprise infrastructure is combining a SHA-256 HMAC signature with a strict request timestamp.

Here is how I implemented it using plain JavaScript on both ends.

Step 1: Generate the Signature on Netlify

When a user triggers an action on the site, a Netlify Function prepares the payload. Before sending it to my home server’s dynamic DNS endpoint, the function generates a cryptographic signature using a shared secret key stored in Netlify's environment variables.

We also attach the current Unix timestamp. This prevents replay attacks, where a malicious actor intercepts a legitimate payload and replays it hours later.

const crypto = require('crypto');

exports.handler = async function(event) {
  if (event.httpMethod !== 'POST') {
    return { statusCode: 405, body: 'Method Not Allowed' };
  }

  const secret = process.env.WEBHOOK_SECRET;
  const timestamp = Date.now().toString();
  const payload = event.body;

  // Create the HMAC signature
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(`${timestamp}.${payload}`);
  const signature = hmac.digest('hex');

  try {
    const response = await fetch('https://my-home-server.duckdns.org/webhook', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Signature': signature,
        'X-Timestamp': timestamp
      },
      body: payload
    });

    return { statusCode: 200, body: 'Webhook dispatched securely.' };
  } catch (error) {
    return { statusCode: 500, body: 'Dispatch failed.' };
  }
};

Step 2: Verify the Signature on the Home Server

On my home automation server running a lightweight Node.js and Express setup, the incoming request must pass two strict validation checks before any business logic executes.

First, it checks if the timestamp is older than five minutes. If it is, the request is dropped immediately. Second, it recalculates the HMAC using the shared secret and compares it against the incoming X-Signature header using crypto.timingSafeEqual to prevent timing attacks.

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

app.use(express.text({ type: 'application/json' }));

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-signature'];
  const timestamp = req.headers['x-timestamp'];
  const payload = req.body;

  if (!signature || !timestamp) {
    return res.status(401).send('Missing security headers.');
  }

  // Check timestamp drift (5 minute window)
  const fiveMinutes = 5 * 60 * 1000;
  if (Math.abs(Date.now() - parseInt(timestamp, 10)) > fiveMinutes) {
    return res.status(401).send('Timestamp expired.');
  }

  // Recalculate HMAC
  const secret = process.env.WEBHOOK_SECRET;
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(`${timestamp}.${payload}`);
  const expectedSignature = hmac.digest('hex');

  // Constant-time comparison
  const sigBuffer = Buffer.from(signature, 'hex');
  const expectedBuffer = Buffer.from(expectedSignature, 'hex');

  if (sigBuffer.length !== expectedBuffer.length || !crypto.timingSafeEqual(sigBuffer, expectedBuffer)) {
    return res.status(403).send('Invalid signature.');
  }

  // Parse payload and run local automation
  const data = JSON.parse(payload);
  console.log('Valid webhook received:', data);

  res.status(200).send('Received');
});

app.listen(3000);

Why This Matters for Self-Hosted Setups

Exposing a home server to the internet always carries inherent risks. Relying solely on obscure routing paths or firewall rules is not enough because public DNS leaks happen. By forcing cryptographic verification with a rolling timestamp, you ensure that even if someone sniffs the webhook URL, they cannot forge valid payloads or replay old ones.

It takes less than an hour to set up, requires zero paid third-party tools, and instantly cleans up your server logs.

I build sites and automations for small businesses at dreamfuturestech.com.

πŸ“° Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.