A self-hosted service that forwards GitHub webhook events to Discord channels with beautifully formatted embeds. Supports multi-user, multi-repo configurations with secure webhook signature verification.
- On first start, the deployment will automatically create an invite code for you to use. You can use this to sign up for an account.
- Secure by design - GitHub webhook signature verification (HMAC-SHA256)
- Multi-user support - Each user manages their own webhook mappings
- Flexible registration - Open, closed, or invite-only modes
- Beautiful Discord embeds - Rich formatting for PR events
- Easy deployment - Single binary, auto-migrations, Railway-ready
- Bun runtime
- PostgreSQL database
# Clone the repository
git clone https://github.com/alexng353/github-discord-webhook.git
cd github-discord-webhook
# Install dependencies
bun install
# Set up environment variables (see below)
cp .env.example .env
# Run the server (auto-runs migrations)
bun run start| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL | ✅ | - | PostgreSQL connection string (postgres://...) |
PORT | ❌ | 3000 | Server port |
NODE_ENV | ❌ | development | Environment mode |
REGISTRATION | ❌ | open | Registration mode: open, closed, or invite_only |
RAILWAY_PUBLIC_DOMAIN | ❌ | - | Public domain for generated webhook URLs (Railway deployment) |
DATABASE_URL=postgres://user:password@localhost:5432/webhooks
PORT=3000
REGISTRATION=invite_onlyGitHub Repo -> GitHub Webhook -> This Service -> Discord Webhook -> Discord Channel
- User registers and creates a webhook mapping (repo → Discord webhook)
- Service generates a unique GitHub webhook URL
- User configures this URL in their GitHub repository settings
- GitHub sends webhook events to the service
- Service verifies the signature and forwards formatted embeds to Discord
The service has two authentication systems:
Used for managing webhook mappings through the web interface or API.
POST /auth/register → Create account
POST /auth/login → Get session cookie (7-day TTL)
POST /auth/logout → Invalidate session
GET /auth/me → Get current user info
Each webhook mapping has a secret that you also configure in GitHub. When GitHub sends a webhook:
- GitHub signs the payload with your secret using HMAC-SHA256
- GitHub sends the signature in
X-Hub-Signature-256header - This service verifies the signature before processing
This ensures only legitimate GitHub webhooks are processed.
Configure via REGISTRATION environment variable:
| Mode | Description |
|---|---|
open | Anyone can register |
closed | No new registrations allowed |
invite_only | Requires an invite code from existing user |
When running in invite_only mode, the first invite code is printed to the console on startup.
# Check registration mode
curl http://localhost:3000/auth/registration-mode
# Register (if open or with invite code)
curl -X POST http://localhost:3000/auth/register \
-H "Content-Type: application/json" \
-d '{"username": "myuser", "password": "mypassword123", "inviteCode": "abc123"}'# Login
curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "myuser", "password": "mypassword123"}' \
-c cookies.txt- Open Discord and go to your server
- Right-click the channel → Edit Channel → Integrations → Webhooks
- Click New Webhook, customize name/avatar
- Click Copy Webhook URL
curl -X POST http://localhost:3000/webhooks/mapping \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{ "repo": "owner/repo-name", "webhookUrl": "https://discord.com/api/webhooks/123/abc...", "secret": "your-github-webhook-secret" }'Response:
{
"created": true,
"repo": "owner/repo-name",
"id": "uuid-here",
"githubWebhookUrl": "https://your-domain.com/webhook/github/uuid-here"
}- Go to your GitHub repository → Settings → Webhooks → Add webhook
- Payload URL: Use the
githubWebhookUrlfrom the previous step - Content type:
application/json - Secret: The same secret you used when creating the mapping
- Events: Select "Pull requests" (or "Let me select individual events")
- Click Add webhook
Create or close a pull request in your repository. You should see a beautifully formatted embed in your Discord channel!
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET | /auth/registration-mode | - | Get current registration mode |
POST | /auth/register | - | Create new account |
POST | /auth/login | - | Login, returns session cookie |
POST | /auth/logout | Session | Logout, clears session |
GET | /auth/me | Session | Get current user info |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST | /auth/invites | Session | Create new invite code |
GET | /auth/invites | Session | List your invite codes |
DELETE | /auth/invites/:code | Session | Revoke unused invite code |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST | /webhooks/mapping | Session | Create new repo→Discord mapping |
GET | /webhooks/mapping | Session | List your webhook mappings |
GET | /webhooks/mapping/:repo | Session | Get specific mapping info |
DELETE | /webhooks/mapping/:repo | Session | Delete a mapping |
PATCH | /webhooks/mapping/:repo/secret | Session | Update webhook secret |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST | /webhook/github/:id | GitHub Signature | Receive GitHub webhook events |
| Method | Endpoint | Description |
|---|---|---|
GET | /health | Health check endpoint |
Currently supported:
- Pull Request Opened - Green embed with PR details
- Pull Request Closed - Red embed (or purple if merged)
More events coming soon!
# Run in development mode with hot reload
bun run dev
# Lint and format
bun run lintThe service uses PostgreSQL with Drizzle ORM. Migrations run automatically on startup.
users- User accounts (id, username, password_hash)sessions- Login sessions with expiryauth_tokens- Bearer tokens for API authwebhook_mappings- Repo → Discord webhook mappingsinvite_codes- Registration invite codes
- Connect your GitHub repo to Railway
- Add a PostgreSQL database
- Set environment variables:
DATABASE_URL(auto-set by Railway if using their Postgres)REGISTRATION=invite_only
- Deploy!
The RAILWAY_PUBLIC_DOMAIN is automatically detected for generating webhook URLs.
FROM oven/bun:1
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
COPY . .
CMD ["bun", "run", "start"]bun install --production
NODE_ENV=production bun run start- Passwords are hashed with Argon2 (via Bun's built-in
Bun.password) - GitHub webhook signatures use timing-safe comparison
- Sessions expire after 7 days
- Discord webhook URLs are redacted in API responses
- Each webhook mapping is scoped to its owner
Github-Discord-Webhook-Bridge Copyright (C) 2025 Alexander Ng
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.