This project is a middleware for a Cloudflare Worker that listens to GitHub Webhook events and sends notifications to a Discord server via Discord Webhook. The GitHub Webhook comes from a GitHub App that is installed in the GitHub account so it can listen to all selected repositories instead of requiring setting up an individual Webhook for each repository.
- Create a webhook in the desired server in
Server Settings→Integrations→Webhooks - Save the Webhook URL
- Enable Developer Mode in
User Settings→Advanced→Developer Mode - In
Server Settings→Rolescreate a role formentionsso the messages can trigger the mobile notification - Click in
Copy Role IDand save it
- Create a GitHub App in
GitHub Settings→Developer Settings→GitHub Apps - Configure the Webhook URL to the Worker endpoint (or use any valid URL as a placeholder)
- Generate and save the Private Key (*.pem)
- Enable permissions for following events:
issuesissue_commentpull_requestpull_request_reviewpull_request_review_commentpull_request_review_threaddiscussiondiscussion_commentworkflow_jobstarfork
- Install the App
- Create a KV namespace
- Link the KV namespace to the Worker in
wrangler.toml - Configure the secrets
- GITHUB_WEBHOOK_SECRET: Webhook secret configured in the GitHub App
- DISCORD_WEBHOOK_URL: Webhook URL for the Discord Server
- GITHUB_APP_ID: Application ID for the GitHub App
- GITHUB_PRIVATE_KEY: Private Key (base64 encoded) for the GitHub App already formatted so no parsing is required:
- No header:
-----BEGIN RSA PRIVATE KEY----- - No footer:
-----END RSA PRIVATE KEY----- - No newlines
- No header:
- DISCORD_ROLE_ID: Role ID for the Discord channel
X-GitHub-Eventheader is used to identify the event type andX-Hub-Signature-256header is used to verify the payload authenticity- Deduplicate events are filtered using the
X-GitHub-Deliveryheader +KVstorage with 24h expiration - Asynchronous processing of the events
Responseis sent immediately to GitHub with the correspondingStatus CodeandStatus Messageto avoid timeouts- The event is processed in the background via
ctx.waitUntil()
Retry-Afterheader is respected when Discord returns a429 Too Many Requestsresponse (max of 5 retries)
- When
owner = senderexcept for GitHub Actions/Workflows - Non-relevant
actionandstatelikeedited,draft,labeled, etc. - GitHub Actions/Workflows only notifies for
failure,cancelled,timed_outandaction_required
- Main limits:
- max 6000 characters per post
- max 10
embedsper post content: max 2000 charactersembedstitle: max 256 charactersdescription: max 4096 characters
allowed_mentionsis used to avoid@everyoneand@herementions in the Discord messages- Each event generates two Discord Messages:
- content: a brief summary of the event with the
@mentionto trigger the mobile notification - embeds: more detailed information about the event, including the original post when context is necessary
- content: a brief summary of the event with the
- The colors used for the
embedscommunicate a mix ofevent typeandsemanticsdepending on theevent,actionorstate:- issue:
#AB80FF - pull_request:
#FF4AC3 - discussion:
#2EE6D7 - star:
#FFD500 - fork:
#50B6FF - resolved:
#51D936 - attention:
#FF9925 - failure:
#F22468 - dismissed:
#A6ADB6
- issue:
- TypeScript
- Cloudflare Workers
- Wrangler
- Bun
comment.diff_hunkbehavior is inconsistent in bothGitHub WebviewandGitHub API, somepull_request_review_commentandpull_request_review_threadlinked to some lines will show thediffand some others will not. It's also a completely arbitrary behavior, I thought it could be due to a line that changed and one that didn't, but it happens in both cases.ctx.waitUntil()can cancel some processes when multiple events are triggered at once due to the free account limitations. In my case it only happens when there are multiple pending comments in a review and the user submits the review. That triggerspull_request_review- which is filtered - and onepull_request_review_commentfor each pending comment. This ends up working in my favor because I don't want to receive all the comments of the same thread at once, ideally, I would receive only the first comment of the bunch, but that's something I can't filter for.
I've recently learned the basics of GitHub Actions, so my fork of the TypeScript TmLanguage project for the Nebula Oni Theme, a VSCode Color Theme, can be automatically updated with the upstream repository and create an issue if any relevant changes are detected. When I tested it, I didn't receive the notification.
The problem is that GitHub notifications have always been a bit inconsistent. As I was doing research I've found out that using Discord Webhooks was a nice way to get custom notifications for GitHub events. Of all options Cloudflare Workers seemed as the more straight forward option for my use case.
Since I'm a first timer when it comes to Cloudflare Workers, GitHub and Discord Webhooks, there sure was a lot of learning involved in this project. Most of it came down to the authentication and authorization process (for the webhook but also for the GitHub API), which was a bit overwhelming, and understanding in details the GitHub Webhook events and payloads and how to format them into a Discord message.
The most annoying part was deciding which GitHub events and informations were relevant, what events could only be triggered by other users, and deciding how to design and format the Discord messages for each event, action and state. I had to constantly check and update the specsheet I created while keeping all the documentations opened to crosscheck something I might have missed.
Getting the Discord messages to look right in terms of content and formatting almost drove me crazy, a lot of trial and error, tons of testing. I thank a lot the existence of Discohook because it helped me a lot to prototype the Discord messages and better understand the Discord Webhook payload.
In August 2012, we changed the way watching works on GitHub. Some API client applications may still be using the original "watcher" endpoints for accessing this data. You should now use the "star" endpoints instead. For more information, REST API endpoints for starring and the changelog post.
In responses from the REST API, subscribers_count corresponds to the number of watchers, whereas watchers, watchers_count, and stargazers_count correspond to the number of users that have starred a repository.
In other words, until 2012, there was just watch but then they changed it to star, and for legacy reasons they kept the watch endpoint, which is normal.
But then, for no apparent logical or necessary reason, they decided to create a new feature with the same name watch as the legacy one, but with a different meaning and functionality.
WHY?!?
To mitigate this predictable confusion, internally, the new watch function is related to subscribers_count but there is no subscribe event at all, so there is also no way to track when a user subscribes or unsubscribes to a repository, which makes no sense either.
I really don't understand how and why this came to be, because it seems so obvious that you shouldn't have created a new functionality with the same name as a legacy one, even more when you could have simply called it subscribe and unsubscribe, which would have been much better - maybe more like follow and unfollow.
I really wished GitHub would make it more clear in the documentation which events, action and state are triggered by what level of access and permissions. And I think that's relevant for anyone using GitHub Webhooks because you want to be able to easily know and filter out what can be triggered by each type of user so you don't have to handle unnecessary events and payloads.
GitHub already has roles:
Read— can view, clone, and comment on issues, pull requests, and discussionsTriage— can manage issues and pull requests without write accessWrite— can push code and contribute to the repositoryMaintain— can manage the repository without access to sensitive or destructive actionsAdmin— full access to the repository except destructive actions on the organizationOwner— full access to the repository and its settings, including destructive actions
All the trial and error and even setting up a second account to figure it out all the possibilities and limitations of the GitHub Webhook events and payloads felt like a waste of time because it could have been easily avoided by a better documentation.
Imagine if you could have a table or a list for each event, with the action and state and what role can trigger them. Or even better, a filter for each role just like they have for each action.
Maybe it's also related to some legacy functionality, but there should be a pull_request_comment event instead of using issue_comment for Pull Request comments.
One could say that underneath everything is an issue and that's why they use issue_comment, but that would also be valid for discussion and discussion has discussion_comment.
So in order to deal with a Pull Request comments, you have to filter issue_comment by the existence of issue.pull_request and then translate all the proprieties to handle it as a Pull Request comment.
For something that helps to keep code organized, GitHub is quite confusing.
After some initial testings I found out that I needed @mention in order to properly trigger the mobile notifications in Discord.
At first I intended to just post a single message with both content and embeds, but unfortunately, when you use @mention Discord will highlight the whole message in an ugly orange color.
So I decided to split the message into two, one for the content with the @mention and another for the embeds with the detailed information.
In order to hide the @mention in the message, I used the same color of the orange highlight (#30271C) and set it for the role so it stays invisible in the message.
* for some reason Discord mobile notifications don't render markdown so it shows all the markdown formatting in the notification.
- Creating webhooks for a GitHub App
- GitHub Webhook events and payloads
- GitHub REST API documentation
- Discord Official Webhook Resource
- Discord API Types Community Reference
- Discohook
This project is licensed under the MIT License. See the LICENSE file for details.
