n8n ile GitHub Webhook Entegrasyonu: Depo Olaylarını Otomatikleştirin
GitHub’dan bir push geldiğinde otomatik olarak deployment pipeline’ını tetiklemek, Slack’e bildirim göndermek ya da ticket sistemini güncellemek… Bunları manuel yapmak yerine n8n ile birkaç dakikada kurabilirsiniz. Ben bu entegrasyonu ilk kurduğumda “bu kadar mı basit?” diye şaşırmıştım, ama altında birkaç ince nokta var. Gelin bunları adım adım konuşalım.
n8n Nedir ve Neden GitHub Webhook ile Kullanıyoruz
n8n, self-hosted çalıştırabileceğiniz, görsel iş akışı otomasyon aracı. Zapier veya Make’e benziyor ama kodunuz sizin sunucunuzda kalıyor, veri dışarı çıkmıyor ve açık kaynak. Enterprise ortamlarda bu önemli bir kriter.
GitHub Webhook entegrasyonu ise şu anlama geliyor: GitHub’daki herhangi bir event (push, pull request, issue açılması, release vs.) gerçekleştiğinde GitHub doğrudan sizin n8n instance’ınıza HTTP POST isteği atar. n8n bu isteği yakalayıp sonraki adımları otomatik tetikler.
Neden bu kombinasyonu kullanıyoruz? Çünkü GitHub Actions’ın yapamadığı şeyler var. GitHub Actions, dış sistemlere erişmek için secret management gerektiriyor, her workflow değişikliği için commit atmak zorunda kalıyorsunuz ve logic karmaşıklaştığında YAML içinde boğuluyorsunuz. n8n ile bu logic’i görsel olarak kuruyorsunuz, değiştiriyorsunuz, test ediyorsunuz. Özellikle “GitHub’da bir PR merge olduğunda JIRA ticket’ını kapat, Confluence’a deployment notu ekle, on-call ekibine Slack mesajı at” gibi çok adımlı iş akışları için n8n çok daha uygun.
Ortam Kurulumu
Önce n8n’i Docker ile ayağa kaldıralım. Production için docker-compose kullanmanızı tavsiye ederim:
mkdir -p /opt/n8n && cd /opt/n8n
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
n8n:
image: docker.n8n.io/n8nio/n8n:latest
restart: always
ports:
- "5678:5678"
environment:
- N8N_HOST=n8n.sirketiniz.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.sirketiniz.com/
- N8N_ENCRYPTION_KEY=guclu-bir-encryption-key-buraya
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=guclu-db-sifresi
volumes:
- n8n_data:/home/node/.n8n
depends_on:
- postgres
postgres:
image: postgres:15-alpine
restart: always
environment:
- POSTGRES_DB=n8n
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=guclu-db-sifresi
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
n8n_data:
postgres_data:
EOF
docker-compose up -d
WEBHOOK_URL değişkeni kritik. n8n, webhook URL’lerini bu değere göre oluşturuyor. Yanlış girerseniz GitHub’ın atacağı POST isteği ulaşmaz. Dışarıdan erişilebilir bir domain ya da ngrok gibi bir tunnel çözümü kullanıyorsanız bu değeri ona göre ayarlayın.
Nginx reverse proxy için minimal bir config:
cat > /etc/nginx/sites-available/n8n << 'EOF'
server {
listen 443 ssl;
server_name n8n.sirketiniz.com;
ssl_certificate /etc/letsencrypt/live/n8n.sirketiniz.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/n8n.sirketiniz.com/privkey.pem;
location / {
proxy_pass http://localhost:5678;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300s;
}
}
EOF
ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
proxy_read_timeout 300s önemli. n8n bazı workflow’ları uzun süre çalıştırabilir, varsayılan 60 saniye timeout bu durumda bağlantıyı koparır.
GitHub Webhook Ayarları
n8n tarafında önce bir workflow oluşturun ve trigger olarak Webhook node’unu ekleyin. Node ayarlarında:
- HTTP Method: POST
- Path: github-events (ya da istediğiniz bir path)
- Authentication: Header Auth (önerilen)
n8n size şuna benzer bir URL verecek: https://n8n.sirketiniz.com/webhook/github-events
Bu URL’yi kopyalayın ve GitHub’a gidin. Repository Settings > Webhooks > Add webhook:
- Payload URL: n8n’den aldığınız URL
- Content type: application/json
- Secret: Güçlü bir secret oluşturun (bunu n8n’de de kullanacaksınız)
- Events: Hangi event’leri dinleyeceğinize karar verin
Secret için güvenli bir değer üretmek:
openssl rand -hex 32
# Örnek çıktı: a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1
Bu değeri hem GitHub webhook secret’ına hem de n8n workflow’unuzdaki ilgili alana giriyorsunuz.
Webhook İmzasını Doğrulama
Bu adımı atlayan çok sysadmin görüyorum. GitHub, her webhook isteğine X-Hub-Signature-256 header’ı ekler. Bu header, payload’ın secret ile HMAC-SHA256 hash’idir. Doğrulamadan payload’ı işlerseniz, webhook URL’nizi bilen herkes sahte event gönderebilir.
n8n’de Code node’u ile bu doğrulamayı yapabilirsiniz:
// n8n Code Node - GitHub Signature Doğrulama
const crypto = require('crypto');
const secret = 'github-webhook-secretiniz';
const payload = JSON.stringify($input.first().json);
const signature = $input.first().headers['x-hub-signature-256'];
if (!signature) {
throw new Error('X-Hub-Signature-256 header eksik');
}
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)) {
throw new Error('Geçersiz webhook imzası - Sahte istek olabilir!');
}
return $input.all();
timingSafeEqual kullanmak önemli. Normal string karşılaştırması timing attack’a açıktır.
Gerçek Dünya Senaryosu 1: Push Event ile Otomatik Deployment Bildirimi
En yaygın use case: main branch’e push geldiğinde deploy pipeline’ını tetikle ve takıma bildir.
n8n workflow’unda Webhook node’undan sonra IF node’u ekleyin. Koşul olarak şunu kullanın:
{{ $json.body.ref }} == "refs/heads/main"
Bu koşul sadece main branch push’larını geçirir. True branch’e devam eden akışta bir HTTP Request node’u ile deployment sisteminizi tetikleyebilirsiniz. Ardından Slack bildirimi için:
// Slack mesaj içeriğini hazırlayan Code node
const body = $input.first().json.body;
const pusher = body.pusher.name;
const commitCount = body.commits.length;
const branch = body.ref.replace('refs/heads/', '');
const repoName = body.repository.name;
const lastCommit = body.commits[body.commits.length - 1];
const commitMessages = body.commits
.slice(-3)
.map(c => `• ${c.message.split('n')[0]} (${c.author.name})`)
.join('n');
return [{
json: {
text: `:rocket: *${repoName}* reposuna deploy başlatıldı!`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `:rocket: *${repoName}* - `${branch}` branch deploy edildi`
}
},
{
type: 'section',
fields: [
{ type: 'mrkdwn', text: `*Kim:* ${pusher}` },
{ type: 'mrkdwn', text: `*Commit sayısı:* ${commitCount}` }
]
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*Son commitler:*n${commitMessages}`
}
}
]
}
}];
Gerçek Dünya Senaryosu 2: Pull Request Event ile JIRA Entegrasyonu
PR açıldığında veya merge edildiğinde JIRA ticket’ını otomatik güncellemek. Bu senaryoda branch adından ticket numarasını parse ediyoruz. Örneğin feature/PROJ-1234-yeni-ozellik gibi branch adları varsa:
// Branch adından JIRA ticket numarası çıkarma
const body = $input.first().json.body;
const action = body.action; // opened, closed, merged
const branchName = body.pull_request.head.ref;
const prTitle = body.pull_request.title;
const prUrl = body.pull_request.html_url;
const merged = body.pull_request.merged;
// JIRA ticket pattern: PROJE-1234
const ticketPattern = /([A-Z]+-d+)/g;
const ticketsInBranch = branchName.match(ticketPattern) || [];
const ticketsInTitle = prTitle.match(ticketPattern) || [];
const allTickets = [...new Set([...ticketsInBranch, ...ticketsInTitle])];
if (allTickets.length === 0) {
// Ticket bulunamadı, workflow'u durdur
return [];
}
let jiraStatus = null;
let jiraComment = null;
if (action === 'opened') {
jiraStatus = 'In Review';
jiraComment = `PR açıldı: ${prUrl}`;
} else if (action === 'closed' && merged) {
jiraStatus = 'Done';
jiraComment = `PR merge edildi: ${prUrl}`;
} else if (action === 'closed' && !merged) {
jiraComment = `PR kapatıldı (merge edilmeden): ${prUrl}`;
}
return allTickets.map(ticket => ({
json: {
ticketKey: ticket,
status: jiraStatus,
comment: jiraComment,
prUrl: prUrl
}
}));
Bu node’dan çıkan her item için JIRA API’ye ayrı istek atılacak. n8n’de Split In Batches node’u veya Item Lists node’unu kullanarak bu map işlemini yönetebilirsiniz.
Gerçek Dünya Senaryosu 3: Release Event ile Otomatik Changelog
GitHub’da bir release publish edildiğinde e-posta göndermek ve Confluence’a belge eklemek için:
// Release event'ini işleyen Code node
const body = $input.first().json.body;
if (body.action !== 'published') {
return []; // Sadece published event'ini işle
}
const release = body.release;
const repo = body.repository;
// Markdown'ı basit bir şekilde parse et
const releaseBody = release.body || 'Changelog belirtilmemiş.';
// E-posta için HTML'e çevir (basit versiyon)
const htmlBody = releaseBody
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
.replace(/^* (.+)$/gm, '<li>$1</li>')
.replace(/^- (.+)$/gm, '<li>$1</li>')
.replace(/nn/g, '</p><p>')
.replace(/`([^`]+)`/g, '<code>$1</code>');
return [{
json: {
releaseName: release.name || release.tag_name,
tagName: release.tag_name,
repoName: repo.full_name,
releaseUrl: release.html_url,
publishedAt: release.published_at,
author: release.author.login,
isPrerelease: release.prerelease,
rawBody: releaseBody,
htmlBody: htmlBody,
subject: `[${repo.name}] ${release.tag_name} Sürümü Yayınlandı`
}
}];
Hata Yönetimi ve Retry Mekanizması
Production’da dikkat edilmesi gereken önemli bir konu: n8n webhook’ları varsayılan olarak retry yapmaz. GitHub ise webhook’u iletirken başarısız olursa birkaç kez dener. Bu durumda aynı event birden fazla işlenebilir. Idempotency sağlamak için:
// Event ID'yi cache'de kontrol eden Code node
// n8n'de Static Data kullanarak basit idempotency
const deliveryId = $input.first().headers['x-github-delivery'];
const processedEvents = $getWorkflowStaticData('global');
if (!processedEvents.deliveries) {
processedEvents.deliveries = {};
}
// 1 saatten eski kayıtları temizle
const oneHourAgo = Date.now() - (60 * 60 * 1000);
Object.keys(processedEvents.deliveries).forEach(id => {
if (processedEvents.deliveries[id] < oneHourAgo) {
delete processedEvents.deliveries[id];
}
});
if (processedEvents.deliveries[deliveryId]) {
// Bu event zaten işlendi
console.log(`Tekrar eden event atlandı: ${deliveryId}`);
return [];
}
processedEvents.deliveries[deliveryId] = Date.now();
return $input.all();
Bu yaklaşım küçük-orta ölçekli kullanım için yeterli. Yüksek hacimli bir ortamda Redis veya veritabanı tabanlı bir çözüme geçmeniz gerekebilir.
Monitoring ve Alerting
Workflow’larınızın sağlıklı çalıştığını nasıl anlayacaksınız? n8n’in execution log’larını izlemek için basit bir cron job:
#!/bin/bash
# /opt/scripts/n8n-health-check.sh
N8N_URL="https://n8n.sirketiniz.com"
API_KEY="n8n-api-keyiniz"
ALERT_EMAIL="[email protected]"
# Son 1 saatteki başarısız execution'ları çek
FAILED_COUNT=$(curl -s
-H "X-N8N-API-KEY: ${API_KEY}"
"${N8N_URL}/api/v1/executions?status=error&limit=100"
| jq '.data | length')
if [ "$FAILED_COUNT" -gt 5 ]; then
echo "Uyarı: Son 1 saatte ${FAILED_COUNT} başarısız workflow execution"
| mail -s "[UYARI] n8n Workflow Hataları" "$ALERT_EMAIL"
fi
# Webhook endpoint sağlık kontrolü
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}"
-X POST
-H "Content-Type: application/json"
-d '{"test": true}'
"${N8N_URL}/webhook-test/github-events")
if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "404" ]; then
echo "n8n webhook endpoint erişilemiyor. HTTP: ${HTTP_CODE}"
| mail -s "[KRİTİK] n8n Erişim Sorunu" "$ALERT_EMAIL"
fi
# Crontab'a ekleyin
crontab -e
# Şu satırı ekleyin:
# */15 * * * * /opt/scripts/n8n-health-check.sh >> /var/log/n8n-health.log 2>&1
Güvenlik Notları
Birkaç kritik güvenlik noktasını listeleyelim:
- Webhook URL’ini gizli tutun: URL tahmin edilemez olsun diye path kısmına rastgele string ekleyin (
/webhook/gh-a3f8b2c1d4e5gibi) - Secret rotation: GitHub webhook secret’ını düzenli aralıklarla değiştirin. Rotasyon sırasında kısa bir süre her iki secret’ı da kabul eden bir geçiş süreci uygulayın
- Rate limiting: Nginx seviyesinde webhook endpoint’ine rate limiting ekleyin, kötü niyetli toplu istek saldırılarına karşı önlem alın
- IP whitelist: GitHub’ın webhook IP aralıklarını
https://api.github.com/metaendpoint’inden çekip sadece bu IP’lerden gelen isteklere izin verebilirsiniz - n8n API key yönetimi: n8n API key’lerini environment variable olarak tutun, workflow içine gömmeyin
# GitHub webhook IP'lerini çekip firewall kuralı oluşturma
GITHUB_IPS=$(curl -s https://api.github.com/meta | jq -r '.hooks[]')
for ip in $GITHUB_IPS; do
ufw allow from "$ip" to any port 443 comment "GitHub Webhook"
done
Sonuç
n8n ile GitHub webhook entegrasyonu başlangıçta kolay görünüyor ama production’da sağlam çalışması için dikkat edilmesi gereken noktalar var: imza doğrulama, idempotency, hata yönetimi ve monitoring. Bu adımları atlamak başlangıçta zaman kazandırıyor ama bir gece sizi uyandıracak bir incident olarak geri dönüyor.
Benim önerim şu: Önce tek bir basit use case ile başlayın, örneğin “main’e push gelince Slack’e bildirim at”. Bunu sağlam kurun, monitor edin, birkaç hafta gözlemleyin. Sonra daha karmaşık workflow’ları üstüne ekleyin. GitHub Actions’ın güçlü olduğu yerlerde onu kullanmaya devam edin, n8n’i dış sistem entegrasyonları ve karmaşık conditional logic için ayırın. İkisini birbirinin rakibi değil, tamamlayıcısı olarak düşünün.
Son bir not: n8n workflow’larını da kod gibi yönetin. Export edin, Git’e koyun, değişikliklerini takip edin. Yarın n8n container’ı çöktüğünde veya yanlışlıkla workflow’u sildiğinizde backup olmadığını fark etmek istemezsiniz.
