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.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.