Skip to content

Webhooks

Zitena reports events such as requests, calls and outbound calls to your URL with signed POST requests. Endpoints are managed on Settings → Webhooks (plan: Webhooks; writing needs admin or owner).

Webhook delivery1. Event happens (e.g. request opened) → 2. Delivery queue (separate per endpoint) → 3. Signed POST (X-Zitena-Signature) → 4. 2xx = delivered (otherwise retried)Event happense.g. request openedDelivery queueseparate per endpointSigned POSTX-Zitena-Signature2xx = deliveredotherwise retried
  1. New endpoint: URL (https:// only; addresses leading to an internal network or private IP are rejected), description and the events to listen to.
  2. After saving, the secret (whsec_…) is shown once. Keep it on your server; change it with Rotate secret.
  3. Use Send test to send the test event right away and see the result.

Each endpoint shows the status of its last delivery and the number of failed deliveries in the last 24 hours; the Deliveries list shows each delivery’s status, attempts, response code and body, and you can retry a failed delivery by hand.

POST https://your-endpoint.example.com/zitena
Content-Type: application/json
User-Agent: Zitena-Webhook/1
X-Zitena-Event: request.created
X-Zitena-Delivery: 0199a1b2-… (delivery id; same on retries)
X-Zitena-Signature: t=1790000000,v1=5f2c…
{"id": "0199a1b2-…", "event": "request.created", "created_at": "2026-09-24T10:15:00+00:00", "data": {…}}
EventWhendata
request.createdA request was opened during a callRequest: id, number, call_id, kind, status, order_number, order_status, customer_name, email, phone, details, history, created_at, updated_at
request.status_changedA request’s status changedRequest + previous_status
talep.acildiA request was opened (lean, no contact data)id, talep_no, tur, durum, siparis_no, call_id, created_at
order.appliedA request was applied on the site automatically (address, cancellation)request_id, request_number, kind, order_number, action, site, call_id, applied_at
call.completedA call ended (completed, busy, no answer, failed…)id, agent_id, channel, status, outcome, phone, callee_name, is_test, summary, created_at, answered_at, ended_at, duration_s, requests
cagri.etiketlendiA call was tagged or its tags were corrected by handid, agent_id, channel, is_test, created_at, duration_s, summary, cozum, kaynak (otomatik/elle), konu_duzeltme, konular, tagged_at
arama.planlandiAn outbound call request was scheduledOutbound call request (below)
arama.reddedildiA request was rejected by a pre-checkOutbound call request (sebep)
arama.tamamlandiA request finished: tamamlandi, basarisiz or iptalOutbound call request
arama.sonucThe call’s outcome code was recordedOutbound call request (sonuc_kodu, sonuc)
kupon.olusturulduThe assistant created a coupon during a callid, kod, tip, deger, gecerlilik, arama_id, call_id, siparis_no, created_at (no email)
testSend test in the consolemessage, endpoint_id

Outbound call request payload: id, amac, tur, iys_kontrolu, kaynak, durum, sebep, planlanan, deneme, siparis_no, sepet_id, musteri (ad, eposta masked, telefon_maskeli), tetikleyen (eposta masked), call_id, sonuc_kodu, sonuc, parent_id, created_at, updated_at.

{
"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",
"email": "[email protected]",
"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"
}
}

The X-Zitena-Signature header has the form t=<unix seconds>,v1=<signature>. The signature is the hex value of HMAC-SHA256(secret, "<t>.<raw body>"). To verify:

  1. Split the header on ,; take t and one or more v1 values (after a secret rotation there can be several v1; any match is valid).
  2. Check that t is at most 300 seconds from now (replay protection).
  3. Compute the expected signature over the raw body (don’t parse and re-serialise the JSON) and compare in constant time.
import crypto from 'node:crypto';
import express from 'express';
const SECRET = process.env.RZ_WEBHOOK_SECRET; // whsec_xxx
const 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'));
// The same delivery can arrive more than once: store evt.id (X-Zitena-Delivery) and skip repeats.
res.sendStatus(204); // answer fast, queue the work
});
  • A 2xx response counts as delivered. Timeout 10 seconds; redirects are not followed (3xx fails).
  • A failed delivery is retried after 1 min, 5 min, 30 min, 2 h and 12 h (6 attempts in total), then marked failed. You can retry by hand from the console.
  • Pending deliveries older than 24 hours are not sent (expired): a late event notification is meaningless and carries personal data.
  • The address is resolved again on every send; requests to private/local IPs are never made and count as permanent errors (not retried).
  • At-least-once delivery: the same delivery can arrive more than once. Store X-Zitena-Delivery (the body’s id) and skip repeats. Order is not guaranteed; use time fields such as updated_at.
  • Logs and delivery errors never contain the body, the secret or the response content.