GitHub Entegrasyonu: Webhook ile Push Event Dinleme
Bir geliştirici takımını yönetirken en çok canınızı sıkan şeylerden biri nedir? Benim için cevap netti: “Kod depoya gitti mi, deployment tetiklendi mi, test koştu mu?” sorularını Slack’ten takip etmek. GitHub webhook’ları bu derdi büyük ölçüde ortadan kaldırıyor. Push event’leri dinleyerek otomatik deployment pipeline’ları kurabilir, kod kalite kontrollerini tetikleyebilir, ekibi anlık bildirimlerle haberdar edebilirsiniz. Bu yazıda sıfırdan bir webhook alıcısı kuracak, güvenliğini sağlayacak ve gerçek dünya senaryolarında nasıl kullanacağınızı adım adım göreceğiz.
GitHub Webhook Nedir ve Nasıl Çalışır
GitHub webhook’ları, belirli olaylar gerçekleştiğinde GitHub’ın sizin belirlediğiniz bir URL’ye HTTP POST isteği göndermesi mekanizmasıdır. Klasik polling yönteminde “bir şey değişti mi?” diye sürekli soru sorarsınız. Webhook modelinde ise GitHub size “şu an şu oldu” diye haber verir. Bu yaklaşım hem kaynak tüketimi açısından çok daha verimlidir hem de gerçek zamanlı tetikleme sağlar.
Push event özelinde konuşacak olursak: Bir geliştirici git push yaptığında GitHub, payload olarak adlandırılan JSON verisi içeren bir POST isteğini webhook endpoint’inize gönderir. Bu payload içinde commit bilgileri, hangi branch’e push yapıldığı, kim tarafından yapıldığı ve değişen dosyalar gibi son derece kullanışlı veriler bulunur.
Genel akış şu şekilde işler:
- Geliştirici kodu depoya push’lar
- GitHub webhook tetikleyicisi devreye girer
- Yapılandırdığınız URL’ye HTTP POST gönderilir
- Sunucunuz isteği alır, doğrular ve işler
- GitHub, sunucunuzun döndürdüğü HTTP durum kodunu kaydeder
Sunucu Tarafında Webhook Alıcısı Kurma
Önce webhook isteklerini karşılayacak basit bir HTTP sunucusuna ihtiyacınız var. Ben Python’un Flask framework’ünü tercih ediyorum çünkü hızlı kurulumu ve okunabilir kodu ile prototipleme için ideal. Üretim ortamı için aynı mantığı Node.js veya Go ile de uygulayabilirsiniz.
Sunucunuza Python ve Flask kurulumunu yapalım:
# Python pip kurulu değilse
apt update && apt install -y python3-pip python3-venv
# Proje dizini oluştur
mkdir -p /opt/webhook-server
cd /opt/webhook-server
# Virtual environment
python3 -m venv venv
source venv/bin/activate
# Gerekli paketleri kur
pip install flask gunicorn requests
Şimdi temel webhook alıcısını yazalım:
cat > /opt/webhook-server/app.py << 'EOF'
import hmac
import hashlib
import json
import logging
from flask import Flask, request, jsonify, abort
app = Flask(__name__)
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
logger = logging.getLogger(__name__)
WEBHOOK_SECRET = "buraya-guclu-bir-secret-yazin"
def verify_signature(payload_body, signature_header):
"""GitHub HMAC-SHA256 imza doğrulaması"""
if not signature_header:
return False
hash_object = hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
msg=payload_body,
digestmod=hashlib.sha256
)
expected_signature = "sha256=" + hash_object.hexdigest()
return hmac.compare_digest(expected_signature, signature_header)
@app.route('/webhook', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Hub-Signature-256')
if not verify_signature(request.data, signature):
logger.warning("Gecersiz imza, istek reddedildi")
abort(403)
event_type = request.headers.get('X-GitHub-Event')
payload = request.json
if event_type == 'push':
handle_push_event(payload)
elif event_type == 'ping':
logger.info("GitHub ping alindi, webhook aktif")
return jsonify({'status': 'ok'}), 200
def handle_push_event(payload):
branch = payload.get('ref', '').replace('refs/heads/', '')
pusher = payload.get('pusher', {}).get('name', 'bilinmiyor')
commits = payload.get('commits', [])
repo = payload.get('repository', {}).get('name', 'bilinmiyor')
logger.info(f"Push event: repo={repo}, branch={branch}, pusher={pusher}, commit_sayisi={len(commits)}")
for commit in commits:
logger.info(f"Commit: {commit['id'][:8]} - {commit['message'][:60]}")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
EOF
Systemd Servisi Olarak Çalıştırma
Webhook sunucusunu bir systemd servisi haline getirmeniz gerekiyor. Sunucu yeniden başladığında otomatik olarak ayağa kalkmalı ve çökmesi durumunda kendini restore etmeli.
cat > /etc/systemd/system/webhook-server.service << 'EOF'
[Unit]
Description=GitHub Webhook Server
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/webhook-server
Environment="PATH=/opt/webhook-server/venv/bin"
ExecStart=/opt/webhook-server/venv/bin/gunicorn
--workers 2
--bind 0.0.0.0:5000
--timeout 30
--access-logfile /var/log/webhook-server/access.log
--error-logfile /var/log/webhook-server/error.log
app:app
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# Log dizini oluştur
mkdir -p /var/log/webhook-server
chown www-data:www-data /var/log/webhook-server
# Servisi etkinleştir ve başlat
systemctl daemon-reload
systemctl enable webhook-server
systemctl start webhook-server
systemctl status webhook-server
Nginx Reverse Proxy Yapılandırması
Flask sunucusunu doğrudan internete açmak yerine Nginx arkasına almak hem güvenlik hem performans açısından doğru yaklaşım. Ayrıca HTTPS zorunlu hale getireceğiz çünkü webhook payload’ları hassas bilgiler içerebilir.
cat > /etc/nginx/sites-available/webhook << 'EOF'
server {
listen 80;
server_name webhook.sirketiniz.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name webhook.sirketiniz.com;
ssl_certificate /etc/letsencrypt/live/webhook.sirketiniz.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/webhook.sirketiniz.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# GitHub IP araliklari (opsiyonel ek guvenlik)
# allow 192.30.252.0/22;
# allow 185.199.108.0/22;
# allow 140.82.112.0/20;
# deny all;
location /webhook {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Hub-Signature-256 $http_x_hub_signature_256;
proxy_set_header X-GitHub-Event $http_x_github_event;
proxy_read_timeout 30s;
client_max_body_size 5M;
}
}
EOF
ln -s /etc/nginx/sites-available/webhook /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
GitHub’da Webhook Yapılandırması
Sunucu tarafı hazır olduğuna göre GitHub deposunda webhook’u tanımlamanın zamanı geldi.
- GitHub deposuna girin ve Settings sekmesine tıklayın
- Sol menüden Webhooks seçeneğini seçin
- Add webhook butonuna basın
- Payload URL alanına
https://webhook.sirketiniz.com/webhookyazın - Content type olarak
application/jsonseçin - Secret alanına
app.pyiçinde kullandığınız secret değerini yazın - Which events would you like to trigger this webhook? sorusu için Let me select individual events seçeneğini işaretleyin
- Listeden sadece Pushes kutucuğunu işaretleyin
- Active kutucuğunun işaretli olduğundan emin olun ve Add webhook ile kaydedin
Webhook kaydedildikten sonra GitHub otomatik olarak bir ping event’i gönderir. Sunucunuz bu ping’e 200 döndürürse webhook kurulumu başarılıdır. GitHub’ın webhook sayfasında son gönderim geçmişini ve response kodlarını görebilirsiniz. Bu kısım debug için son derece kullanışlı.
Güvenlik: Secret Token Doğrulaması
Webhook güvenliği çoğu zaman göz ardı edilir. Eğer endpoint’inizi doğru korumadan açarsanız, URL’yi bilen herkes sahte push event’leri göndererek deployment pipeline’ınızı tetikleyebilir. GitHub, her webhook isteğinin header’ına X-Hub-Signature-256 ekler. Bu değer, payload’ın shared secret ile HMAC-SHA256 kullanılarak imzalanmış halidir.
Daha önce yazdığımız verify_signature fonksiyonu bu doğrulamayı yapıyor. Burada dikkat edilmesi gereken kritik nokta: hmac.compare_digest kullanmak. Python’un normal == operatörü timing attack’lara karşı savunmasızdır. compare_digest ise sabit zamanlı karşılaştırma yaparak bu açığı kapatır.
Secret değerini doğrudan kod içinde tutmak yerine environment variable kullanmak daha iyi bir pratik:
# /etc/systemd/system/webhook-server.service dosyasini duzenle
# [Service] blogu altina ekle:
# EnvironmentFile=/opt/webhook-server/.env
cat > /opt/webhook-server/.env << 'EOF'
WEBHOOK_SECRET=buraya-gercekten-guclu-bir-secret-yazin
DEPLOY_BRANCH=main
REPO_PATH=/var/www/uygulamam
EOF
chmod 600 /opt/webhook-server/.env
chown www-data:www-data /opt/webhook-server/.env
Gerçek Dünya Senaryosu: Otomatik Deployment
İşte asıl eğlenceli kısım. Main branch’e push yapıldığında otomatik olarak deployment başlatan bir senaryo kuralım. Basit bir web uygulaması için şu akışı hedefliyoruz: push gelir, branch kontrol edilir, sunucuya git pull yapılır, servis restart edilir.
cat > /opt/webhook-server/deploy.sh << 'EOF'
#!/bin/bash
REPO_PATH="${1}"
SERVICE_NAME="${2}"
BRANCH="${3}"
LOG_FILE="/var/log/webhook-server/deploy.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "${LOG_FILE}"
}
log "Deployment basliyor: repo=${REPO_PATH}, branch=${BRANCH}"
if [ ! -d "${REPO_PATH}" ]; then
log "HATA: Repo dizini bulunamadi: ${REPO_PATH}"
exit 1
fi
cd "${REPO_PATH}" || exit 1
# Yerel degisiklikleri temizle (dikkatli kullanin!)
git fetch origin "${BRANCH}" 2>&1 | tee -a "${LOG_FILE}"
git reset --hard "origin/${BRANCH}" 2>&1 | tee -a "${LOG_FILE}"
if [ $? -ne 0 ]; then
log "HATA: git pull basarisiz oldu"
exit 1
fi
# Servisi yeniden baslat
systemctl restart "${SERVICE_NAME}"
if systemctl is-active --quiet "${SERVICE_NAME}"; then
log "Basarili: ${SERVICE_NAME} servisi yeniden baslatildi"
else
log "HATA: Servis baslatilirken sorun olustu"
exit 1
fi
log "Deployment tamamlandi"
EOF
chmod +x /opt/webhook-server/deploy.sh
Bu script’i webhook handler’ımıza entegre edelim:
cat > /opt/webhook-server/app.py << 'EOF'
import hmac
import hashlib
import json
import logging
import subprocess
import os
from flask import Flask, request, jsonify, abort
app = Flask(__name__)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s %(levelname)s %(message)s',
handlers=[
logging.FileHandler('/var/log/webhook-server/app.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET', '')
DEPLOY_BRANCH = os.environ.get('DEPLOY_BRANCH', 'main')
REPO_PATH = os.environ.get('REPO_PATH', '/var/www/uygulamam')
SERVICE_NAME = os.environ.get('SERVICE_NAME', 'myapp')
def verify_signature(payload_body, signature_header):
if not signature_header or not WEBHOOK_SECRET:
return False
hash_object = hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
msg=payload_body,
digestmod=hashlib.sha256
)
expected = "sha256=" + hash_object.hexdigest()
return hmac.compare_digest(expected, signature_header)
@app.route('/webhook', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Hub-Signature-256')
if not verify_signature(request.data, signature):
logger.warning(f"Gecersiz imza - IP: {request.remote_addr}")
abort(403)
event_type = request.headers.get('X-GitHub-Event')
payload = request.json
if event_type == 'ping':
return jsonify({'status': 'pong'}), 200
if event_type == 'push':
branch = payload.get('ref', '').replace('refs/heads/', '')
pusher = payload.get('pusher', {}).get('name', 'bilinmiyor')
commit_count = len(payload.get('commits', []))
logger.info(f"Push alindi: branch={branch}, pusher={pusher}, commits={commit_count}")
if branch == DEPLOY_BRANCH:
logger.info(f"Deployment tetikleniyor: {DEPLOY_BRANCH} branch'i icin")
result = subprocess.run(
['/opt/webhook-server/deploy.sh', REPO_PATH, SERVICE_NAME, branch],
capture_output=True,
text=True,
timeout=120
)
if result.returncode == 0:
logger.info("Deployment basarili tamamlandi")
return jsonify({'status': 'deployed'}), 200
else:
logger.error(f"Deployment basarisiz: {result.stderr}")
return jsonify({'status': 'deployment_failed'}), 500
else:
logger.info(f"Branch {branch} deployment icin yapilandirilmamis, atlanıyor")
return jsonify({'status': 'ok'}), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
EOF
Webhook Payload Analizi ve Debugging
Webhook geliştirirken en çok zaman kaybedilen konu payload yapısını anlamak. GitHub’ın push event payload’u oldukça zengin bir JSON yapısına sahip. Gelen tüm payload’ları geçici olarak diske yazmak debug sürecini çok hızlandırır:
# Debug modunda gecici payload kaydetme fonksiyonu
# app.py icindeki handle_push_event fonksiyonuna ekleyin
def save_debug_payload(event_type, payload):
"""Gelistirme ortaminda payload'lari kaydet"""
debug_dir = '/tmp/webhook-payloads'
os.makedirs(debug_dir, exist_ok=True)
from datetime import datetime
filename = f"{debug_dir}/{event_type}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
with open(filename, 'w') as f:
json.dump(payload, f, indent=2, ensure_ascii=False)
logger.debug(f"Payload kaydedildi: {filename}")
Gerçek bir test ortamında ngrok kullanarak yerel geliştirme makinenizde webhook test edebilirsiniz:
# ngrok kurulumu (Ubuntu)
curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null
echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | sudo tee /etc/apt/sources.list.d/ngrok.list
apt update && apt install ngrok
# Yerel Flask sunucusunu expose et
ngrok http 5000
# Cikan URL'yi (ornegin https://abc123.ngrok.io/webhook)
# GitHub webhook ayarlarinda Payload URL olarak kullan
GitHub’ın webhook sayfasından geçmiş istekleri ve response’ları inceleyebilirsiniz. Recent Deliveries sekmesi her isteğin tam header’ını, payload’ını ve sunucunuzdan dönen response’u gösterir. Bir isteği Redeliver butonu ile yeniden gönderebilirsiniz, bu özellik development sırasında çok işe yarıyor.
Güvenlik Katmanlarını Güçlendirme
Production ortamı için birkaç ek önlem daha almak gerekiyor. GitHub’ın webhook’ları gönderdiği IP aralıkları belgelenmiş durumda. Bu aralıkları firewall seviyesinde beyaz listeye alabilirsiniz:
# GitHub webhook IP aralikları icin UFW kurali
# Guncel liste icin: https://api.github.com/meta
ufw allow from 192.30.252.0/22 to any port 443
ufw allow from 185.199.108.0/22 to any port 443
ufw allow from 140.82.112.0/20 to any port 443
ufw allow from 143.55.64.0/20 to any port 443
# Diger kaynaklardan 443'u kapatirsaniz (dikkatli olun!)
# ufw deny 443
fail2ban ile kötü niyetli imza denemelerini otomatik olarak engelleyebilirsiniz:
cat > /etc/fail2ban/filter.d/webhook-server.conf << 'EOF'
[Definition]
failregex = Gecersiz imza - IP: <HOST>
ignoreregex =
EOF
cat >> /etc/fail2ban/jail.local << 'EOF'
[webhook-server]
enabled = true
port = 443
filter = webhook-server
logpath = /var/log/webhook-server/app.log
maxretry = 5
findtime = 300
bantime = 3600
EOF
systemctl restart fail2ban
İzleme ve Alerting
Webhook sisteminin çalışır durumda olduğunu izlemek kritik. Basit bir health check endpoint’i ekleyin ve bunu monitoring sistemine dahil edin:
# app.py'a health check endpoint ekle
@app.route('/health', methods=['GET'])
def health_check():
return jsonify({
'status': 'healthy',
'service': 'webhook-server',
'deploy_branch': DEPLOY_BRANCH
}), 200
Log analizini kolaylaştırmak için aşağıdaki komutu cron’a ekleyebilirsiniz. Her sabah bir önceki günün deployment özetini mail olarak göndermek küçük ama etkili bir pratik:
#!/bin/bash
# /opt/webhook-server/daily-report.sh
LOG_FILE="/var/log/webhook-server/app.log"
TARIH=$(date -d "yesterday" '+%Y-%m-%d')
echo "=== Webhook Gunluk Raporu: ${TARIH} ==="
echo ""
echo "Toplam push event:"
grep "${TARIH}" "${LOG_FILE}" | grep "Push alindi" | wc -l
echo ""
echo "Basarili deployment:"
grep "${TARIH}" "${LOG_FILE}" | grep "Deployment basarili" | wc -l
echo ""
echo "Basarisiz deployment:"
grep "${TARIH}" "${LOG_FILE}" | grep "Deployment basarisiz" | wc -l
echo ""
echo "Reddedilen istekler (gecersiz imza):"
grep "${TARIH}" "${LOG_FILE}" | grep "Gecersiz imza" | wc -l
Sonuç
GitHub webhook’ları ile push event dinlemek ilk bakışta karmaşık görünebilir ama parçalara ayırınca gayet anlaşılır bir yapı. Özetleyecek olursak:
- Flask ile HTTP endpoint kurarak GitHub’ın POST isteklerini karşıladık
- HMAC-SHA256 imza doğrulaması ile sahte istekleri filtreledik
- Systemd servisi ile webhook sunucusunu production-ready hale getirdik
- Nginx reverse proxy ile HTTPS zorunluluğu ve ek güvenlik katmanı ekledik
- Otomatik deployment script ile push event’ini aksiyona dönüştürdük
- fail2ban ve UFW ile güvenlik katmanlarını güçlendirdik
Bu yapıyı bir kez kurduğunuzda sadece push event değil, PR açılması, issue kapatılması, release yayınlanması gibi onlarca farklı GitHub event’ini aynı altyapıyla dinleyebilirsiniz. Kod kalite araçlarını entegre etmek, Slack bildirim sistemi kurmak veya multi-environment deployment yapmak bu temel üzerine kolayca inşa edilebilir.
Production ortamına geçmeden önce muhakkak geliştirme ortamında ngrok ile test edin, GitHub’ın Recent Deliveries sayfasından response’ları takip edin ve log seviyesini DEBUG modunda tutarak nelerin geldiğini gözlemleyin. Webhook sisteminiz bir kez doğru çalışmaya başladıktan sonra takımın verimliliğine yapacağı katkıyı çok hızlı fark edeceksiniz.
