DEV Community

Cover image for πŸ“¬ Understanding GitHub Webhooks: Send Email Notifications on Pull Request Events Using Node.js
Mehak.
Mehak.

Posted on

πŸ“¬ 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!

Top comments (0)