DEV Community

Cover image for 📬 Understanding GitHub Webhooks: Send Email Notifications on Pull Request Events Using Node.js
Mehak.
Mehak.

Posted on

1 1 1

📬 Understanding GitHub Webhooks: Send Email Notifications on Pull Request Events Using Node.js

Recently, I became curious about how GitHub webhooks work. To deepen my understanding, I built a small Node.js project that sends an email notification every time a pull request (PR) is opened.

Yes, GitHub already has a built-in notification system, but building your own gives you full control and a deeper understanding of the ecosystem.


🔐 First, What’s HMAC?

Before we dive into code, it’s important to understand how GitHub ensures that webhook events are secure.

HMAC stands for Hash-based Message Authentication Code. It’s a cryptographic technique used to verify both:

  • The integrity of a message (that it hasn’t been tampered with)
  • The authenticity of the sender (that it’s really from GitHub)

GitHub does this by hashing the body of the request with a shared secret you provide when creating the webhook. It then sends that signature along with the request using the X-Hub-Signature-256 header.


🧩 Webhook Levels

Webhooks can be configured at three levels:

  • Repository level – scoped to a single repo
  • Organization level – applies to all repositories within an organization
  • GitHub App level – used in GitHub Apps for deep integration across multiple repositories or organizations

For this example, I used an organization-level webhook so it applies to all repos in my org.


🔎 Signature Verification

To verify that the incoming request is really from GitHub, we need to:

  1. Capture the raw request body before it’s parsed by Express.
  2. Recompute the HMAC signature using the same secret.
  3. Use a constant-time comparison to prevent timing attacks.

Here’s how I do it:

🔸 Express Middleware

We add a raw body parser to capture the exact payload:

app.use(express.json({
  verify: (req, _, buf) => {
    req.rawBody = buf;
  }
}));
Enter fullscreen mode Exit fullscreen mode

This step is critical — HMAC must be calculated over the raw payload. If you parse it first, you’ll get a mismatch.

🔸 Signature Verifier (utils/verifier.js)

import crypto from 'crypto';
import { config } from './config.js';

export const verifySignature = (req)=>{
    const signature = req.headers['x-hub-signature-256'];
    if(!signature){
        return false;
    }
    const hmac = crypto.createHmac("sha-256", config.GIT_WEBHOOK_SECRET );
    hmac.update(req.rawBody);
    const expectedSignature = `sha256=${hmac.digest('hex')}`;
    return signature === expectedSignature;
}
Enter fullscreen mode Exit fullscreen mode

📬 Sending Email Notifications

Once the signature is verified, I check if the action is "opened" on a PR, and then send an email.

I used Nodemailer with Gmail as the SMTP service. Since my Gmail account uses 2FA, I generated an App Password to authenticate.Use the app password without spaces.

🔸 Mailer (services/emailService.js)

const transporter = nodemailer.createTransport({
    service:"Gmail",
    auth:{
        user: config.EMAIL,
        pass: config.PASSWORD
    }
})

export  const sendPRrequestMail = (recipents, subject, text)=>{
    const mailOption = {
         from :config.EMAIL,
         to: recipents,
         subject: subject,
         text: text
    }
    return new Promise((resolve, reject)=>{
        transporter.sendMail(mailOption, (error, result)=>{
             if(error){
                 reject(error);
             }else{
                resolve(result);
             }
        })
    })

}
Enter fullscreen mode Exit fullscreen mode

🚀 Exposing the Server for GitHub to Reach

To allow GitHub to reach my local server, I had two options:

  1. Use a tunneling service like Ngrok
  2. Deploy to a cloud provider. I chose to deploy to Render, which has a generous free tier and makes deployment super easy. Once deployed, I used the Render URL as the webhook endpoint in GitHub. ---

✅ Summary

  • 🔐 We used HMAC with a shared secret to verify webhook authenticity.
  • 📦 We used Nodemailer + Gmail to send email notifications.
  • 🌐 We deployed our app to Render to make it accessible to GitHub.
  • 🧠 And we learned that understanding webhooks at a low level is a great way to grow as a developer.

📂 Full Source Code

You can view the complete code for this project on GitHub:

👉 https://github.com/Mehakb78/git-prrequest-mailer
Feel free to clone it, experiment!

Neon image

Set up a Neon project in seconds and connect from a Next.js application ⚡

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Get started →

Top comments (0)

Heroku

Tired of jumping between terminals, dashboards, and code?

Check out this demo showcasing how tools like Cursor can connect to Heroku through the MCP, letting you trigger actions like deployments, scaling, or provisioning—all without leaving your editor.

Learn More

👋 Kindness is contagious

Explore this compelling article, highly praised by the collaborative DEV Community. All developers, whether just starting out or already experienced, are invited to share insights and grow our collective expertise.

A quick “thank you” can lift someone’s spirits—drop your kudos in the comments!

On DEV, sharing experiences sparks innovation and strengthens our connections. If this post resonated with you, a brief note of appreciation goes a long way.

Get Started