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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir