WooCommerce Sipariş Bildirimleri: Webhook ile Gerçek Zamanlı Alma

E-ticaret projelerinde en sık karşılaşılan gereksinimlerden biri, sipariş oluştuğunda bunu anlık olarak dış sistemlere iletmektir. Muhasebe yazılımı, depo yönetim sistemi, Slack kanalı veya özel bir CRM… Hepsinin ortak noktası şu: “Sipariş geldi, ben haberdar olmak istiyorum.” İşte bu noktada WooCommerce’in webhook desteği devreye giriyor. Bu yazıda, WooCommerce webhook’larını sıfırdan nasıl kuracağınızı, gelen veriyi nasıl işleyeceğinizi ve üretim ortamında dikkat etmeniz gereken güvenlik konularını adım adım ele alacağız.

WooCommerce Webhook Nedir?

Webhook, bir olay gerçekleştiğinde WooCommerce’in belirlediğiniz URL’ye otomatik olarak HTTP POST isteği atması anlamına gelir. Klasik API entegrasyonundan farkı şu: Siz sürekli “Yeni sipariş var mı?” diye sormuyorsunuz, WooCommerce size geliyor.

WooCommerce’de webhook tetikleyebilecek olaylar oldukça geniş:

  • order.created: Yeni sipariş oluşturulduğunda
  • order.updated: Sipariş güncellendiğinde
  • order.deleted: Sipariş silindiğinde
  • order.restored: Çöp kutusundan geri yüklendiğinde
  • product.created: Yeni ürün eklendiğinde
  • product.updated: Ürün güncellendiğinde
  • customer.created: Yeni müşteri kaydı oluştuğunda
  • coupon.created: Kupon oluşturulduğunda

Biz bu yazıda özellikle order.created olayına odaklanacağız çünkü çoğu entegrasyon senaryosunda kritik olan sipariş bildirimidir.

Alıcı Endpoint Hazırlamak

Webhook’u WooCommerce panelinde tanımlamadan önce, veriyi alacak bir endpoint’e ihtiyacınız var. Bu endpoint; HTTP POST isteği kabul eden, JSON body’yi parse edebilen ve 200 HTTP yanıtı döndüren herhangi bir web servisi olabilir.

PHP ile Basit Bir Receiver

En hızlı yol, sitenizin veya ayrı bir sunucunun üzerinde PHP ile basit bir alıcı yazmaktır:

mkdir -p /var/www/html/webhooks
touch /var/www/html/webhooks/woo-order.php
chmod 644 /var/www/html/webhooks/woo-order.php
cat > /var/www/html/webhooks/woo-order.php << 'EOF'
<?php
// WooCommerce Webhook Secret
define('WC_WEBHOOK_SECRET', 'buraya_gizli_anahtarinizi_yazin');

// Log dosyasi
define('LOG_FILE', '/var/log/woo-webhooks/orders.log');

// Imzayi dogrula
function verify_webhook_signature($payload, $signature) {
    $computed = base64_encode(hash_hmac('sha256', $payload, WC_WEBHOOK_SECRET, true));
    return hash_equals($computed, $signature);
}

// Log yaz
function write_log($message) {
    $timestamp = date('Y-m-d H:i:s');
    file_put_contents(LOG_FILE, "[{$timestamp}] {$message}" . PHP_EOL, FILE_APPEND);
}

// Giris
$raw_payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WC_WEBHOOK_SIGNATURE'] ?? '';

if (empty($signature)) {
    http_response_code(401);
    write_log("HATA: Imza eksik");
    exit;
}

if (!verify_webhook_signature($raw_payload, $signature)) {
    http_response_code(401);
    write_log("HATA: Gecersiz imza - IP: " . $_SERVER['REMOTE_ADDR']);
    exit;
}

$order = json_decode($raw_payload, true);

if (json_last_error() !== JSON_ERROR_NONE) {
    http_response_code(400);
    write_log("HATA: JSON parse hatasi");
    exit;
}

// Siparis bilgilerini isle
$order_id     = $order['id'] ?? 'bilinmiyor';
$order_total  = $order['total'] ?? '0';
$customer_email = $order['billing']['email'] ?? 'yok';
$order_status = $order['status'] ?? 'bilinmiyor';

write_log("YENI SIPARIS: #{$order_id} | Toplam: {$order_total} TL | Musteri: {$customer_email} | Durum: {$order_status}");

// Buraya kendi is mantiginizi ekleyin
// process_order($order);

http_response_code(200);
echo json_encode(['success' => true, 'order_id' => $order_id]);
EOF

Log dizinini de oluşturalım:

mkdir -p /var/log/woo-webhooks
chown www-data:www-data /var/log/woo-webhooks
chmod 755 /var/log/woo-webhooks

Python Flask ile Receiver

Eğer ayrı bir mikroservis yapısı kullanıyorsanız Python Flask daha temiz bir çözüm sunar:

pip install flask requests

cat > /opt/woo-webhook-receiver/app.py << 'EOF'
import hmac
import hashlib
import base64
import json
import logging
from flask import Flask, request, jsonify
from datetime import datetime

app = Flask(__name__)

WEBHOOK_SECRET = "buraya_gizli_anahtarinizi_yazin"

logging.basicConfig(
    filename='/var/log/woo-webhooks/flask-orders.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

def verify_signature(payload_body: bytes, signature: str) -> bool:
    computed = base64.b64encode(
        hmac.new(
            WEBHOOK_SECRET.encode('utf-8'),
            payload_body,
            hashlib.sha256
        ).digest()
    ).decode('utf-8')
    return hmac.compare_digest(computed, signature)

@app.route('/webhook/woo-order', methods=['POST'])
def handle_order():
    signature = request.headers.get('X-WC-Webhook-Signature', '')
    
    if not signature:
        logging.warning(f"Imzasiz istek - IP: {request.remote_addr}")
        return jsonify({'error': 'Imza eksik'}), 401
    
    if not verify_signature(request.data, signature):
        logging.warning(f"Gecersiz imza - IP: {request.remote_addr}")
        return jsonify({'error': 'Gecersiz imza'}), 401
    
    order = request.get_json()
    
    order_id    = order.get('id', 'bilinmiyor')
    total       = order.get('total', '0')
    email       = order.get('billing', {}).get('email', 'yok')
    status      = order.get('status', 'bilinmiyor')
    
    logging.info(f"Yeni siparis: #{order_id} | Toplam: {total} | Musteri: {email} | Durum: {status}")
    
    # Slack bildirimi, veritabani kaydi vs. buraya
    
    return jsonify({'success': True, 'order_id': order_id}), 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
EOF

WooCommerce Panelinde Webhook Tanımlama

Endpoint hazır olduğuna göre WooCommerce tarafında webhook’u oluşturabiliriz.

WooCommerce > Ayarlar > Gelişmiş > Webhooks yolunu izleyin. “Webhook Ekle” butonuna tıklayın.

Doldurmanız gereken alanlar:

  • Ad: “Yeni Sipariş Bildirimi” gibi tanımlayıcı bir isim verin
  • Durum: Aktif olarak ayarlayın
  • Konu: “Sipariş Oluşturuldu” seçin
  • Teslim URL’si: Endpoint adresinizi girin (https://receiver.example.com/webhook/woo-order)
  • Gizli: Güçlü bir rastgele string girin, bu imzalama için kullanılacak
  • API Sürümü: WP REST API v3 seçin

Gizli anahtar oluşturmak için:

openssl rand -base64 32
# Ornek cikti: K8mP2xQvR9nL4wE7jT1yA5oF3uH6cB0dN

Bu değeri hem WooCommerce paneline hem de endpoint kodunuza yazın.

Webhook Güvenliği: İmzalama Mekanizması

WooCommerce, her webhook isteğinde X-WC-Webhook-Signature başlığını gönderir. Bu başlık, payload’ın HMAC-SHA256 ile imzalanmış ve base64 ile encode edilmiş halidir.

İmzalama mantığını anlamak için:

# Komut satiriyla test edebilirsiniz
SECRET="K8mP2xQvR9nL4wE7jT1yA5oF3uH6cB0dN"
PAYLOAD='{"id":123,"status":"processing"}'

echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" -binary | base64
# Bu ciktiyi WooCommerce'in gonderdigi imzayla karsilastirabilirsiniz

Neden imzayı doğrulamak zorunlu? Çünkü endpoint URL’niz dışarıdan erişilebilir durumdadır. İmza doğrulaması olmadan herkes sahte sipariş verisi gönderebilir ve sisteminizi manipüle edebilir.

Nginx ile SSL Terminasyonu

Production ortamında webhook endpoint’iniz mutlaka HTTPS üzerinden çalışmalı. WooCommerce zaten HTTP endpoint’lere güvenmez. Nginx konfigürasyonu:

cat > /etc/nginx/sites-available/woo-webhook << 'EOF'
server {
    listen 443 ssl http2;
    server_name receiver.example.com;

    ssl_certificate     /etc/letsencrypt/live/receiver.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/receiver.example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    # Webhook icin maksimum body boyutu
    client_max_body_size 1m;

    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-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;

        # WooCommerce imza basligini ilet
        proxy_pass_header  X-WC-Webhook-Signature;

        # Timeout ayarlari
        proxy_read_timeout 30s;
        proxy_connect_timeout 10s;
    }
}
EOF

ln -s /etc/nginx/sites-available/woo-webhook /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

Webhook Test Etme

WooCommerce panelinden “Ping Gonder” butonu ile test edebilirsiniz. Ama daha kontrollü bir test için curl kullanabilirsiniz:

SECRET="K8mP2xQvR9nL4wE7jT1yA5oF3uH6cB0dN"

# Test payload
PAYLOAD='{"id":9999,"status":"processing","total":"149.90","currency":"TRY","billing":{"first_name":"Test","last_name":"Kullanici","email":"[email protected]","phone":"05001234567"},"line_items":[{"id":1,"name":"Test Urun","quantity":2,"total":"149.90"}]}'

# HMAC hesapla
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" -binary | base64)

echo "Imza: $SIGNATURE"

# Webhook gonder
curl -v -X POST 
  -H "Content-Type: application/json" 
  -H "X-WC-Webhook-Source: https://maazaniz.com" 
  -H "X-WC-Webhook-Topic: order.created" 
  -H "X-WC-Webhook-Resource: order" 
  -H "X-WC-Webhook-Event: created" 
  -H "X-WC-Webhook-Signature: $SIGNATURE" 
  -d "$PAYLOAD" 
  https://receiver.example.com/webhook/woo-order

Gerçek Dünya Senaryosu: Slack Bildirimi

Şimdi işe yarayan bir senaryo kuralım. Yeni sipariş geldiğinde Slack kanalına bildirim gönderelim:

cat > /opt/woo-webhook-receiver/slack_notifier.py << 'EOF'
import requests
import json

SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/T.../B.../..."

def format_order_message(order: dict) -> dict:
    order_id    = order.get('id', '?')
    total       = order.get('total', '0')
    currency    = order.get('currency', 'TRY')
    billing     = order.get('billing', {})
    ad_soyad    = f"{billing.get('first_name', '')} {billing.get('last_name', '')}".strip()
    email       = billing.get('email', 'yok')
    telefon     = billing.get('phone', 'yok')
    
    items = order.get('line_items', [])
    urun_listesi = "n".join([
        f"- {item.get('name', '?')} x{item.get('quantity', 1)} ({item.get('total', '0')} {currency})"
        for item in items
    ])
    
    return {
        "blocks": [
            {
                "type": "header",
                "text": {
                    "type": "plain_text",
                    "text": f"Yeni Siparis #{order_id}"
                }
            },
            {
                "type": "section",
                "fields": [
                    {"type": "mrkdwn", "text": f"*Musteri:*n{ad_soyad}"},
                    {"type": "mrkdwn", "text": f"*E-posta:*n{email}"},
                    {"type": "mrkdwn", "text": f"*Telefon:*n{telefon}"},
                    {"type": "mrkdwn", "text": f"*Toplam:*n{total} {currency}"}
                ]
            },
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": f"*Urunler:*n{urun_listesi}"
                }
            }
        ]
    }

def send_slack_notification(order: dict) -> bool:
    try:
        message = format_order_message(order)
        response = requests.post(
            SLACK_WEBHOOK_URL,
            json=message,
            timeout=10
        )
        return response.status_code == 200
    except Exception as e:
        print(f"Slack bildirimi gonderilirken hata: {e}")
        return False
EOF

Webhook Başarısız Olduğunda: Retry Mekanizması

WooCommerce, başarısız webhook teslimatlarını otomatik olarak yeniden dener. Varsayılan olarak 5 denemeden sonra webhook’u devre dışı bırakır. Bu davranışı wp-config.php ile değiştirebilirsiniz:

# wp-config.php dosyasina ekleyin
cat >> /var/www/html/wp-config.php << 'EOF'

// Webhook retry sayisi (varsayilan: 5)
define('WC_WEBHOOK_DELIVERY_TIMEOUT', 10);
EOF

Kendi tarafınızda da bir kuyruk mekanizması kurabilirsiniz. Basit bir Redis tabanlı yaklaşım:

cat > /opt/woo-webhook-receiver/queue_handler.py << 'EOF'
import redis
import json
import time
import threading
import logging

r = redis.Redis(host='localhost', port=6379, db=0)
QUEUE_KEY = "woo:order:queue"
FAILED_KEY = "woo:order:failed"

def enqueue_order(order: dict):
    """Siparisi kuyruga ekle"""
    r.lpush(QUEUE_KEY, json.dumps(order))
    logging.info(f"Siparis kuyruga eklendi: #{order.get('id')}")

def process_queue():
    """Kuyrugu isle - ayri bir thread'de calistir"""
    while True:
        try:
            item = r.brpop(QUEUE_KEY, timeout=5)
            if item:
                _, order_data = item
                order = json.loads(order_data)
                order_id = order.get('id', '?')
                
                try:
                    # Asil islemi yap
                    # process_order(order)
                    logging.info(f"Siparis islendi: #{order_id}")
                except Exception as e:
                    logging.error(f"Siparis isleme hatasi #{order_id}: {e}")
                    # Basarisiz siparisi ayri bir listeye ekle
                    r.lpush(FAILED_KEY, order_data)
        except Exception as e:
            logging.error(f"Kuyruk hatasi: {e}")
            time.sleep(1)

# Worker thread baslat
worker = threading.Thread(target=process_queue, daemon=True)
worker.start()
EOF

Webhook Loglarını İzleme

Üretim ortamında webhook loglarını düzenli izlemek şarttır. Basit bir izleme scripti:

cat > /usr/local/bin/woo-webhook-monitor.sh << 'EOF'
#!/bin/bash

LOG_FILE="/var/log/woo-webhooks/orders.log"
ALERT_EMAIL="[email protected]"

# Son 5 dakikadaki hatalari say
SON_5_DK=$(date -d '5 minutes ago' '+%Y-%m-%d %H:%M:%S')
HATA_SAYISI=$(awk -v tarih="$SON_5_DK" '$0 >= tarih && /HATA/' "$LOG_FILE" | wc -l)

if [ "$HATA_SAYISI" -gt 10 ]; then
    echo "Son 5 dakikada $HATA_SAYISI webhook hatasi tespit edildi!" | 
    mail -s "UYARI: Webhook Hata Spike" "$ALERT_EMAIL"
fi

# Gunluk rapor
BUGUN=$(date '+%Y-%m-%d')
TOPLAM=$(grep "$BUGUN" "$LOG_FILE" | grep "YENI SIPARIS" | wc -l)
echo "[$BUGUN] Toplam islenen siparis: $TOPLAM"
EOF

chmod +x /usr/local/bin/woo-webhook-monitor.sh

# Cron'a ekle
echo "*/5 * * * * root /usr/local/bin/woo-webhook-monitor.sh >> /var/log/woo-webhook-monitor.log 2>&1" 
  >> /etc/cron.d/woo-webhook

Sık Karşılaşılan Sorunlar

408 Timeout Hatası: WooCommerce varsayılan olarak 10 saniyede yanıt bekler. Endpoint’iniz ağır işlemler yapıyorsa, isteği hemen kabul edip asenkron işleyin.

İmza Doğrulama Başarısız: Payload’ı işlemeden önce raw body olarak okuyun. JSON parse ettikten sonra imzalamaya çalışırsanız boşluk farkları nedeniyle eşleşmez.

Duplicate Webhook: Bazen WooCommerce aynı olayı birden fazla gönderebilir. X-WC-Webhook-Delivery-ID başlığını kullanarak idempotency sağlayın:

# Ornek: delivery_id kontrolu
DELIVERY_ID=$(echo "$HTTP_X_WC_WEBHOOK_DELIVERY_ID")
# Bu ID'yi Redis/DB'de kontrol et, daha once islenmisse atla

SSL Sertifika Sorunu: Receiver sunucunuzun SSL sertifikası geçerli olmalı. Self-signed sertifikalar WooCommerce tarafından reddedilir.

WooCommerce REST API ile Webhook Oluşturma

Webhook’ları panelden değil API üzerinden de yönetebilirsiniz. Bu özellikle CI/CD pipeline’larında işe yarar:

# WooCommerce API anahtari olusturun (WooCommerce > Ayarlar > Gelismis > REST API)
CK="ck_xxxxxxxxxxxx"  # Consumer Key
CS="cs_xxxxxxxxxxxx"  # Consumer Secret
SITE="https://maazaniz.com"

# Webhook olustur
curl -X POST "$SITE/wp-json/wc/v3/webhooks" 
  -u "$CK:$CS" 
  -H "Content-Type: application/json" 
  -d '{
    "name": "Siparis Bildirimi",
    "topic": "order.created",
    "delivery_url": "https://receiver.example.com/webhook/woo-order",
    "secret": "K8mP2xQvR9nL4wE7jT1yA5oF3uH6cB0dN",
    "status": "active"
  }'

# Mevcut webhook listesini goruntule
curl -X GET "$SITE/wp-json/wc/v3/webhooks" 
  -u "$CK:$CS" | python3 -m json.tool

Sonuç

WooCommerce webhook’ları, doğru kurulduğunda gerçekten güçlü bir entegrasyon altyapısı sağlar. Bu yazıda ele aldığımız noktaları özetlemek gerekirse:

  • Güvenlik önce gelir: İmza doğrulamasını asla atlamayın. Endpoint’iniz herkese açık olduğundan sahte istek riski gerçektir.
  • Hızlı yanıt, asenkron işlem: Endpoint’iniz 200 döndürmeli, ağır işlemleri arka plana almalıdır. Timeout sorunu yaşamamak için bu kritiktir.
  • Log her şeyi: Özellikle production ortamında ne zaman, hangi sipariş için ne yapıldığını kayıt altına alın. Hata ayıklarken bu loglar hayat kurtarır.
  • Idempotency unutulmasın: Aynı webhook iki kez gelebilir. Sisteminiz bunu tolere edebilecek şekilde tasarlanmalıdır.
  • HTTPS zorunlu: Hem güvenlik hem de WooCommerce uyumluluğu açısından HTTP endpoint kullanmayın.

Gerçek dünyada bu yapıyı kurarken en çok zaman kaybedilen nokta genellikle imza doğrulamasıdır, özellikle raw body yerine parse edilmiş body kullanıldığında. Bu detaya dikkat ederseniz geri kalan kısım oldukça düzgün çalışır. Sorularınız için yorum bırakabilirsiniz.

Bir yanıt yazın

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