Webhook Nedir: Olay Tabanlı Entegrasyon Temelleri
Bir uygulama başka bir uygulamadan veri almak istediğinde aklınıza ilk gelen şey muhtemelen “her 5 dakikada bir API’yi çağırırım” oluyor. Bu yaklaşım işe yarıyor, evet. Ama bir e-ticaret sisteminde her saniye binlerce sipariş geliyorsa, bir CI/CD pipeline’ında her commit’te build tetikleniyorsa ya da bir ödeme sisteminde anlık bildirim gerekiyorsa polling (yoklama) yöntemi hem verimsiz hem de pahalı hale geliyor. İşte bu noktada webhook kavramı devreye giriyor ve “sen beni ara, ben seni ararım” felsefesini tersine çeviriyor.
Webhook Nedir, Ne Değildir
Webhook, bir olay gerçekleştiğinde bir sistemin başka bir sisteme HTTP POST isteği göndermesidir. Teknik jargonu bir kenara bırakırsak: bir şey olduğunda seni haberdar etmek için kullanılan bir mekanizma.
Klasik API mantığında siz sürekli sunucuya “yeni bir şey var mı?” diye soruyorsunuz. Webhook’ta ise sunucu size “yeni bir şey oldu, işte detaylar” diyor. Bu farka push vs pull deniyor ve bu fark ciddi mimari sonuçlar doğuruyor.
Gündelik hayattan örnekler verelim:
- GitHub’da bir pull request açıldığında Slack kanalınıza mesaj gönderilmesi
- Stripe’ta bir ödeme başarısız olduğunda muhasebe sisteminin otomatik tetiklenmesi
- Docker Hub’da yeni bir image push edildiğinde production sunucunun güncellenmesi
- Shopify’da bir sipariş verildiğinde kargo sistemine kayıt atılması
Bunların hepsi webhook’un gerçek dünya kullanım senaryoları.
Webhook’un Teknik Anatomisi
Bir webhook akışını kafanızda canlandırmak için şu adımları takip edin:
1. Kayıt (Registration): Webhook alan taraf (consumer), webhook gönderen tarafa (provider) bir URL verir. “Bu URL’e event geldiğinde POST at” der.
2. Olay Tetiklenmesi: Provider sisteminde bir olay gerçekleşir. Örneğin GitHub’da yeni bir commit gelir.
3. HTTP POST Gönderimi: Provider, belirlenen URL’e JSON payload içeren bir POST isteği gönderir.
4. Yanıt ve Onay: Consumer bu isteği alır, işler ve genellikle 200 OK döner. Provider başarıyı onaylar.
5. Retry Mekanizması: Consumer 200 dönmezse provider belirli aralıklarla yeniden göndermeyi dener.
Basit bir webhook payload’u şöyle görünür:
# Bir GitHub push event payload örneği
curl -X POST https://yourdomain.com/webhook/github
-H "Content-Type: application/json"
-H "X-GitHub-Event: push"
-H "X-Hub-Signature-256: sha256=abc123..."
-d '{
"ref": "refs/heads/main",
"repository": {
"name": "my-project",
"full_name": "username/my-project"
},
"pusher": {
"name": "johndoe"
},
"commits": [
{
"id": "abc123def456",
"message": "Fix critical bug in payment module",
"author": {
"name": "John Doe",
"email": "[email protected]"
}
}
]
}'
Basit Bir Webhook Receiver Yazmak
Teoriden pratiğe geçelim. Python Flask ile minimal bir webhook receiver yazıyoruz:
# Gerekli paketleri kur
pip install flask hmac hashlib
# webhook_server.py dosyasini olustur
cat > webhook_server.py << 'EOF'
from flask import Flask, request, jsonify
import hmac
import hashlib
import json
app = Flask(__name__)
WEBHOOK_SECRET = "supersecretkey123"
@app.route('/webhook', methods=['POST'])
def handle_webhook():
# Signature dogrulama
signature = request.headers.get('X-Hub-Signature-256', '')
payload = request.get_data()
expected_sig = 'sha256=' + hmac.new(
WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected_sig):
return jsonify({'error': 'Invalid signature'}), 401
data = request.get_json()
event_type = request.headers.get('X-GitHub-Event', 'unknown')
print(f"Event: {event_type}")
print(f"Repository: {data.get('repository', {}).get('full_name', 'N/A')}")
# Event tipine gore islem yap
if event_type == 'push':
handle_push_event(data)
elif event_type == 'pull_request':
handle_pr_event(data)
return jsonify({'status': 'processed'}), 200
def handle_push_event(data):
branch = data.get('ref', '').replace('refs/heads/', '')
commits = data.get('commits', [])
print(f"Push to {branch}: {len(commits)} commit(s)")
def handle_pr_event(data):
action = data.get('action', '')
pr_title = data.get('pull_request', {}).get('title', '')
print(f"PR {action}: {pr_title}")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
EOF
python webhook_server.py
Bu basit ama güvenli bir başlangıç noktası. Şimdi daha önemli bir konuya geçelim: güvenlik.
Webhook Güvenliği: İmzalama ve Doğrulama
Webhook’ların en büyük güvenlik açığı şu: herhangi biri sizin endpoint’inize istek gönderebilir. “Ben GitHub’um” diyen her isteğe inanmak saflık olur. Bu yüzden provider’lar HMAC imzalama kullanır.
Mantık şu: Provider ve Consumer aynı secret key’i biliyor. Provider her isteği bu key ile imzalıyor, Consumer ise gelen imzayı doğruluyor.
# Bash ile HMAC signature hesaplama
PAYLOAD='{"event": "payment.completed", "amount": 150.00}'
SECRET="mysecretkey"
# HMAC-SHA256 hesapla
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)
echo "Signature: sha256=$SIGNATURE"
# Test icin webhook endpoint'e gonder
curl -X POST http://localhost:5000/webhook
-H "Content-Type: application/json"
-H "X-Signature: sha256=$SIGNATURE"
-d "$PAYLOAD"
Güvenlik için birkaç kritik nokta daha:
- HTTPS zorunlu: Webhook URL’iniz mutlaka HTTPS olmalı. HTTP üzerinden gelen webhook verisi dinlenebilir.
- IP whitelist: Provider’ın IP adreslerini biliyorsanız sadece o IP’lerden gelen isteklere izin verin.
- Timestamp kontrolü: Replay attack’ları önlemek için payload’da timestamp bulundurun ve 5 dakikadan eski istekleri reddedin.
- Secret rotation: Webhook secret’larını düzenli olarak değiştirin.
# Nginx ile webhook endpoint icin IP whitelist
# /etc/nginx/conf.d/webhook.conf
cat > /etc/nginx/conf.d/webhook.conf << 'EOF'
server {
listen 443 ssl;
server_name webhook.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/webhook.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/webhook.yourdomain.com/privkey.pem;
location /webhook/github {
# GitHub'in IP araliglarini whitelist et
allow 192.30.252.0/22;
allow 185.199.108.0/22;
allow 140.82.112.0/20;
deny all;
proxy_pass http://localhost:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
EOF
nginx -t && systemctl reload nginx
Gerçek Dünya Senaryosu: CI/CD Pipeline Webhook
Bir startup’ta şöyle bir senaryo yaşadım: geliştirici takımı her gün onlarca kez main branch’e merge yapıyordu ve deploy işlemi tamamen manueldi. Birisi “şimdi deploy atsak mı?” diyordu, biri sunucuya bağlanıyordu, git pull, systemctl restart… Saat fark varsa uyandırmak gerekiyordu. Webhook ile bu acıyı tamamen ortadan kaldırdık.
#!/bin/bash
# /opt/scripts/deploy.sh
# GitHub webhook'tan tetiklenen deploy scripti
REPO_DIR="/var/www/myapp"
LOG_FILE="/var/log/auto-deploy.log"
BRANCH="main"
APP_USER="appuser"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}
# JSON payload'u parse et
PAYLOAD=$(cat)
PUSHED_BRANCH=$(echo "$PAYLOAD" | python3 -c "import sys,json; data=json.load(sys.stdin); print(data.get('ref','').replace('refs/heads/',''))")
if [ "$PUSHED_BRANCH" != "$BRANCH" ]; then
log "Push to $PUSHED_BRANCH branch, skipping deploy"
exit 0
fi
log "Deploy triggered for branch: $BRANCH"
cd "$REPO_DIR" || {
log "ERROR: Cannot access $REPO_DIR"
exit 1
}
# Mevcut commit'i kaydet (rollback icin)
PREV_COMMIT=$(git rev-parse HEAD)
log "Previous commit: $PREV_COMMIT"
# Yeni kodu cek
sudo -u "$APP_USER" git pull origin "$BRANCH" 2>> "$LOG_FILE"
if [ $? -ne 0 ]; then
log "ERROR: git pull failed"
exit 1
fi
NEW_COMMIT=$(git rev-parse HEAD)
log "New commit: $NEW_COMMIT"
# Dependency guncelle
sudo -u "$APP_USER" pip install -r requirements.txt -q
# Test calistir
sudo -u "$APP_USER" python3 -m pytest tests/ -q 2>> "$LOG_FILE"
if [ $? -ne 0 ]; then
log "ERROR: Tests failed, rolling back to $PREV_COMMIT"
sudo -u "$APP_USER" git checkout "$PREV_COMMIT"
exit 1
fi
# Servisi yeniden baslat
systemctl restart myapp.service
log "Deploy completed successfully: $PREV_COMMIT -> $NEW_COMMIT"
Webhook Güvenilirliği: Idempotency ve Retry Mantığı
Webhook’larla ilgili sysadmin’lerin sıkça gözden kaçırdığı bir konu: aynı webhook birden fazla kez gelebilir. Provider retry mekanizması devreye girdiğinde veya network kısa süre kesildiğinde sisteminiz aynı event’i 2, 3 hatta 5 kez alabilir.
Bu durumda ne olur? Eğer webhook “sipariş verildi, stoktan düş” mantığı çalıştırıyorsa ve 3 kez çalışırsa stoğunuz 3 kat düşer. Felakettir.
Çözüm: Idempotency (Tekrarlanabilirlik). Her event için benzersiz bir ID kullanın ve aynı ID ile gelen isteği bir kez işleyin.
# Redis ile idempotency kontrolu
cat > idempotency_middleware.py << 'EOF'
import redis
import hashlib
from functools import wraps
from flask import request, jsonify
r = redis.Redis(host='localhost', port=6379, db=0)
IDEMPOTENCY_TTL = 86400 # 24 saat
def idempotent_webhook(f):
@wraps(f)
def decorated(*args, **kwargs):
# Event ID'yi headerdan al (GitHub, Stripe vs. saglar)
event_id = request.headers.get('X-GitHub-Delivery') or
request.headers.get('Stripe-Signature') or
hashlib.md5(request.get_data()).hexdigest()
redis_key = f"webhook:processed:{event_id}"
# Daha once islendi mi?
if r.exists(redis_key):
print(f"Duplicate event ignored: {event_id}")
return jsonify({'status': 'already_processed'}), 200
# Islemi yap
result = f(*args, **kwargs)
# Islendi olarak isaretle
r.setex(redis_key, IDEMPOTENCY_TTL, 'processed')
return result
return decorated
# Kullanim
@app.route('/webhook', methods=['POST'])
@idempotent_webhook
def handle_webhook():
# ...
pass
EOF
Webhook Debug ve Test Araçları
Geliştirme aşamasında webhook test etmek can sıkıcı olabilir. Local sunucunuz internetten erişilemez, her test için deploy gerekmiyor. Bu noktada birkaç araç hayat kurtarır.
ngrok ile local webhook testing:
# ngrok kur
wget https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz
tar xvzf ngrok-v3-stable-linux-amd64.tgz
sudo mv ngrok /usr/local/bin/
# Local servisi internete ac
ngrok http 5000
# Cikan URL'i webhook olarak kaydet
# Ornek: https://abc123.ngrok.io/webhook
# Bu URL'e gelen istekler localhost:5000'e yonlendirilir
webhook.site ile payload inceleme:
Bazen sadece “bu webhook bana ne gönderiyor?” sorusunu yanıtlamak istiyorsunuz. webhook.site’e gidin, size benzersiz bir URL verir, o URL’i webhook olarak kaydedin ve gelen tüm istekleri gerçek zamanlı görün.
Manuel test için curl:
# Stripe webhook simule et
curl -X POST http://localhost:5000/webhook/stripe
-H "Content-Type: application/json"
-H "Stripe-Signature: t=1614556800,v1=abc123"
-d '{
"id": "evt_test_001",
"type": "payment_intent.succeeded",
"data": {
"object": {
"id": "pi_test_001",
"amount": 2000,
"currency": "usd",
"status": "succeeded"
}
}
}'
# Sonuc kodunu ve response'u goster
echo "Exit code: $?"
Webhook Queue Sistemi: Asenkron İşleme
Bir webhook aldığınızda ne kadar sürede 200 dönmelisiniz? Provider’lar genellikle 3-30 saniye arasında bir timeout bekler. Eğer bu sürede yanıt gelmezse request başarısız sayılır ve retry başlar.
Peki ya webhook işleminiz 2 dakika sürüyorsa? Veritabanı migrasyonu, harici API çağrısı, rapor oluşturma… Bu durumlarda asenkron işleme şart.
Mantık şu: Webhook gelir, hemen kuyruğa atılır, 200 dönersiniz. Arka planda worker kuyruğu işler.
# Redis + Celery ile asenkron webhook isleme
cat > celery_webhook.py << 'EOF'
from celery import Celery
from flask import Flask, request, jsonify
import json
app = Flask(__name__)
celery = Celery('webhooks', broker='redis://localhost:6379/1')
@celery.task(bind=True, max_retries=3)
def process_webhook_task(self, event_type, payload):
try:
if event_type == 'payment.succeeded':
# Uzun suren islem simule et
update_inventory(payload)
send_confirmation_email(payload)
update_crm(payload)
elif event_type == 'subscription.cancelled':
handle_churn(payload)
except Exception as exc:
# Hata durumunda retry
raise self.retry(exc=exc, countdown=60 * (self.request.retries + 1))
@app.route('/webhook/stripe', methods=['POST'])
def stripe_webhook():
payload = request.get_json()
event_type = payload.get('type', '')
# Hemen kuyruğa at, 200 don
process_webhook_task.delay(event_type, payload)
return jsonify({'status': 'queued'}), 200
# Worker'i baslat
# celery -A celery_webhook worker --loglevel=info
EOF
Webhook Monitoring ve Alerting
Production’da webhook’ları izlememek kör uçmak gibi. Şu metrikleri mutlaka takip edin:
- Saatlik/günlük webhook sayısı (anomali tespiti için)
- Başarısız webhook oranı
- Ortalama işlem süresi
- Retry sayısı (provider retry başlattıysa bir yerde sorun var demektir)
#!/bin/bash
# webhook_health_check.sh
# Cron ile her 5 dakikada calistir
LOG_FILE="/var/log/webhook.log"
ALERT_EMAIL="[email protected]"
ERROR_THRESHOLD=10
# Son 5 dakikadaki hata sayisini say
RECENT_ERRORS=$(grep "ERROR" "$LOG_FILE" |
awk -v cutoff="$(date -d '5 minutes ago' '+%Y-%m-%d %H:%M')"
'$0 >= cutoff' | wc -l)
if [ "$RECENT_ERRORS" -gt "$ERROR_THRESHOLD" ]; then
MESSAGE="ALERT: $RECENT_ERRORS webhook errors in last 5 minutes"
echo "$MESSAGE" | mail -s "Webhook Alert" "$ALERT_EMAIL"
# Slack'e de bildir
curl -s -X POST "$SLACK_WEBHOOK_URL"
-H 'Content-Type: application/json'
-d "{"text": "$MESSAGE", "username": "webhook-monitor"}"
fi
# Basarisiz webhook yuzdesini hesapla
TOTAL=$(grep -c "webhook received" "$LOG_FILE" | tail -100)
FAILED=$(grep -c "ERROR|failed|timeout" "$LOG_FILE" | tail -100)
if [ "$TOTAL" -gt 0 ]; then
FAILURE_RATE=$(( FAILED * 100 / TOTAL ))
echo "Webhook failure rate: %$FAILURE_RATE"
if [ "$FAILURE_RATE" -gt 5 ]; then
echo "HIGH FAILURE RATE: %$FAILURE_RATE" | mail -s "Webhook Health Alert" "$ALERT_EMAIL"
fi
fi
Yaygın Hatalar ve Çözümleri
Webhook kurarken sysadmin’lerin en sık yaptığı hatalar:
Timeout sorunları: Webhook işleminiz yavaş çalışıyorsa ve provider timeout alıyorsa durmadan retry gelir. Çözüm: yukarıda anlattığım asenkron işleme.
SSL sertifika sorunları: Self-signed sertifika kullanan bir endpoint’e webhook kurmaya çalışmak. Büyük provider’ların çoğu self-signed sertifikayı reddeder. Let’s Encrypt kullanın, ücretsiz ve otomatik yenilenebilir.
Firewall engellemesi: Cloud provider’ların webhook IP aralıklarını güvenlik duvarınızda açmayı unutmak. GitHub, Stripe, Slack bunların hepsinin dokümante edilmiş IP aralıkları var.
Secret key hardcoding: Webhook secret’ı doğrudan kod içine yazmak. Environment variable kullanın.
# Yanlis
WEBHOOK_SECRET = "hardcoded_secret_123"
# Dogru
import os
WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET')
if not WEBHOOK_SECRET:
raise ValueError("WEBHOOK_SECRET environment variable not set")
Response body boyutu: Bazı sysadmin’ler webhook response’unda çok fazla data dönüyor. Provider genellikle response body’yi görmezden gelir, sadece status code’a bakar. Minimal tutun.
Sonuç
Webhook, modern yazılım mimarisinin temel yapı taşlarından biri. Polling’e kıyasla daha az kaynak tüketiyor, daha anlık tepki veriyor ve daha ölçeklenebilir bir yapı sunuyor. Ancak bu kolaylığın altında ciddi bir sorumluluk yatıyor: güvenlik, güvenilirlik ve idempotency konularında tembellik yapılamaz.
Özetlemek gerekirse:
- İmzalamayı atlama: Her gelen isteği doğrula, asla güvenme.
- Asenkron işlemeyi benimse: Uzun süren işler için hemen kuyruğa at, sonra işle.
- Idempotency sağla: Aynı event birden fazla geldiğinde sisteminiz bundan etkilenmemeli.
- Monitoring kur: Webhook’lar sessizce başarısız olabilir, bunu fark etmek için ölçüm şart.
- Test et: ngrok gibi araçlarla local geliştirme sürecinde de webhook’ları gerçekçi biçimde test et.
Webhook entegrasyonlarını bir kez doğru kurduğunuzda manual süreçlerin, gecenin körü gelen deploy telefonlarının ve “sistem neden güncellenmiyor?” sorularının büyük bir kısmından kurtuluyorsunuz. Bu özgürlük, kurulum sırasındaki ekstra zahmete fazlasıyla değiyor.
