GuardAPI Logo
GuardAPI

Fix Insecure Webhooks in Meteor

Webhooks in Meteor are often implemented via 'WebApp.connectHandlers'. The common failure mode is 'blind trust': accepting incoming POST requests without cryptographic verification of the sender. An attacker can easily spoof payloads to trigger unauthorized actions, such as marking unpaid orders as 'paid' or escalating user privileges. To fix this, you must implement HMAC signature verification using a shared secret.

The Vulnerable Pattern

import { WebApp } from 'meteor/webapp';
import bodyParser from 'body-parser';

// VULNERABLE: No signature verification. Anyone can POST to this endpoint. WebApp.connectHandlers.use(‘/webhooks/payment’, bodyParser.json()); WebApp.connectHandlers.use(‘/webhooks/payment’, (req, res) => { const { orderId, status } = req.body; if (status === ‘completed’) { Orders.update(orderId, { $set: { paid: true } }); } res.writeHead(200); res.end(‘OK’); });

The Secure Implementation

The secure implementation ditches generic body-parser middleware to capture the 'raw' buffer. This is critical because re-stringifying a parsed object often changes the byte-order or whitespace, breaking the hash. We compute an HMAC-SHA256 hash of the raw body using a pre-shared secret and compare it to the provider's header using 'crypto.timingSafeEqual'. This ensures the request is authentic and hasn't been tampered with in transit. If the hashes don't match, we drop the request immediately.

import { WebApp } from 'meteor/webapp';
import crypto from 'crypto';

const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

WebApp.connectHandlers.use(‘/webhooks/payment’, (req, res, next) => { let rawBody = ”; req.on(‘data’, (chunk) => { rawBody += chunk; }); req.on(‘end’, () => { const signature = req.headers[‘x-hub-signature-256’]; if (!signature) { res.writeHead(401); return res.end(‘Missing signature’); }

const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
const digest = 'sha256=' + hmac.update(rawBody).digest('hex');

// Use timingSafeEqual to prevent timing attacks
const signatureBuffer = Buffer.from(signature);
const digestBuffer = Buffer.from(digest);

if (signatureBuffer.length !== digestBuffer.length || !crypto.timingSafeEqual(signatureBuffer, digestBuffer)) {
  res.writeHead(403);
  return res.end('Invalid signature');
}

try {
  req.body = JSON.parse(rawBody);
  // Logic to update DB goes here...
  res.writeHead(200);
  res.end('Verified');
} catch (e) {
  res.writeHead(400);
  res.end('Invalid JSON');
}

}); });

System Alert • ID: 2964
Target: Meteor API
Potential Vulnerability

Your Meteor API might be exposed to Insecure Webhooks

74% of Meteor apps fail this check. Hackers use automated scanners to find this specific flaw. Check your codebase before they do.

RUN FREE SECURITY DIAGNOSTIC
GuardLabs Engine: ONLINE

Free Tier • No Credit Card • Instant Report

Verified by Ghost Labs Security Team

This content is continuously validated by our automated security engine and reviewed by our research team. Ghost Labs analyzes over 500+ vulnerability patterns across 40+ frameworks to provide up-to-date remediation strategies.