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ı¶
- MuditaKurye restoran paneline giriş yapın
- Ayarlar → Entegrasyon → Webhook URL'leri bölümüne gidin
- Aşağıdaki URL'leri kaydedin:
- Status Update Webhook URL: Durum değişiklikleri için
- 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 URL'ini (https://abc123.ngrok.io) MuditaKurye paneline kaydedin.
Sorun Giderme¶
Webhook Gelmiyor¶
- URL doğru mu? - MuditaKurye panelinde kontrol edin
- HTTPS mi? - HTTP desteklenmez
- Firewall/IP whitelist? - MuditaKurye IP'lerini izin listesine ekleyin
- SSL sertifikası geçerli mi? - Self-signed kabul edilmez
Signature Doğrulama Başarısız¶
- Secret doğru mu? - Panelden kopyalanan secret'ı kullanın
- JSON serialization - Boşluksuz serialize edin:
JSON.stringify(payload, null, 0) - Encoding - UTF-8 encoding kullanın
Timeout Hatası¶
- 5 saniye kuralı - Yoğun işlemleri background'a alın
- Database connection - Connection pool kullanın
- External API calls - Webhook içinde yapmayın
Sonraki Adım¶
Webhook entegrasyonunuz tamamlandı! Şimdi Hata Kodları sayfasına göz atın.