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)