---
heading: 'Verify Github webhooks'
title: 'Verifying Github webhooks requests in Node.js'
date: '2023-07-09'
lastmod: '2024-11-07'
icon: 'GitHub'
draft: false
summary: "Verify and process incoming GitHub webhooks. How to set up listeners, validate requests, and handle Github's webhook events."
tags: [github, webhook, github-api, verify-webhook-signature, remix]
---

Webhooks provide a way for apps to receive real-time information from **Github** whenever there is a new event.
It is important to ensure that the webhook request is coming from Github and process it accordingly.

This is how you can simply verify the webhook signature in a [Remix](https://remix.run) app (or any Node.js server).

```ts:github.ts showLineNumbers
import type { ActionFunction } from '@remix-run/node'
import { json } from '@remix-run/node'
import crypto from 'crypto'

// The `loader` function handle the GET request to the route
// In this case, we are returning a 400 status, cause we only want to handle POST requests only
// See more about Remix route's loader here: https://remix.run/docs/en/route/loader
export let loader = () => {
  return json({ message: 'Bad request!' }, { status: 400 })
}

// The `action` function handle non-GET requests to the route
// See more about Remix route's action here: https://remix.run/docs/en/route/action
export let action: ActionFunction = async ({ request }) => {
  // Return a 405 status if the request method is not POST
  if (request.method !== 'POST') {
    return json({ message: 'Method not allowed' }, 405)
  }

  // Verify the webhook signature
  let signature = request.headers.get('X-Hub-Signature-256')
  let rawBody = await request.text()
  let webhookSecret = process.env.GITHUB_APP_WEBHOOK_SECRET
  let hmac = crypto.createHmac('sha256', webhookSecret)
  hmac.update(rawBody)
  let generatedSignature = `sha256=${hmac.digest('hex')}`
  if (signature !== generatedSignature) {
    return json({ message: 'Webhook must originate from GitHub!' }, 400)
  }

  let event = request.headers.get('X-GitHub-Event')
  console.log(`✅ Github webhook verified!. Event: "${event}"`)

  try {
    let payload = JSON.parse(rawBody)
    if (event === 'your_subscribed_event') {
      let { action, sender, ...rest } = payload
      // Do something with the payload from Github
      // `action` is the action that triggered the event
      // `sender` is the user that triggered the event
    }
    return json({ message: 'Webhook processed successfully!', event }, 200)
  } catch (err) {
    console.log(`❌ Error processing webhook event: ${err?.toString()}`)
    // Return a 200 status to Github to avoid retries
    // even if the webhook payload is not processed successfully in your app
    return json({ message: 'Webhook processed!', event }, 200)
  }
}
```

Feel free to use this snippet in your app if you find it useful!

Happy verifying! <Twemoji emoji="clinking-beer-mugs" />
