Ana içeriğe geç

Webhook'lar (Durum Güncellemeleri)

MuditaKurye, sipariş durum değişikliklerini ve iptal bildirimlerini webhook'lar aracılığıyla gönderir.

Webhook URL Yapılandırması

  1. MuditaKurye restoran paneline giriş yapın
  2. AyarlarEntegrasyonWebhook URL'leri bölümüne gidin
  3. Aşağıdaki URL'leri kaydedin:
  4. Status Update Webhook URL: Durum değişiklikleri için
  5. Cancel Webhook URL: İptal bildirimleri için

HTTPS Zorunlu

Webhook URL'leri mutlaka HTTPS olmalıdır. Self-signed sertifikalar kabul edilmez.

Sipariş Durum Güncellemesi

Request

Method: POST

Headers:

Header Açıklama
Content-Type application/json
X-MuditaKurye-Signature HMAC imza (opsiyonel, etkinleştirilmişse)

Payload:

{
  "event": "order.status_changed",
  "orderId": "order_123456",
  "yemekKuryeOrderId": "550e8400-e29b-41d4-a716-446655440000",
  "orderNumber": "RST-20251110-0042",
  "status": "PREPARED",
  "previousStatus": "VALIDATED",
  "timestamp": "2025-11-10T17:45:00+03:00",
  "provider": "THIRD_PARTY",
  "providerRestaurantId": "rest_abc1234567890"
}

Payload Alanları

Alan Tip Açıklama
event string Her zaman order.status_changed
orderId string Sizin gönderdiğiniz orijinal sipariş ID'si
yemekKuryeOrderId string MuditaKurye içdaki UUID
orderNumber string Platform sipariş numarası
status string Yeni durum (detaylar aşağıda)
previousStatus string Önceki durum
timestamp string Durum değişikliği zamanı (ISO-8601)
provider string Entegrasyon türü (THIRD_PARTY)
providerRestaurantId string Restoran ID'niz

Sipariş Durumları

Durum Açıklama Sonraki Durumlar
NEW Sipariş alındı, onay bekleniyor VALIDATED, CANCELED
VALIDATED Restoran onayladı ROUTED, ASSIGNED, PREPARED, CANCELED
ROUTED VRP optimizasyonu ile kurye planlaması yapıldı ASSIGNED, CANCELED
ASSIGNED Kuryeye manuel atama yapıldı ACCEPTED, CANCELED
ACCEPTED Kurye kabul etti PREPARED, CANCELED
PREPARED Sipariş hazır, teslim alınmayı bekliyor ON_DELIVERY, CANCELED
ON_DELIVERY Kurye teslim aldı, yolda DELIVERED, CANCELED
DELIVERED Teslim edildi (final durum) -
CANCELED İptal edildi (final durum) -

Durum Geçiş Diyagramı

stateDiagram-v2
    [*] --> NEW
    NEW --> VALIDATED: Restoran onayı
    NEW --> CANCELED: İptal

    VALIDATED --> ROUTED: VRP ataması
    VALIDATED --> ASSIGNED: Manuel atama
    VALIDATED --> PREPARED: Hazırlandı
    VALIDATED --> CANCELED: İptal

    ROUTED --> ASSIGNED: Kurye seçildi
    ROUTED --> CANCELED: İptal

    ASSIGNED --> ACCEPTED: Kurye kabul etti
    ASSIGNED --> CANCELED: İptal

    ACCEPTED --> PREPARED: Hazırlandı
    ACCEPTED --> CANCELED: İptal

    PREPARED --> ON_DELIVERY: Teslim alındı
    PREPARED --> CANCELED: İptal

    ON_DELIVERY --> DELIVERED: Teslim edildi
    ON_DELIVERY --> CANCELED: İptal

    DELIVERED --> [*]
    CANCELED --> [*]

İptal Webhook'u

Request

Method: POST

Headers: Durum güncellemesi ile aynı

Payload:

{
  "event": "order.canceled",
  "orderId": "order_123456",
  "yemekKuryeOrderId": "550e8400-e29b-41d4-a716-446655440000",
  "reason": "Müşteri ürünleri beğenmedi",
  "cancelReasonId": "CUSTOMER_REQUEST",
  "canceledBy": "Restaurant",
  "timestamp": "2025-11-10T17:50:00+03:00"
}

İptal Nedenleri

cancelReasonId canceledBy Açıklama
CUSTOMER_REQUEST Restaurant Müşteri istedi
OUT_OF_STOCK Restaurant Ürün kalmadı
WRONG_ADDRESS Courier Adres bulunamadı
CUSTOMER_NOT_AVAILABLE Courier Müşteriye ulaşılamadı
COURIER_NOT_AVAILABLE System Kurye bulunamadı
RESTAURANT_CLOSED System Restoran kapalı
OTHER Restaurant/Courier Diğer nedenler

Webhook Receiver Örnekleri

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

// Signature doğrulama
function verifySignature(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  const calculatedSignature = hmac.update(JSON.stringify(payload)).digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(calculatedSignature)
  );
}

// Durum güncelleme webhook'u
app.post('/webhook/muditakurye/status', async (req, res) => {
  try {
    // Signature kontrolü (opsiyonel)
    const signature = req.headers['x-muditakurye-signature'];
    const secret = process.env.WEBHOOK_SECRET;

    if (signature && !verifySignature(req.body, signature, secret)) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    const { event, orderId, status, previousStatus, timestamp } = req.body;

    console.log(`Order ${orderId}: ${previousStatus}${status} at ${timestamp}`);

    // Veritabanınızı güncelleyin
    await updateOrderStatus(orderId, status);

    // Müşteriye bildirim gönderin
    if (status === 'ON_DELIVERY') {
      await sendCustomerNotification(orderId, 'Siparişiniz yolda!');
    }

    // 5 saniye içinde yanıt dönün (önerilir)
    res.status(200).json({ received: true });

  } catch (error) {
    console.error('Webhook error:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

// İptal webhook'u
app.post('/webhook/muditakurye/cancel', async (req, res) => {
  try {
    const { orderId, reason, canceledBy } = req.body;

    console.log(`Order ${orderId} canceled by ${canceledBy}: ${reason}`);

    await updateOrderStatus(orderId, 'CANCELED');
    await sendCustomerNotification(orderId, `Siparişiniz iptal edildi: ${reason}`);

    res.status(200).json({ received: true });

  } catch (error) {
    console.error('Webhook error:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});
from flask import Flask, request, jsonify
import hmac
import hashlib
import json
import os

app = Flask(__name__)

def verify_signature(payload: dict, signature: str, secret: str) -> bool:
    """HMAC signature doğrulama"""
    payload_str = json.dumps(payload, separators=(',', ':'))
    calculated_signature = hmac.new(
        secret.encode(),
        payload_str.encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, calculated_signature)

@app.route('/webhook/muditakurye/status', methods=['POST'])
def status_webhook():
    try:
        # Signature kontrolü
        signature = request.headers.get('X-MuditaKurye-Signature')
        secret = os.getenv('WEBHOOK_SECRET')

        if signature and not verify_signature(request.json, signature, secret):
            return jsonify({'error': 'Invalid signature'}), 401

        data = request.json
        order_id = data['orderId']
        status = data['status']
        previous_status = data['previousStatus']
        timestamp = data['timestamp']

        print(f'Order {order_id}: {previous_status}{status} at {timestamp}')

        # Veritabanı güncelleme
        update_order_status(order_id, status)

        # Müşteri bildirimi
        if status == 'ON_DELIVERY':
            send_customer_notification(order_id, 'Siparişiniz yolda!')

        return jsonify({'received': True}), 200

    except Exception as e:
        print(f'Webhook error: {e}')
        return jsonify({'error': 'Internal server error'}), 500

@app.route('/webhook/muditakurye/cancel', methods=['POST'])
def cancel_webhook():
    try:
        data = request.json
        order_id = data['orderId']
        reason = data['reason']
        canceled_by = data['canceledBy']

        print(f'Order {order_id} canceled by {canceled_by}: {reason}')

        update_order_status(order_id, 'CANCELED')
        send_customer_notification(order_id, f'Siparişiniz iptal edildi: {reason}')

        return jsonify({'received': True}), 200

    except Exception as e:
        print(f'Webhook error: {e}')
        return jsonify({'error': 'Internal server error'}), 500

if __name__ == '__main__':
    app.run(port=3000)

Webhook En İyi Uygulamaları

1. Hızlı Yanıt Verin

5 Saniye Kuralı

Webhook'unuz 5 saniye içinde HTTP 200-299 yanıtı dönmelidir. Yoğun işlemleri background job olarak yapın.

// ❌ YANLIŞ: Webhook içinde yoğun işlem
app.post('/webhook', async (req, res) => {
  await updateDatabase(req.body);        // 2 saniye
  await sendEmail(req.body);             // 3 saniye
  await updateInventory(req.body);       // 2 saniye
  res.json({ received: true });          // Toplam 7 saniye - TIMEOUT!
});

// ✅ DOĞRU: Hızlı acknowledge, sonra background
app.post('/webhook', async (req, res) => {
  // Hemen yanıt dön
  res.json({ received: true });

  // Background job olarak işle
  queue.add('process-webhook', req.body);
});

2. İdempotency Sağlayın

Aynı webhook birden fazla kez gelebilir. İdempotent olduğundan emin olun:

async function processWebhook(payload) {
  const { orderId, status, timestamp } = payload;

  // Bu güncelleme zaten yapıldı mı kontrol et
  const lastUpdate = await getLastUpdate(orderId);
  if (lastUpdate && lastUpdate.timestamp >= timestamp) {
    console.log('Already processed, skipping');
    return;
  }

  // İşlemi yap
  await updateOrderStatus(orderId, status, timestamp);
}

3. Retry Mekanizması

MuditaKurye, başarısız webhook'ları otomatik olarak yeniden dener:

Deneme Bekleme Süresi
1 1 dakika
2 5 dakika
3 15 dakika
4 1 saat
5 4 saat
6+ 24 saat (max)

Maksimum 24 Saat

Webhook 24 saat boyunca denemeler başarısız olursa, destek ekibiyle iletişime geçilir.

4. Loglama ve İzleme

Tüm webhook isteklerini loglayın:

app.post('/webhook/muditakurye/status', async (req, res) => {
  const requestId = req.headers['x-request-id'] || uuid();

  logger.info('Webhook received', {
    requestId,
    event: req.body.event,
    orderId: req.body.orderId,
    status: req.body.status,
    timestamp: req.body.timestamp
  });

  try {
    await processWebhook(req.body);

    logger.info('Webhook processed', { requestId });
    res.json({ received: true });

  } catch (error) {
    logger.error('Webhook processing failed', {
      requestId,
      error: error.message,
      stack: error.stack
    });
    res.status(500).json({ error: 'Processing failed' });
  }
});

5. Health Check Endpoint

Webhook URL'inizde bir health check endpoint'i sunun:

app.get('/webhook/muditakurye/health', (req, res) => {
  res.json({
    status: 'ok',
    timestamp: new Date().toISOString()
  });
});

Webhook Test Etme

Staging Ortamı

Test siparişleri göndermek için staging URL'ini kullanın:

curl -X POST https://staging-api.muditakurye.com/webhook/third-party/order \
  -H "X-API-Key: yk_test_..." \
  -d '{ ... }'

Manuel Test

Webhook endpoint'inizi test etmek için:

curl -X POST http://localhost:3000/webhook/muditakurye/status \
  -H "Content-Type: application/json" \
  -d '{
    "event": "order.status_changed",
    "orderId": "test_order_123",
    "status": "DELIVERED",
    "previousStatus": "ON_DELIVERY",
    "timestamp": "2025-11-10T18:00:00+03:00"
  }'

Ngrok ile Lokal Test

Lokal sunucunuzu internetten erişilebilir yapmak için ngrok kullanın:

ngrok http 3000

Ngrok URL'ini (https://abc123.ngrok.io) MuditaKurye paneline kaydedin.

Sorun Giderme

Webhook Gelmiyor

  1. URL doğru mu? - MuditaKurye panelinde kontrol edin
  2. HTTPS mi? - HTTP desteklenmez
  3. Firewall/IP whitelist? - MuditaKurye IP'lerini izin listesine ekleyin
  4. SSL sertifikası geçerli mi? - Self-signed kabul edilmez

Signature Doğrulama Başarısız

  1. Secret doğru mu? - Panelden kopyalanan secret'ı kullanın
  2. JSON serialization - Boşluksuz serialize edin: JSON.stringify(payload, null, 0)
  3. Encoding - UTF-8 encoding kullanın

Timeout Hatası

  1. 5 saniye kuralı - Yoğun işlemleri background'a alın
  2. Database connection - Connection pool kullanın
  3. External API calls - Webhook içinde yapmayın

Sonraki Adım

Webhook entegrasyonunuz tamamlandı! Şimdi Hata Kodları sayfasına göz atın.