shadow
How to Build a GitHub–Discord Automation Bot

How to Build a GitHub–Discord Automation Bot

See everything from GitHub without leaving Discord.

Ajay Yadav
September 2, 202513 min read

Share this article

linkedintwitterfacebookreddit

Last month, the founders at CreoWis kicked off a hackathon called Hatch-and-Hype (HnH). An initiative where every CreoWis team member had to build a project based on a given list of themes. The twist? Each project had to be built individually.

CreoWis hath-and-hype initiative.

The rules were simple: pick a theme, work on the project during weekends or after office hours, and once done, write a blog post about it and present a live demo to the team.

Our founders would then review each project along with its blog, and the top three projects would win a prize (see in the image above).

There were 11 themes to choose from, and I picked the third one, which focused on Automation. My idea was to build something for the CreoWis family that would allow anyone to access GitHub information without ever leaving our Discord server.

Today, I’m excited to share that the project is now stable and fully functional. We can view everything from GitHub right inside Discord!

This blog post is part of the Hatch-and-Hype initiative, and in it, I’ll walk you through my journey of building this project and the tech stack I used, the challenges I faced, and the future improvements I have in mind.

So without further ado, let’s dive in.

Brainstorming & Idea Selection

The tricky part was picking a theme and coming up with a solid project idea. Instead of just banging my head against random concepts, I wanted to solve a real problem that our Creowis family faces on a daily basis.

Around this time, I heard the term “Webhook” from Ajeet, one of our team members. That single word sparked my curiosity and pushed me to explore what I could build using it.

With the theme now clear, something related to automation. The next challenge was choosing the exact project. To help with brainstorming, I turned to ChatGPT for idea suggestions and explored the feasibility of each.

After a day of research and validation, I finally landed on an idea: creoper (creowis+super), a GitHub–Discord automation bot. The goal was simple: allow our team to view GitHub activity directly inside Discord without switching tabs.

I’ll dive deeper into the project’s features later in this blog, but this was the moment the idea took shape.

Choosing the Tech Stack

At this stage, I was figuring out which tech stack would be the best fit for this project. There were two possible approaches:

  1. Option 1: Use a .yml file and write all the logic in YAML. While simple, I felt it would become a headache later when adding new features. So I decided to discard this approach.

  2. Option 2: Build it using JavaScript or TypeScript, which I’m already comfortable with, and keep the project flexible for future enhancements.

Since my primary work at Creowis revolves around frontend development, I wanted something from the JavaScript ecosystem but still powerful enough for backend automation.

After researching with AI tools and Google, I found Node.js to be the best choice for handling APIs, webhooks, and real-time features.

I also decided to use TypeScript because its type safety and auto-suggestions are incredibly helpful. They feel like magic to me and help prevent many simple bugs during development. Below is a clear diagram showing the flow of this application.

Step 1: Basic Project Setup

I created a new project called creoper, initialized a basic Node.js app with TypeScript, and set up the initial files and folders. Here is the current folder structure of the project, which will be added to incrementally as needed during development.

Step 2: Creating the Discord Bot

Once the base setup was ready, I needed to create a Discord bot that would live inside our server. This was done through the Discord Developer Portal.

When creating the bot application, I could set a name, profile image, cover image, and bio. I also assigned the necessary permissions so the bot could interact with our server as required. Essentially, this bot would behave like a new member in the Discord server.

Step 3: GitHub Tokens and Webhook Setup

Next, I created a GitHub Personal Access Token for authentication with the Octokit API. I then set up an Express endpoint /webhook to receive GitHub events and configured a webhook in the target GitHub repository so the bot could react to updates in real time.

// to handle GitHub webhook verification and events
import { Webhooks } from "@octokit/webhooks"; 

// Create Webhooks instance with your GitHub webhook secret
const webhooks = new Webhooks({
  secret: process.env.GITHUB_WEBHOOK_SECRET as string,
});

You need to replace the payload URL with your local-tunnel (cloudflared tunnel) URL during development. Once we deploy it on Render, update the same URL to the Render URL.

Step 4: Create local-tunnel

During local development, the GitHub webhook needs a public URL to send requests to. For this, I used a tunneling tool that connects my local server to the internet. I tested multiple tools like ngrok, localtunnel, and cloudflared tunnel.

I recommend Cloudflared Tunnel because it’s free, easy to set up, and doesn’t require installing additional development dependencies. Later, the application would be hosted on Render so it could run 24/7.

💡
Pro Tip: To run multiple commands at the same time, use the concurrently package.

Here’s how my concurrently script looks for development:

  "scripts": {
    "dev": "concurrently \"ts-node src/index.ts\" \"ts-node src/bot/bot.ts\" \"cloudflared tunnel --url http://localhost:3000\""
  },

Tech Stack Overview

Here’s the tech stack I used for this project. I added these technologies one by one as needed, but in this section, I'm sharing all the technologies in one place along with a brief explanation of what each library does.

Dependencies

  • octokit/rest – Official GitHub REST API client for Node.js, used to fetch repository details, issues, pull requests, and more.

  • discord.js – A powerful Node.js library to interact with the Discord API, enabling the bot to send messages, embed content, and respond to commands.

  • dotenv – Loads environment variables from a .env file, keeping sensitive credentials like tokens safe and out of the codebase.

  • express – Minimal and flexible Node.js framework to handle incoming webhook requests and serve APIs if needed.

  • node-cron – Allows scheduling tasks at specific intervals, like automatically fetching updates from GitHub every few hours.

Dev Dependencies

  • octokit/webhooks – Handles GitHub webhook events easily, such as push events, issue updates, and pull request changes.

  • octokit/webhooks-types – Provides TypeScript type definitions for GitHub webhook payloads.

  • types/express – TypeScript type definitions for Express.

  • types/node – TypeScript type definitions for Node.js.

  • types/node-cron – TypeScript type definitions for Node-Cron.

  • concurrently – Runs multiple commands/scripts at the same time in a single terminal, useful during development.

  • ts-node – Runs TypeScript files directly without compiling them first.

  • typescript – The main TypeScript compiler for adding type safety and better development experience.

From Plan to Implementation

It's finally time to build the project, but is it really possible to create every feature I'm thinking of all at once? Of course not. As you likely know, stable applications don't appear overnight; they develop gradually, often following a versioning system like v1.0.0, v1.1.0, and so on.

For this project, I decided to skip traditional versioning and instead break the work into four phases. Each phase focuses on a set of features, which I implement and deploy to the main branch before moving on to the next.

Phase 1: Real-time Notifications

This first phase is all about enabling passive, real-time updates from GitHub directly into specific Discord channels. It’s the foundation of the integration, and it includes three main capabilities.

  1. Commit & Push Alerts:

    Whenever a new commit is pushed to a repository, the bot posts a message in a designated Discord channel.

    This message includes the project name, author, branch, commit message, and a direct link to the commit on GitHub.

     // Build discord message.
     const pushMessage = `
     **Recent push:**
     - **Project:** [${repo}](${repoUrl})
     - **Author:** <@${discordUser}>
     **Commits:**
     ${commitMessages}
     `;
    

    The GitHub Webhook event powering this feature is push.

     webhooks.onAny(async ({ id, name, payload }) => {
       // Triggerd push event
       if (name === "push") {
         await handlePushEvent(payload as WebhookEventMap["push"]);
       }
     }
    

Here is what the Discord message will look like:

  1. Pull Request Lifecycle

    All stages of a PR's lifecycle are announced in Discord. This includes when a PR is created, its status changes, a review is requested, it’s approved, changes are requested, or it’s successfully merged. These updates include the PR title, author, relevant links, and even celebratory messages for merges.

  const prMessage = `
**Pull Request:**
- **Title:** [${prTitle}](${prULR})
- **Project:** [${repoName}](${repoURL})
- **PR Status:** ${prAction}
- **Author:** <@${discordUser}>`;

Here is the condition to send the message if the reviewer requests changes or approves the pull request.

  if (review.state === "approved") {
    message = `
✅ **Pull Request Approved**
- **Project:** ${repo}
- **PR:** [${pr.title}](${prURL})
- **Reviewer:** <@${reviewer}>
- Hey <@${creator}>, your PR has been approved! 🚀`;
  }

  if (review.state === "changes_requested") {
    message = `
🔁 **Changes Requested on PR**
- **Project:** ${repo}
- **PR:** [${pr.title}](${prURL})
- **Reviewer:** <@${reviewer}>
- Hey <@${creator}>, <@${reviewer}> requested changes on your PR. Please take a look. 👀`;
  }

This is managed through the pull_request and pull_request_review webhook events.

  if (name === "pull_request") {
    await handlePullRequestEvent(payload as WebhookEventMap["pull_request"]);
  }

  if (name === "pull_request_review") {
    await handlePullRequestReview(
      payload as WebhookEventMap["pull_request_review"]
    );
  }

Here's how the Discord message looks:

  1. Issue Assignee

    When a GitHub issue is assigned, the bot pings the assigned user in Discord with the project name, issue title, assigner, and a link to the issue.

  // Build the issue assign message
  const message = `
**New Issue Assigned**
- **Project:** ${repo}
- **Issue:** [${issue.title}](${issueURL})
- **Assigned By:** <@${assigner}>
- Hey <@${assignee}>, you’ve got a new issue to work on.
  `;

This uses the issues webhook event.

  // Triggered issue assign event
  if (name === "issues") {
    await handleIssueAssign(payload as WebhookEventMap["issues"]);
  }

Here's how the issue assigned message looks in the Discord channel:

These essential features from Phase 1 are now fully functional, as you can see in the screenshots above. Next, let’s move to Phase 2.

Phase 2: CI/CD Workflow Status Updates

At Creowis, I noticed that after a pull request gets merged, either the admin or the developer has to constantly check GitHub’s Actions tab to monitor the workflow status (also known as the build).

The workflow can be in one of several states: failed, in progress (or queued), or completed. If the build fails, someone has to manually re-run it. This manual checking can be time-consuming and inefficient, which is why I decided to automate it.

With this feature, there's no need to keep checking the GitHub Actions tab. From now on, the team will receive real-time build status updates directly in a dedicated Discord channel.

To achieve this, I used GitHub’s workflow_run event:

  if (name === "workflow_run") {
    await handleWorkflowRun(payload as WebhookEventMap["workflow_run"]);
  }

Depending on the workflow’s status, the bot sends a relevant message to Discord:

 let message = "";
  if (status === "in_progress" || status === "queued") {
    message = `
**⚙️ Build in progress**
- Project: ${repo}
- Status: ${status}
- Actor: <@${discordUser}>
- Workflow: ${url}
`;
  }

  if (status == "completed") {
    message = `
**🎉 Build completed**
- Project: ${repo}
- Status: ${status}
- Conclusion: ${conclusion}
- Actor: <@${discordUser}>
- Workflow: ${url}
`;
  }

  if (
    status !== "in_progress" &&
    status !== "completed" &&
    status !== "queued"
  ) {
    message = `
**❌ Build failed**
- Project: ${repo}
- Status: ${status}
- Conclusion: ${conclusion}
- Actor: <@${discordUser}>
- Workflow: ${url}
`;
  }

Now, whether a build is in progress, completed successfully, or failed, the whole team is instantly informed via Discord, eliminating the need for constant tab-switching.

So far, I’ve completed two phases of this project, and everything is working perfectly. However, to keep the application running 24/7, it needs to be hosted somewhere.

I experimented with multiple hosting platforms, but I prefer Render.com because it’s free. The downside is that Render puts the application to sleep after 20–30 minutes of inactivity, which means it won’t stay active all the time unless it’s used consistently.

I’m still exploring alternative hosting options that can keep the application running continuously, but for now, it’s hosted on Render.

Once I deployed this on Render, I updated the GitHub payload URL to the Render URL.

Upcoming Features & Improvements

Two phases of the project are still pending and will be implemented in the future, where I plan to integrate AI and slash commands into the existing application. Here’s a quick overview.

Phase 3: Standup Automation and AI Integration

It will automatically generate and post daily standup summaries for each team member in Discord, based on their GitHub activity.

These summaries will be enhanced with AI to ensure they are clear and grammatically correct. The system will collect each user’s GitHub activity, such as commits, pull requests, and issue interactions.

If there is no activity on GitHub, the system will send a message tagging the user in the Discord channel. From there, they can manually edit the message to reflect their actual activity or mention if they are on leave.

After that, AI will clean and format the messages. The standup summaries will then be posted in a dedicated Discord channel twice daily: at 6:30 PM for the day’s standup and at 10:00 AM for a recap of the previous day.

Phase 4: GitHub Dashboard in Discord

In this phase, the goal is to transform Discord into a GitHub viewing dashboard for developers. Team members will be able to track issues, pull requests, and sprint progress directly within Discord using slash commands, eliminating the need to constantly switch to GitHub.

Planned Slash Commands:

  • /my-issues – View all GitHub issues assigned to the user running the command.

  • /issues @user – View all GitHub issues assigned to a specific Discord user.

  • /prs – List all open pull requests in connected repositories.

  • /my-prs – Show pull requests created by the user running the command.

  • /sprint-status – Display current sprint/milestone progress with completed and open issue counts.

  • /search-issues keyword=login – Search GitHub issues by keyword in the title or body.

The aim is to keep the team fully updated within Discord while reserving GitHub for core actions like creating issues and merging pull requests.

If successfully implemented, these features will significantly enhance the current application, making it more powerful and convenient for day-to-day development workflows.

This is not the end of the application, we plan to continue improving it based on team requirements and, if possible, integrate it with other internal tools in the future.

Conclusion

This project has been an incredible learning journey for me. Winning the Hatch-and-Hype hackathon is secondary; what matters most is that it gave me the opportunity to showcase my skills to my Creowis family and the world, of course.

Who knows, maybe in the future, my experience here will contribute to one of our internal projects. Of course, if I do win, it would be a solid source of motivation for me.

From brainstorming and planning to executing and successfully integrating multiple cool features, I’ve learned a great deal through this process. I’m also genuinely excited about the upcoming features I plan to add to enhance the application even further.

If you’ve stayed with me until this point, I feel confident that you’ve found this project and idea valuable. If so, I’d love to hear from you. Please share your comments, suggestions, or thoughts about this blog and project. I promise to respond to everyone, and your feedback will mean a lot to me.

Finally, a huge shout-out to our founders, Tapas, Koustav, and Nirmal, for giving us the opportunity to showcase our skills to the world.


We at CreoWis believe in sharing knowledge publicly to help the developer community grow. Let’s collaborate, ideate, and craft passion to deliver awe-inspiring product experiences to the world.

Let's connect:

This article is crafted by Ajay Yadav, a passionate developer at CreoWis. You can reach out to him on X/Twitter, LinkedIn, and follow his work on GitHub.

CreoWis Technologies © 2025

Crafted with passion by CreoWis