İçeriğe geç
Başvuru yap
Dokümantasyon / Webhooklar

Webhooklar

Olaylar, imza doğrulama ve tekrar deneme

Webhook, AloYemek'te bir olay olduğunda firmanızın belirlediği HTTPS adresine gönderilen POST isteğidir. Webhook adresinizi başvuru formunda verirsiniz; sonradan değiştirmek için bize yazın.

Olaylar

OlayNe zaman?data
order.createdRestorana yeni sipariş geldi.Sipariş nesnesi + event
order.status_changedSipariş durumu değişti (kabul, hazır, yolda, teslim, tamamlandı). Değişiklik restoran panelinden, AloYemek'ten veya sizin API çağrınızdan gelebilir.Sipariş nesnesi + event
order.cancelledSipariş reddedildi, müşteri tarafından iptal edildi veya teslim edilemedi.Sipariş nesnesi + event
connection.createdBir restoran firmanıza bağlandı.restaurantName, scopes
connection.key_rotatedRestoran anahtarını yeniledi; eski anahtar ve token'lar geçersiz.restaurantName, scopes
connection.revokedRestoran bağlantıyı kaldırdı.restaurantName, scopes
pingAloYemek panelinden gönderilen test bildirimi.message

İstek

POST /aloyemek/webhook HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: AloYemek-Webhook/1.0
X-AloYemek-Event: order.created
X-AloYemek-Delivery: evt_3
X-AloYemek-Timestamp: 1789653499
X-AloYemek-Integrator: 3f0c1d7a-…
X-AloYemek-Signature: sha256=5b1e0c…
{
  "id": "evt_3",
  "event": "order.created",
  "createdAt": "2026-09-17T17:18:30+03:00",
  "restaurantId": "0b484214-4740-407b-b3ec-d3987e6f1bb4",
  "data": {
    "id": "ad136e9a-9e62-43a5-a739-246ad09ab3dc",
    "number": 10018,
    "status": "new",
    "statusGroup": "new",
    "availableActions": ["accept", "reject", "ready"],
    "type": "delivery",
    "customer": { "name": "Ayşe Yılmaz", "phone": "+905551112233" },
    "items": [ … ],
    "totals": { … },
    "event": { "status": "new", "changedBy": "" }
  }
}

Sipariş olaylarında data, sipariş detayı ile aynı nesnedir. data.event.status olayın gerçekleştiği andaki durumu, data.status gönderim anındaki güncel durumu gösterir. data.event.changedBy değişikliği kimin yaptığını belirtir; kendi API çağrılarınız API: Firma adınız olarak görünür.

İmza doğrulama

Her isteği işlemeden önce doğrulayın. İmza, X-AloYemek-Timestamp değeri, bir nokta ve ham istek gövdesinin webhook imza anahtarınızla HMAC-SHA256 özetidir. Tekrar saldırılarına karşı 5 dakikadan eski zaman damgalarını reddedin.

$govde  = file_get_contents('php://input');
$zaman  = $_SERVER['HTTP_X_ALOYEMEK_TIMESTAMP'] ?? '';
$imza   = $_SERVER['HTTP_X_ALOYEMEK_SIGNATURE'] ?? '';
$beklenen = 'sha256=' . hash_hmac('sha256', $zaman . '.' . $govde, getenv('ALOYEMEK_WEBHOOK_SECRET'));

if (!hash_equals($beklenen, $imza) || abs(time() - (int)$zaman) > 300) {
    http_response_code(401);
    exit;
}
$olay = json_decode($govde, true);
// Olayı kuyruğa alın, hemen 200 dönün
http_response_code(200);
import crypto from 'node:crypto';
import express from 'express';

const app = express();
app.post('/aloyemek/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const zaman = req.get('X-AloYemek-Timestamp');
  const imza = req.get('X-AloYemek-Signature') || '';
  const beklenen = 'sha256=' + crypto
    .createHmac('sha256', process.env.ALOYEMEK_WEBHOOK_SECRET)
    .update(zaman + '.' + req.body)
    .digest('hex');
  const gecerli = imza.length === beklenen.length &&
    crypto.timingSafeEqual(Buffer.from(imza), Buffer.from(beklenen));
  if (!gecerli || Math.abs(Date.now() / 1000 - Number(zaman)) > 300) return res.sendStatus(401);
  const olay = JSON.parse(req.body);
  res.sendStatus(200);
});
import hmac, hashlib, os, time
from flask import Flask, request, abort

app = Flask(__name__)

@app.post('/aloyemek/webhook')
def webhook():
    govde = request.get_data()
    zaman = request.headers.get('X-AloYemek-Timestamp', '')
    imza = request.headers.get('X-AloYemek-Signature', '')
    beklenen = 'sha256=' + hmac.new(os.environ['ALOYEMEK_WEBHOOK_SECRET'].encode(),
                                    zaman.encode() + b'.' + govde, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(beklenen, imza) or abs(time.time() - int(zaman or 0)) > 300:
        abort(401)
    olay = request.get_json()
    return '', 200
[HttpPost("aloyemek/webhook")]
public async Task<IActionResult> Webhook()
{
    using var okuyucu = new StreamReader(Request.Body);
    var govde = await okuyucu.ReadToEndAsync();
    var zaman = Request.Headers["X-AloYemek-Timestamp"].ToString();
    var imza = Request.Headers["X-AloYemek-Signature"].ToString();
    var anahtar = Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("ALOYEMEK_WEBHOOK_SECRET"));
    using var hmac = new HMACSHA256(anahtar);
    var ozet = hmac.ComputeHash(Encoding.UTF8.GetBytes(zaman + "." + govde));
    var beklenen = "sha256=" + Convert.ToHexString(ozet).ToLowerInvariant();
    if (!CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(beklenen), Encoding.UTF8.GetBytes(imza))
        || Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(zaman)) > 300)
        return Unauthorized();
    return Ok();
}

Yanıt ve tekrar deneme

  • 10 saniye içinde 2xx dönün. Ağır işleri (yazdırma, veritabanı) kuyruğa alıp hemen yanıt verin.
  • 2xx dışındaki her yanıt veya zaman aşımı başarısız sayılır ve şu aralıklarla tekrar denenir: 1 dk, 5 dk, 15 dk, 30 dk, 1 sa, 3 sa, 6 sa (toplam 8 deneme).
  • Yönlendirmeler (3xx) izlenmez; adresinizin doğrudan 2xx döndüğünden emin olun.
  • Aynı olay birden fazla gelebilir. id (ör. evt_3) alanını saklayıp tekrar gelenleri yok sayın.
  • Olayların sırası garanti değildir; sipariş durumunu createdAt ve data.updatedAt ile karşılaştırarak güncelleyin veya şüphede GET /orders/{id} ile doğrulayın.
Webhook gönderimleri her dakika çalışan bir görevle yapılır; yeni sipariş genellikle 5–10 saniye içinde adresinize ulaşır.
Sorunuz mu var? destek@aloyemek.com