Verify Github webhooks

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)

}

}