Webhook’lar
Zitena, talepler, çağrılar ve giden aramalar gibi olayları sizin adresinize imzalı POST istekleriyle bildirir. Uçlar Ayarlar → Webhook’lar sayfasında yönetilir (paket: Webhooklar; yazma yönetici ve üstü).
Uç ekleme
Bölüm başlığı “Uç ekleme”- Yeni uç: adres (yalnız
https://; iç ağa ya da özel IP’ye çıkan adresler reddedilir), açıklama ve dinlenecek olaylar. - Kaydedince gizli anahtar (
whsec_…) bir kez gösterilir. Sunucunuzda saklayın; Gizliyi yenile ile değiştirebilirsiniz. - Test gönder ile
testolayını hemen gönderip sonucu görün.
Her uçta son teslimatın durumu ve son 24 saatteki başarısız teslimat sayısı görünür; Teslimatlar listesinde her teslimatın durumunu, deneme sayısını, yanıt kodunu ve gövdesini görebilir, başarısız bir teslimatı elle yeniden deneyebilirsiniz.
POST https://sizin-adresiniz.example.com/zitenaContent-Type: application/jsonUser-Agent: Zitena-Webhook/1X-Zitena-Event: request.createdX-Zitena-Delivery: 0199a1b2-… (teslimat kimliği; tekrarlarda aynı)X-Zitena-Signature: t=1790000000,v1=5f2c…
{"id": "0199a1b2-…", "event": "request.created", "created_at": "2026-09-24T10:15:00+00:00", "data": {…}}Olaylar
Bölüm başlığı “Olaylar”| Olay | Ne zaman | data |
|---|---|---|
request.created | Görüşmede talep açıldı | Talep: id, number, call_id, kind, status, order_number, order_status, customer_name, email, phone, details, history, created_at, updated_at |
request.status_changed | Talebin durumu değişti | Talep + previous_status |
talep.acildi | Görüşmede talep açıldı (sade, iletişim bilgisi yok) | id, talep_no, tur, durum, siparis_no, call_id, created_at |
order.applied | Talep sitede otomatik uygulandı (adres, iptal) | request_id, request_number, kind, order_number, action, site, call_id, applied_at |
call.completed | Görüşme bitti (tamamlandı, meşgul, cevapsız, başarısız…) | id, agent_id, channel, status, outcome, phone, callee_name, is_test, summary, created_at, answered_at, ended_at, duration_s, requests |
cagri.etiketlendi | Görüşme etiketlendi ya da etiketi elle düzeltildi | id, agent_id, channel, is_test, created_at, duration_s, summary, cozum, kaynak (otomatik/elle), konu_duzeltme, konular, tagged_at |
arama.planlandi | Giden arama isteği planlandı | Giden arama isteği (aşağıda) |
arama.reddedildi | İstek ön kontrolde reddedildi | Giden arama isteği (sebep) |
arama.tamamlandi | İstek bitti: tamamlandi, basarisiz ya da iptal | Giden arama isteği |
arama.sonuc | Görüşmenin sonuç kodu yazıldı | Giden arama isteği (sonuc_kodu, sonuc) |
kupon.olusturuldu | Asistan görüşmede kupon oluşturdu | id, kod, tip, deger, gecerlilik, arama_id, call_id, siparis_no, created_at (e-posta yok) |
test | Konsoldaki Test gönder | message, endpoint_id |
Giden arama isteği gövdesi: id, amac, tur, iys_kontrolu, kaynak, durum, sebep, planlanan, deneme, siparis_no, sepet_id, musteri (ad, eposta maskeli, telefon_maskeli), tetikleyen (eposta maskeli), call_id, sonuc_kodu, sonuc, parent_id, created_at, updated_at.
Örnek gövdeler
Bölüm başlığı “Örnek gövdeler”{ "id": "0199a1b2-7c3d-7e41-9f00-2b1c3d4e5f60", "event": "request.created", "created_at": "2026-09-24T10:15:02+00:00", "data": { "id": "0199a1b2-6a10-7b22-8c33-1d2e3f405162", "number": "TLP-000123", "call_id": "0199a1b0-1111-7222-8333-944455566677", "kind": "adres_degisikligi", "status": "acik", "order_number": "98765", "order_status": "processing", "customer_name": "Ayşe Yılmaz", "phone": "+90 532 *** ** 67", "details": {"yeni_adres": "…"}, "history": [], "created_at": "2026-09-24T10:15:01+00:00", "updated_at": "2026-09-24T10:15:01+00:00" }}{ "id": "0199a1c0-…", "event": "call.completed", "created_at": "2026-09-24T10:18:40+00:00", "data": { "id": "0199a1b0-1111-7222-8333-944455566677", "agent_id": "0199a0aa-…", "channel": "phone_in", "status": "completed", "outcome": "user_hangup", "phone": "+90 532 *** ** 67", "callee_name": null, "is_test": false, "summary": null, "created_at": "2026-09-24T10:14:02+00:00", "answered_at": "2026-09-24T10:14:03+00:00", "ended_at": "2026-09-24T10:18:39+00:00", "duration_s": 276, "requests": ["TLP-000123"] }}Özet görüşme bitiminde henüz hazır olmayabilir; etiketleme bitince cagri.etiketlendi olayı özet, çözüm ve konularla gelir.
{ "id": "0199a2d0-…", "event": "arama.sonuc", "created_at": "2026-09-25T07:06:12+00:00", "data": { "id": "0199a2c0-…", "amac": "sikayet", "tur": "hizmet", "iys_kontrolu": "gerekmez", "kaynak": "api", "durum": "tamamlandi", "sebep": null, "planlanan": "2026-09-25T07:00:00+00:00", "deneme": 1, "siparis_no": "560991", "sepet_id": null, "musteri": {"ad": "Ayşe Yılmaz", "eposta": "a***@example.com", "telefon_maskeli": "+90 532 *** ** 67"}, "tetikleyen": {"ad": "Can Demir", "eposta": "c***@example.com"}, "call_id": "0199a2c5-…", "sonuc_kodu": "memnun", "sonuc": {"kaynak": "asistan", "memnuniyet": 5, "not": "…"}, "parent_id": null, "created_at": "2026-09-24T12:00:00+00:00", "updated_at": "2026-09-25T07:06:12+00:00" }}{ "id": "0199a1c9-…", "event": "cagri.etiketlendi", "created_at": "2026-09-24T10:19:05+00:00", "data": { "id": "0199a1b0-1111-7222-8333-944455566677", "agent_id": "0199a0aa-…", "channel": "phone_in", "is_test": false, "created_at": "2026-09-24T10:14:02+00:00", "duration_s": 276, "summary": "Müşteri teslimat adresini değiştirmek istedi. Talep açıldı.", "cozum": "talep_acildi", "kaynak": "otomatik", "konu_duzeltme": 0, "konular": [{"id": "0199…", "ad": "Adres değişikliği", "grup": "Sipariş", "kaynak": "otomatik"}], "tagged_at": "2026-09-24T10:19:04+00:00" }}İmzayı doğrulama
Bölüm başlığı “İmzayı doğrulama”X-Zitena-Signature başlığı t=<unix saniye>,v1=<imza> biçimindedir. İmza, HMAC-SHA256(gizli, "<t>.<ham gövde>") değerinin onaltılık (hex) halidir. Doğrularken:
- Başlığı
,ile bölün;tve bir ya da daha fazlav1değerini alın (gizli yenilendiğinde birden fazlav1olabilir; herhangi biri eşleşirse geçerlidir). t’nin şimdiden en çok 300 saniye uzak olduğunu kontrol edin (tekrar oynatma koruması).- Ham gövdeyi (JSON’u ayrıştırıp yeniden yazmadan) kullanarak beklenen imzayı hesaplayın ve sabit zamanlı karşılaştırın.
import crypto from 'node:crypto';import express from 'express';
const SECRET = process.env.RZ_WEBHOOK_SECRET; // whsec_xxxconst app = express();
function verify(header, rawBody) { if (!header) return false; let t = null; const sigs = []; for (const part of header.split(',')) { const [k, v] = part.trim().split('=', 2); if (k === 't' && /^\d+$/.test(v)) t = Number(v); else if (k === 'v1' && v) sigs.push(v); } if (t === null || !sigs.length || Math.abs(Date.now() / 1000 - t) > 300) return false; const expected = crypto.createHmac('sha256', SECRET).update(`${t}.`).update(rawBody).digest('hex'); return sigs.some((s) => s.length === expected.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)));}
app.post('/zitena', express.raw({ type: 'application/json' }), (req, res) => { if (!verify(req.get('X-Zitena-Signature'), req.body)) return res.sendStatus(401); const evt = JSON.parse(req.body.toString('utf8')); // Aynı teslimat birden fazla gelebilir: evt.id'yi (X-Zitena-Delivery) saklayıp tekrarı atlayın. res.sendStatus(204); // hızlı yanıt verin, işi kuyruğa alın});<?php$secret = getenv('RZ_WEBHOOK_SECRET'); // whsec_xxx$raw = file_get_contents('php://input');$header = $_SERVER['HTTP_X_ZITENA_SIGNATURE'] ?? '';
function rz_verify(string $secret, string $header, string $raw): bool { $t = null; $sigs = []; foreach (explode(',', $header) as $part) { [$k, $v] = array_pad(explode('=', trim($part), 2), 2, ''); if ($k === 't' && ctype_digit($v)) { $t = (int) $v; } elseif ($k === 'v1' && $v !== '') { $sigs[] = $v; } } if ($t === null || !$sigs || abs(time() - $t) > 300) { return false; } $expected = hash_hmac('sha256', $t . '.' . $raw, $secret); foreach ($sigs as $s) { if (hash_equals($expected, $s)) { return true; } } return false;}
if (!rz_verify($secret, $header, $raw)) { http_response_code(401); exit; }$event = json_decode($raw, true);http_response_code(204);import hashlib, hmac, time
def verify(secret: str, header: str | None, body: bytes, tolerance: int = 300) -> bool: if not header: return False t, sigs = None, [] for part in header.split(","): k, _, v = part.strip().partition("=") if k == "t" and v.isdigit(): t = int(v) elif k == "v1" and v: sigs.append(v) if t is None or not sigs or abs(time.time() - t) > tolerance: return False expected = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest() return any(hmac.compare_digest(expected, s) for s in sigs)Teslimat ve tekrar denemeler
Bölüm başlığı “Teslimat ve tekrar denemeler”- 2xx yanıt teslim sayılır. Zaman aşımı 10 saniye; yönlendirme izlenmez (3xx başarısızdır).
- Başarısız teslimat 1 dk, 5 dk, 30 dk, 2 sa ve 12 sa sonra yeniden denenir (toplam 6 deneme); sonra “başarısız” olur. Konsoldan elle yeniden deneyebilirsiniz.
- 24 saatten eski bekleyen teslimatlar gönderilmez (süresi doldu): olay bildirimi zamanında değilse anlamsızdır ve kişisel veri taşır.
- Adres her gönderimde yeniden çözülür; özel/yerel IP’ye çıkan adrese istek atılmaz ve bu kalıcı hata sayılır (tekrar denenmez).
- En az bir kez teslim: aynı teslimat birden fazla gelebilir.
X-Zitena-Delivery(gövdedekiid) değerini saklayıp tekrarları atlayın. Olayların sırası garanti değildir;updated_atgibi zaman alanlarını kullanın. - Loglarda ve teslimat hatasında gövde, gizli anahtar ya da yanıt içeriği tutulmaz.
© 2026 Erusoft