Webhook Versiyonlama ve Geriye Dönük Uyumluluk Yönetimi
Üretim ortamında bir webhook entegrasyonu çalışırken payload yapısını değiştirmek zorunda kaldığınızda ne hissediyorsunuz? Eğer bu konuda planlı bir versiyonlama stratejiniz yoksa, muhtemelen “şimdi hangi servisi kırdım acaba?” diye düşünüyorsunuzdur. Webhook versiyonlama, çoğu ekibin API geliştirme sürecinde en son düşündüğü ama en çok baş ağrıtan konuların başında gelir. Bu yazıda gerçek dünya senaryolarıyla webhook versiyonlama stratejilerini, geriye dönük uyumluluğu nasıl koruyacağınızı ve migration süreçlerini nasıl yöneteceğinizi ele alacağız.
Webhook Versiyonlamanın Temel Mantığı
Bir webhook esasen sizin sisteminizin dışarıya “hey, şu olay gerçekleşti, işte veriler” diye bağırdığı bir HTTP POST isteğidir. Sorun şu ki bu bağırdığınız yeri dinleyen onlarca, belki yüzlerce farklı istemci sistemi olabilir. Siz payload yapısını değiştirdiğinizde, bu sistemlerin tamamı etkilenir.
Versiyonlama stratejisi temelde üç soruya cevap vermelidir:
- Hangi versiyonu hangi istemci kullanıyor?
- Eski ve yeni versiyonlar paralel çalışabilecek mi?
- Bir versiyonu ne zaman emekliye ayıracaksınız?
Bu soruları cevaplamadan başladığınız her webhook implementasyonu, ilerleyen dönemde teknik borç olarak karşınıza çıkar.
Versiyonlama Yaklaşımları
URL Tabanlı Versiyonlama
En yaygın ve en anlaşılır yöntemdir. Webhook endpoint’inize istek atarken URL içinde versiyon bilgisi taşırsınız.
# v1 endpoint
POST https://api.yourservice.com/webhooks/v1/orders
# v2 endpoint
POST https://api.yourservice.com/webhooks/v2/orders
Nginx tarafında bu trafiği yönetmek için basit bir upstream konfigürasyonu yazabilirsiniz:
# /etc/nginx/sites-available/webhook-routing.conf
upstream webhook_v1 {
server 127.0.0.1:8081;
}
upstream webhook_v2 {
server 127.0.0.1:8082;
}
server {
listen 443 ssl;
server_name api.yourservice.com;
location /webhooks/v1/ {
proxy_pass http://webhook_v1;
proxy_set_header X-Webhook-Version "1";
proxy_set_header X-Forwarded-For $remote_addr;
}
location /webhooks/v2/ {
proxy_pass http://webhook_v2;
proxy_set_header X-Webhook-Version "2";
proxy_set_header X-Forwarded-For $remote_addr;
}
}
Header Tabanlı Versiyonlama
Bazı ekipler URL’yi temiz tutmak için versiyon bilgisini HTTP header’larında taşımayı tercih eder. Bu yaklaşım daha esnektir ama istemci tarafında daha fazla dikkat gerektirir.
# Header tabanlı versiyon bilgisi gönderimi
curl -X POST https://api.yourservice.com/webhooks/orders
-H "Content-Type: application/json"
-H "X-Webhook-Version: 2024-01-15"
-H "X-API-Key: your-secret-key"
-d '{"event": "order.created", "data": {...}}'
Tarih tabanlı versiyonlama (Stripe’ın kullandığı yöntem) özellikle breaking change’lerin ne zaman yapıldığını takip etmek için çok kullanışlıdır. “2024-01-15 sonrası” gibi ifadeler hem teknik ekip hem de müşteriler için anlamlıdır.
Payload İçi Versiyonlama
Her iki yöntemi de beğenmiyorsanız versiyon bilgisini payload’ın içine gömmek de mümkündür:
# Payload içi versiyonlama örneği
cat > sample_payload.json << 'EOF'
{
"version": "2",
"schema_version": "2024-01-15",
"event_type": "order.completed",
"event_id": "evt_8f9a2b3c4d5e",
"timestamp": "2024-01-15T14:30:00Z",
"data": {
"order_id": "ord_12345",
"customer": {
"id": "cust_67890",
"email": "[email protected]"
},
"total_amount": 299.99,
"currency": "TRY"
}
}
EOF
Bu yaklaşımın dezavantajı, payload’ı parse etmeden versiyonu bilemezsiniz. Yani routing kararını middleware seviyesinde veremezsiniz.
Geriye Dönük Uyumluluk Stratejileri
Additive Changes: Güvenli Değişiklikler
Geriye dönük uyumluluğu korumanın en basit yolu, sadece ekleyici değişiklikler yapmaktır. Mevcut alanlara dokunmadan yeni alanlar eklemek genellikle güvenlidir.
# v1 payload - mevcut istemciler bunu bekliyor
{
"order_id": "ord_12345",
"status": "completed",
"amount": 299.99
}
# v1.1 - geriye dönük uyumlu ekleme (breaking change değil)
{
"order_id": "ord_12345",
"status": "completed",
"amount": 299.99,
"currency": "TRY", # YENİ - eklendi
"completed_at": "2024-01-15T14:30:00Z" # YENİ - eklendi
}
İstemciler bilinmeyen alanları ignore edebilecek şekilde yazılmışsa, bu değişiklikler onları kırmaz. Ancak bunu hiçbir zaman garanti edemezsiniz, bu yüzden dokümantasyonunuzda “istemciler bilinmeyen alanları tolere etmelidir” kuralını açıkça belirtmeniz gerekir.
Transformation Layer ile Versiyon Köprüsü
Gerçek dünyada en sık kullandığım pattern, bir transformation layer oluşturmaktır. Yeni bir payload formatınız var ama eski istemcileri kırmak istemiyorsunuz. Bu durumda bir middleware yazıp eski formata dönüştürüyorsunuz.
#!/bin/bash
# webhook_transformer.sh
# Yeni format payload'ı eski formata dönüştürür
transform_v2_to_v1() {
local payload="$1"
# jq ile payload dönüşümü
echo "$payload" | jq '{
order_id: .data.order_id,
status: .data.status,
amount: .data.payment.total,
customer_email: .data.customer.email
}'
}
# Gelen payload'ı oku
incoming_payload=$(cat)
version=$(echo "$incoming_payload" | jq -r '.version')
case "$version" in
"2")
transformed=$(transform_v2_to_v1 "$incoming_payload")
echo "$transformed"
;;
"1")
echo "$incoming_payload"
;;
*)
echo '{"error": "Unsupported version"}' >&2
exit 1
;;
esac
Feature Flag ile Kademeli Geçiş
Tüm istemcileri aynı anda geçirmek yerine, feature flag sistemi kurarak kademeli migration yapabilirsiniz. Bu yaklaşım özellikle büyük müşteri tabanlarında çok işe yarar.
#!/bin/bash
# webhook_dispatcher.sh
# İstemci bazında versiyon kontrolü yapar
REDIS_CLI="redis-cli -h localhost -p 6379"
get_client_version() {
local client_id="$1"
local version
# Redis'ten istemci konfigürasyonunu çek
version=$($REDIS_CLI GET "webhook:client:${client_id}:version")
# Varsayılan versiyon v1
echo "${version:-1}"
}
dispatch_webhook() {
local client_id="$1"
local event_data="$2"
local client_version
client_version=$(get_client_version "$client_id")
case "$client_version" in
"2")
endpoint=$($REDIS_CLI GET "webhook:client:${client_id}:endpoint_v2")
payload=$(format_v2_payload "$event_data")
;;
"1"|*)
endpoint=$($REDIS_CLI GET "webhook:client:${client_id}:endpoint_v1")
payload=$(format_v1_payload "$event_data")
;;
esac
# Webhook'u gönder
curl -s -X POST "$endpoint"
-H "Content-Type: application/json"
-H "X-Webhook-Version: ${client_version}"
-d "$payload"
}
# Versiyon güncelleme örneği
# $REDIS_CLI SET "webhook:client:cust_123:version" "2"
Deprecation Süreci ve İletişim
Sunset Header Kullanımı
IETF RFC 8594 ile standartlaşan Sunset header’ı, bir endpoint’in ne zaman kapatılacağını bildirmek için idealdir.
# Nginx'te deprecated endpoint için Sunset header ekleme
location /webhooks/v1/ {
proxy_pass http://webhook_v1;
# Deprecation bildirimi header'ları
add_header Sunset "Sat, 31 Dec 2024 23:59:59 GMT" always;
add_header Deprecation "true" always;
add_header Link '<https://docs.yourservice.com/webhooks/migration-v2>; rel="deprecation"' always;
# Log seviyesini artır - deprecated endpoint kullanımını izle
access_log /var/log/nginx/webhook_v1_deprecated.log combined;
}
Bu header’ları alan istemciler, uygun altyapıya sahip olduklarında uyarı alabilir ve migration’ı planlayabilirler.
Deprecation Takibi ve Raporlama
Hangi istemcinin hala eski versiyonu kullandığını bilmeden doğru bir geçiş planı yapamazsınız:
#!/bin/bash
# webhook_deprecation_report.sh
# Deprecated endpoint kullanan istemcileri raporlar
LOG_FILE="/var/log/nginx/webhook_v1_deprecated.log"
REPORT_DATE=$(date +%Y-%m-%d)
echo "=== Webhook V1 Deprecation Report - $REPORT_DATE ==="
echo ""
echo "Son 24 saatte v1 endpoint kullanan IP adresleri:"
echo "------------------------------------------------"
# Son 24 saatin loglarını filtrele ve istatistik çıkar
awk -v date="$(date +%d/%b/%Y)" '$4 ~ date {print $1}' "$LOG_FILE" |
sort |
uniq -c |
sort -rn |
while read count ip; do
# IP'ye karşılık gelen istemci adını bul
client_name=$(redis-cli GET "webhook:ip:${ip}:client_name" 2>/dev/null || echo "Unknown")
echo " Istek Sayisi: $count | IP: $ip | Istemci: $client_name"
done
echo ""
echo "Toplam v1 istegi: $(wc -l < "$LOG_FILE" | tr -d ' ')"
echo "Migration durumu icin: https://docs.yourservice.com/migration"
Gerçek Dünya Senaryosu: E-ticaret Platformu Migration’ı
Diyelim ki bir e-ticaret platformu işletiyorsunuz ve sipariş webhook’larınızı v1’den v2’ye geçirmeniz gerekiyor. v1’de flat bir yapı varken, v2’de nested objeler kullanmaya başlıyorsunuz.
#!/bin/bash
# migration_test.sh
# V1 ve V2 formatlarını test eder
V1_PAYLOAD='{
"order_id": "ORD-2024-001",
"customer_name": "Ahmet Yilmaz",
"customer_email": "[email protected]",
"total": 450.00,
"status": "shipped",
"shipping_address": "Kadikoy, Istanbul"
}'
V2_PAYLOAD='{
"version": "2",
"event": "order.shipped",
"order": {
"id": "ORD-2024-001",
"status": "shipped",
"total": {
"amount": 450.00,
"currency": "TRY",
"formatted": "450,00 TL"
}
},
"customer": {
"name": "Ahmet Yilmaz",
"email": "[email protected]",
"phone": "+90-555-1234567"
},
"shipping": {
"address": {
"district": "Kadikoy",
"city": "Istanbul",
"country": "TR"
},
"carrier": "Yurtici Kargo",
"tracking_code": "YK123456789"
}
}'
# Test fonksiyonu
test_webhook_endpoint() {
local version="$1"
local payload="$2"
local endpoint="$3"
echo "Testing v${version} endpoint..."
response=$(curl -s -w "n%{http_code}" -X POST "$endpoint"
-H "Content-Type: application/json"
-H "X-Webhook-Version: ${version}"
-d "$payload")
http_code=$(echo "$response" | tail -1)
body=$(echo "$response" | head -1)
if [ "$http_code" -eq 200 ]; then
echo " BASARILI: HTTP $http_code"
else
echo " HATA: HTTP $http_code - $body"
fi
}
# Her iki versiyonu da test et
test_webhook_endpoint "1" "$V1_PAYLOAD" "http://localhost:8081/webhooks/v1/orders"
test_webhook_endpoint "2" "$V2_PAYLOAD" "http://localhost:8082/webhooks/v2/orders"
Versiyonlar Arası Signature Doğrulama
Versiyon geçişlerinde sık atlanan bir konu da HMAC signature doğrulamasının versiyon bağımlı olabileceğidir. Her versiyon için farklı bir imzalama şeması kullanıyorsanız bunu açıkça yönetmeniz gerekir.
#!/bin/bash
# webhook_signature_verify.sh
# Versiyon bazinda HMAC dogrulama
verify_webhook_signature() {
local payload="$1"
local received_signature="$2"
local webhook_version="$3"
local secret_key="$4"
case "$webhook_version" in
"1")
# V1: SHA1 kullaniyor (eski, guvenli degil)
expected_sig="sha1=$(echo -n "$payload" |
openssl dgst -sha1 -hmac "$secret_key" |
awk '{print $2}')"
algorithm="SHA1"
;;
"2")
# V2: SHA256 kullaniyor (onerilen)
expected_sig="sha256=$(echo -n "$payload" |
openssl dgst -sha256 -hmac "$secret_key" |
awk '{print $2}')"
algorithm="SHA256"
;;
*)
echo "ERROR: Bilinmeyen webhook versiyonu: $webhook_version"
return 1
;;
esac
if [ "$received_signature" = "$expected_sig" ]; then
echo "OK: $algorithm imzasi dogrulandi"
return 0
else
echo "ERROR: Imza dogrulama basarisiz (Versiyon: v$webhook_version, Algoritma: $algorithm)"
echo " Beklenen: $expected_sig"
echo " Alinan: $received_signature"
return 1
fi
}
# Kullanim ornegi:
# verify_webhook_signature "$PAYLOAD" "$HTTP_X_SIGNATURE" "$HTTP_X_WEBHOOK_VERSION" "$SECRET"
Monitoring ve Alerting
Versiyonlama stratejiniz ne kadar iyi olursa olsun, gerçek zamanlı izleme olmadan körle oynarsınız. Prometheus ile webhook metriklerini toplamak için basit bir bash exporter yazabilirsiniz:
#!/bin/bash
# webhook_metrics_exporter.sh
# Prometheus text format ile metrikleri disa aktar
METRICS_FILE="/var/lib/prometheus/webhook_metrics.prom"
LOG_DIR="/var/log/nginx"
collect_webhook_metrics() {
local timestamp=$(date +%s)
# Her versiyon icin istek sayilari
for version in v1 v2; do
log_file="${LOG_DIR}/webhook_${version}_access.log"
if [ -f "$log_file" ]; then
# Son 60 saniyedeki istek sayisi
count=$(awk -v ts="$(date -d '60 seconds ago' '+%d/%b/%Y:%H:%M:%S')"
'$4 > "["ts' "$log_file" | wc -l)
# HTTP status kodlarina gore dagilim
success=$(grep -c " 200 " "$log_file" 2>/dev/null || echo 0)
errors=$(grep -c " 5[0-9][0-9] " "$log_file" 2>/dev/null || echo 0)
echo "webhook_requests_total{version="${version}"} $count $timestamp"
echo "webhook_success_total{version="${version}"} $success $timestamp"
echo "webhook_error_total{version="${version}"} $errors $timestamp"
fi
done
# Deprecated endpoint kullanimi - kritik metrik
deprecated_count=$(wc -l < "${LOG_DIR}/webhook_v1_deprecated.log" 2>/dev/null || echo 0)
echo "webhook_deprecated_endpoint_total{version="v1"} $deprecated_count $timestamp"
}
# Metrikleri dosyaya yaz
collect_webhook_metrics > "$METRICS_FILE"
echo "Metrikler guncellendi: $METRICS_FILE"
Migration Checklist ve Timeline Yönetimi
Başarılı bir versiyon migration’ı için standart bir timeline şu şekilde işler:
- Ay 0: Yeni versiyon duyurusu, dokümantasyon yayını, sandbox ortamında test imkanı sunulması
- Ay 1-2: Erken benimseyiciler için v2’ye geçiş desteği, geri bildirim toplama
- Ay 3: Tüm yeni kayıtların otomatik olarak v2’ye yönlendirilmesi
- Ay 4: v1 üzerinde Sunset header aktive edilmesi, deprecation e-postaları
- Ay 6: v1 read-only moda alınması (sadece GET, POST’lar reddedilir)
- Ay 9: v1 tamamen kapatılması
Bu timeline’ı işlemek için bir cron job kurabilirsiniz:
#!/bin/bash
# deprecation_lifecycle.sh
# Webhook versiyon lifecycle yonetimi
DEPRECATION_CONFIG="/etc/webhook/deprecation.conf"
CURRENT_DATE=$(date +%Y%m%d)
check_and_enforce_lifecycle() {
while IFS='=' read -r key value; do
case "$key" in
"sunset_date")
sunset_ts=$(date -d "$value" +%s 2>/dev/null)
current_ts=$(date +%s)
if [ "$current_ts" -gt "$sunset_ts" ]; then
echo "KRITIK: v1 sunset tarihi gecti! Endpoint kapatilmali."
# Nginx konfigurasyonunu guncelle
sed -i 's/proxy_pass http://webhook_v1/return 410/'
/etc/nginx/sites-available/webhook-routing.conf
nginx -t && nginx -s reload
echo "v1 endpoint devre disi birakildi - $(date)"
fi
;;
"readonly_date")
readonly_ts=$(date -d "$value" +%s 2>/dev/null)
current_ts=$(date +%s)
if [ "$current_ts" -gt "$readonly_ts" ]; then
echo "UYARI: v1 read-only modda olmali"
# POST isteklerini reddet
sed -i '/location /webhooks/v1/a limit_except GET { return 405; }'
/etc/nginx/sites-available/webhook-routing.conf
nginx -t && nginx -s reload
fi
;;
esac
done < "$DEPRECATION_CONFIG"
}
check_and_enforce_lifecycle
Sık Yapılan Hatalar
Yıllar içinde gördüğüm en yaygın webhook versiyonlama hatalarını şöyle listeleyebilirim:
- Versiyon 1’e “son versiyon” gözüyle bakmak: İlk günden itibaren versiyonlama düşünerek tasarlamak, sonradan eklemeye çalışmaktan çok daha kolaydır.
- İstemcilerle iletişim kurmadan sunset tarihini değiştirmek: Bir kez duyurduğunuz tarihi geri almak mümkündür ama öne almak ciddi güven sorunu yaratır.
- Transformation layer’ı test etmemek: v2 payload’ını v1’e çevirirken veri kaybı yaşanabilir. Her alan için unit test yazın.
- Signature doğrulamasını versiyondan bağımsız tutmayı unutmak: Versiyon geçişinde imzalama algoritmasını da değiştirdiyseniz, bunu açıkça belgeleyin.
- Monitoring kurmadan geçiş yapmak: Hangi istemcinin ne zaman geçtiğini bilmeden, sunset tarihini güvenle belirleyemezsiniz.
Sonuç
Webhook versiyonlama, küçük bir uygulama ayrıntısı değil, API’nizin uzun vadeli yaşam döngüsünü belirleyen kritik bir tasarım kararıdır. Doğru yapıldığında istemcileriniz değişikliklerden habersiz bir şekilde çalışmaya devam eder, yanlış yapıldığında ise bir sabah üretim ortamında alarm yağmuruna tutulursunuz.
Pratik tavsiyem şu: Projenizin en başından URL tabanlı versiyonlama ile başlayın, istemci konfigürasyonlarını bir key-value store’da tutun, Sunset header’larını erkenden aktive edin ve her şeyden önce neyin production’a çarptığını izleyin. Monitoring olmadan yaptığınız tüm stratejiler kağıt üzerinde kalır.
Migration süreçlerinde de acele etmeyin. İstemcilerinize gerçekten yeterli süre tanıyın, proaktif iletişim kurun ve geçiş yaparken el tutun. Teknik mükemmellik kadar bu insan boyutu da önemlidir. Sonuçta webhook entegrasyonunuzu kullanan geliştiriciler de sizin gibi biri, ve sabah 3’te deprecated endpoint hatası almak onların da isteyeceği bir şey değildir.
