Skip to content

Instantly share code, notes, and snippets.

@Amgelo563
Last active November 8, 2024 17:41
Show Gist options
  • Select an option

  • Save Amgelo563/1f7a41d9ef59a1a2997b9dc29e68c94d to your computer and use it in GitHub Desktop.

Select an option

Save Amgelo563/1f7a41d9ef59a1a2997b9dc29e68c94d to your computer and use it in GitHub Desktop.
Zero dependency Node script to listen to GitHub webhooks.

A 0 dependency Node script to listen to GitHub webhooks, calling a function on success.

Warning

Requires at least Node 20. If you want to use a lower version, you'll need to load the .env manually, with something like dotenv.

Usage

  1. Replace the contents of handleWebhookEvent to your specific needs.
  2. Create an .env file with the following contents:
GITHUB_WEBHOOK_SECRET=MySecret
SERVER_PORT=3000
GITHUB_WEBHOOK_URL=/webhook

GITHUB_WEBHOOK_URL and SERVER_PORT are optional, they default to / and 3000 respectively.

  1. Start with node --env-file=.env index.js.
import { createServer } from 'http';
import { createHmac, timingSafeEqual } from 'crypto';
const GITHUB_WEBHOOK_SECRET = process.env.GITHUB_WEBHOOK_SECRET;
const unparsedURL = process.env.GITHUB_WEBHOOK_URL || '/';
const GITHUB_WEBHOOK_URL = unparsedURL.startsWith('/') ? unparsedURL : `/${unparsedURL}`;
const unparsedPort = process.env.SERVER_PORT || 3000;
const SERVER_PORT = parseInt(unparsedPort, 10);
if (!GITHUB_WEBHOOK_SECRET) {
console.error('GITHUB_WEBHOOK_SECRET is required');
process.exit(1);
}
if (!SERVER_PORT || isNaN(SERVER_PORT)) {
console.error('PORT is required');
process.exit(1);
}
/**
* Verifies the signature of the incoming request
* @param {IncomingMessage} req
* @param {string} body
* @returns {boolean}
*/
function verifySignature(req, body) {
const signature = req.headers['x-hub-signature-256'];
if (!signature) return false;
const hmac = createHmac('sha256', GITHUB_WEBHOOK_SECRET);
hmac.update(body, 'utf-8');
const expectedSignature = `sha256=${hmac.digest('hex')}`;
return timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
/**
* Handles a GitHub webhook event
* @param {string} event The event type
* @param {object} payload The event payload
*/
function handleWebhookEvent(event, payload) {
console.log('Received GitHub Event:', event);
console.log('Payload:', payload);
}
const server = createServer((req, res) => {
if (req.method !== 'POST' || req.url !== GITHUB_WEBHOOK_URL) {
res.statusCode = 404;
res.end('Not Found');
return;
}
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
if (!verifySignature(req, body)) {
res.statusCode = 403;
res.end('Forbidden');
return;
}
const payload = JSON.parse(body);
const event = req.headers['x-github-event'];
res.statusCode = 200;
res.end('OK');
handleWebhookEvent(event, payload);
});
});
server.listen(SERVER_PORT, () => {
console.log(`Server is listening on localhost:${SERVER_PORT}${GITHUB_WEBHOOK_URL}`);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment