Deploy the @devintern/code webhook server for automated PR review handling
GitHub Integration Guide
This guide covers secure deployment options for the @devintern/code webhook server to automatically address PR review comments.
Table of Contents
- Webhook Server Deployment Guide
Overview
The webhook server listens for GitHub PR review events and automatically runs Agent to address review feedback. The architecture looks like:
GitHub → [Exposure Layer] → Webhook Server → Agent Harness → Git Push
Key Security Principle: The webhook server should never be directly exposed to the internet. Always use one of the secure exposure options below.
Prerequisites
- GitHub App configured with webhook permissions
- Webhook Secret - a random string for request verification
- Agent Harness CLI installed and configured
- Git credentials with push access to target repositories
Generate a webhook secret:
openssl rand -hex 32
Exposure Options
Option 1: Cloudflare Tunnel (Recommended)
Zero open ports - Cloudflare Tunnel creates an outbound-only connection from your server to Cloudflare’s edge network.
Pros:
- No inbound ports to open on your firewall
- Free tier available
- DDoS protection included
- Automatic HTTPS
- Works behind NAT/firewalls
Setup:
- Install cloudflared:
# macOS
brew install cloudflared
# Linux
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb
# Or download from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/
- Authenticate:
cloudflared tunnel login
- Create a tunnel:
cloudflared tunnel create devintern-webhooks
- Configure DNS (creates webhooks.yourdomain.com):
cloudflared tunnel route dns devintern-webhooks webhooks.yourdomain.com
- Create config file (
~/.cloudflared/config.yml):
tunnel: YOUR_TUNNEL_ID
credentials-file: /path/to/.cloudflared/YOUR_TUNNEL_ID.json
ingress:
- hostname: webhooks.yourdomain.com
service: http://localhost:3000
- service: http_status:404
- Run the tunnel:
# Foreground
cloudflared tunnel run devintern-webhooks
# Or as a service
sudo cloudflared service install
sudo systemctl start cloudflared
Final architecture:
GitHub → Cloudflare Edge → Cloudflare Tunnel → localhost:3000
↓
Webhook Server (no open ports)
Option 2: Tailscale Funnel
If you already use Tailscale for your network, Funnel provides a simple way to expose services.
Pros:
- Simple one-command setup
- Integrates with existing Tailscale network
- Automatic HTTPS with valid certificates
Setup:
-
Enable Funnel in Tailscale admin console (requires admin access)
-
Start the funnel:
tailscale funnel 3000
- Your webhook URL will be:
https://your-machine-name.tailnet-name.ts.net
Note: Tailscale Funnel has some limitations on free plans. Check Tailscale Funnel docs.
Option 3: Reverse Proxy (Caddy/nginx)
Use when you have a server with a public IP and want full control.
Caddy (Automatic HTTPS)
- Install Caddy:
# Debian/Ubuntu
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
# macOS
brew install caddy
- Configure (
/etc/caddy/Caddyfile):
webhooks.yourdomain.com {
reverse_proxy localhost:3000
# Rate limiting
rate_limit {
zone webhooks {
key {remote_host}
events 30
window 1m
}
}
# Optional: IP allowlisting for GitHub
# See "GitHub IP Ranges" section below for current IPs
@blocked not remote_ip 140.82.112.0/20 143.55.64.0/20 185.199.108.0/22 192.30.252.0/22
respond @blocked 403
}
- Start Caddy:
sudo systemctl enable caddy
sudo systemctl start caddy
nginx
- Configure (
/etc/nginx/sites-available/webhooks):
server {
listen 443 ssl http2;
server_name webhooks.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/webhooks.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/webhooks.yourdomain.com/privkey.pem;
# Rate limiting
limit_req_zone $binary_remote_addr zone=webhooks:10m rate=30r/m;
location / {
limit_req zone=webhooks burst=5;
# Optional: GitHub IP allowlisting
# allow 140.82.112.0/20;
# allow 143.55.64.0/20;
# allow 185.199.108.0/22;
# allow 192.30.252.0/22;
# deny all;
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
- Get SSL certificate:
sudo certbot certonly --nginx -d webhooks.yourdomain.com
- Enable and start:
sudo ln -s /etc/nginx/sites-available/webhooks /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Option 4: Direct Exposure (Not Recommended)
⚠️ Only use this for testing with tools like ngrok
# ngrok (temporary testing only)
ngrok http 3000
Never expose the webhook server directly to the internet in production.
Security Layers
Regardless of which exposure method you choose, always implement these security measures:
1. Webhook Signature Verification (Critical)
This is implemented in devintern and cannot be bypassed. GitHub signs every webhook with your secret:
# Set your webhook secret
export WEBHOOK_SECRET="your-random-secret-here"
The server will reject any request without a valid X-Hub-Signature-256 header.
2. GitHub IP Allowlisting (Recommended)
GitHub publishes their webhook IP ranges at https://api.github.com/meta. Current ranges (may change):
140.82.112.0/20
143.55.64.0/20
185.199.108.0/22
192.30.252.0/22
Dynamically fetch current IPs:
curl -s https://api.github.com/meta | jq '.hooks'
3. Rate Limiting
Prevent abuse even from valid sources. Recommended limits:
- 30 requests per minute per IP
- Burst of 5 requests
4. Firewall Rules (OS-level)
If using direct exposure or reverse proxy, add firewall rules:
# UFW (Ubuntu)
sudo ufw allow from 140.82.112.0/20 to any port 3000
sudo ufw allow from 143.55.64.0/20 to any port 3000
sudo ufw allow from 185.199.108.0/22 to any port 3000
sudo ufw allow from 192.30.252.0/22 to any port 3000
# iptables
iptables -A INPUT -p tcp --dport 3000 -s 140.82.112.0/20 -j ACCEPT
iptables -A INPUT -p tcp --dport 3000 -s 143.55.64.0/20 -j ACCEPT
iptables -A INPUT -p tcp --dport 3000 -j DROP
5. TLS/HTTPS (Required by GitHub)
GitHub requires HTTPS for production webhooks. All exposure options above provide automatic HTTPS except direct exposure.
GitHub App Configuration
Update App Permissions
Add these permissions to your GitHub App:
- Go to your GitHub App settings
- Navigate to Permissions & events
- Under Repository permissions, add:
- Pull request review comments: Read and write
- Under Subscribe to events, enable:
- Pull request review
- Pull request review comment
Configure Webhook
- In your GitHub App settings, go to Webhooks
- Set Webhook URL to your server’s URL (e.g.,
https://webhooks.yourdomain.com/webhooks/github) - Set Secret to your
WEBHOOK_SECRET - Select content type:
application/json - Enable events:
- Pull request reviews
- Pull request review comments
Running the Server
Environment Variables
# Required
export WEBHOOK_SECRET="your-webhook-secret"
# GitHub App authentication (same as PR creation)
export GITHUB_APP_ID="123456"
export GITHUB_APP_PRIVATE_KEY_PATH="/path/to/key.pem"
# OR
export GITHUB_APP_PRIVATE_KEY_BASE64="..."
# Optional
export WEBHOOK_PORT="3000" # Default: 3000
export WEBHOOK_HOST="0.0.0.0" # Default: 0.0.0.0
export WEBHOOK_AUTO_REPLY="true" # Reply to addressed comments
export WEBHOOK_AUTO_REVIEW="true" # Run self-review loop after addressing feedback
export WEBHOOK_AUTO_REVIEW_MAX_ITERATIONS="5" # Max review iterations (default: 5)
Start the Server
# Development
bun run src/webhook-server.ts
# Production (after build)
devintern serve --port 3000
# With PM2 (process manager)
pm2 start "devintern serve" --name devintern-webhooks
Systemd Service (Linux)
Create /etc/systemd/system/devintern-webhooks.service:
[Unit]
Description=@devintern/code Webhook Server
After=network.target
[Service]
Type=simple
User=your-user
WorkingDirectory=/path/to/your/projects
Environment=WEBHOOK_SECRET=your-secret
Environment=GITHUB_APP_ID=123456
Environment=GITHUB_APP_PRIVATE_KEY_PATH=/path/to/key.pem
Environment=WEBHOOK_AUTO_REPLY=true
Environment=WEBHOOK_AUTO_REVIEW=true
ExecStart=/usr/local/bin/devintern serve --port 3000
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable devintern-webhooks
sudo systemctl start devintern-webhooks
Monitoring & Troubleshooting
Logs
# Systemd
journalctl -u devintern-webhooks -f
# PM2
pm2 logs devintern-webhooks
Health Check
The server exposes a health endpoint:
curl https://webhooks.yourdomain.com/health
# {"status": "ok", "timestamp": "..."}
Test Webhook Delivery
- Go to your GitHub App settings → Advanced
- View Recent Deliveries
- Check response codes and bodies
- Use Redeliver to test
Common Issues
| Issue | Solution |
|---|---|
| 401 Unauthorized | Check WEBHOOK_SECRET matches GitHub App config |
| 403 Forbidden | Check IP allowlisting if enabled |
| 500 Internal Error | Check server logs for stack trace |
| Timeout | Ensure Agent Harness CLI is installed and working |
| No webhook received | Check GitHub App webhook URL and events |
Debug Mode
Run with verbose logging:
WEBHOOK_DEBUG=true devintern serve
Security Checklist
Before going to production:
- Webhook secret is set and matches GitHub App
- HTTPS is enabled (automatic with recommended options)
- Rate limiting is configured
- IP allowlisting is enabled (optional but recommended)
- Server runs as non-root user
- Logs are being collected
- Health monitoring is set up
- Firewall rules are configured (if applicable)
Quick Start Summary
Fastest secure setup (Cloudflare Tunnel):
# 1. Install cloudflared
brew install cloudflared # or appropriate package manager
# 2. Create tunnel
cloudflared tunnel login
cloudflared tunnel create devintern
cloudflared tunnel route dns devintern webhooks.yourdomain.com
# 3. Set environment
export WEBHOOK_SECRET=$(openssl rand -hex 32)
export GITHUB_APP_ID="your-app-id"
export GITHUB_APP_PRIVATE_KEY_PATH="/path/to/key.pem"
# 4. Start server
devintern serve &
# 5. Start tunnel
cloudflared tunnel run devintern
# 6. Configure GitHub App webhook URL to:
# https://webhooks.yourdomain.com/webhooks/github
Your webhook server is now securely exposed with zero open ports!