Entegrasyon

Webhook’lar

Webhook’lar, tenant’ınızdaki olayları seçtiğiniz HTTPS adresine JSON olarak gönderir. Her istek, adrese özel bir sırla HMAC-SHA256 ile imzalanır.

#Kurulum

  1. Uç noktayı ekleyin

    Yönetim panelinde Webhook’lar sayfasında adresinizi ve almak istediğiniz olayları seçin (webhooks:manage yetkisi gerekir). Adres https olmalıdır; URL’de kullanıcı adı/parola veya # parçası olamaz, özel ağ adresleri ve localhost gibi yerel adlar reddedilir. Tenant başına en fazla 20 uç nokta eklenebilir.

  2. İmzalama sırrını saklayın

    Sır (whsec_…) yalnızca oluşturulduğunda bir kez gösterilir. Kaybederseniz Sırrı yenile ile yenisini alın; eski sır hemen geçersiz olur.

  3. Test gönderin

    Test gönder, webhook.test olayını hemen gönderir ve sonucu (HTTP durumu, süre, hata) gösterir.

#Olaylar

OlayNe zamandata içindeki başlıca alanlar
conversation.openedYeni bir konuşma açıldı.conversation { id, status, websiteId, createdAt }
conversation.status_changedKonuşmanın durumu değişti (ör. AI_ACTIVE, HUMAN_ACTIVE, CLOSED).status, previousStatus, ownerType
conversation.assignedKonuşma bir temsilciye atandı veya aktarıldı.assignedAgent { id, displayName }, previousAgentId, reason, status
message.createdKonuşmaya mesaj eklendi (ziyaretçi, temsilci, yapay zekâ veya sistem). İç notlar gönderilmez.message (MessageDto), sequence
action.updatedBir bonus işleminin durumu değişti.action { id, actionType, status, amountMinor, currency, campaignName, … }
incident.openedOperasyon izleme bir olay açtı.incidentId, kind, subject, rule, severity, title
incident.resolvedBir olay çözüldü.incidentId, kind, subject, rule, severity, title

#İstek ve gövde

Her teslim bir POST isteğidir:

HTTP
POST /hooks/2people HTTP/1.1
Content-Type: application/json
User-Agent: 2people-webhooks/1
X-2people-Event: webhook.test
X-2people-Event-Id: 0b8f3c52-5d3e-4a59-9a43-6c1f0f7f2a10
X-2people-Delivery: 6f1d2a7c-0e8b-4c55-8f0a-2d9e3b1c4a77
X-2people-Signature: t=1767225600,v1=5d41402abc4b2a76b9719d911017c592…

{
  "id": "0b8f3c52-5d3e-4a59-9a43-6c1f0f7f2a10",
  "type": "webhook.test",
  "occurredAt": "2026-01-01T00:00:00.000Z",
  "tenantId": "8c7e…",
  "data": { "message": "Test event from 2people" }
}
AdTürAçıklama
idzorunlustring (uuid)Olay kimliği; X-2people-Event-Id ile aynıdır. Yinelenenleri ayıklamak için kullanın.
typezorunlustringOlay türü; X-2people-Event ile aynıdır.
occurredAtzorunlustring (date-time)Olayın gerçekleştiği zaman.
tenantIdzorunlustring (uuid)Olayın ait olduğu tenant.
datazorunluobjectOlaya özgü içerik. Konuşma, mesaj ve işlem olaylarında conversationId ve visitorId alanlarını da içerir.

X-2people-Delivery teslimin kimliğidir; aynı teslimin yeniden denemelerinde değişmez.

#İmzayı doğrulama

X-2people-Signature başlığı t=<unix saniye>,v1=<imza> biçimindedir. İmza, <t>.<ham gövde> metninin, sır metni (whsec_… olduğu gibi, çözmeden) anahtar olarak kullanılarak alınan HMAC-SHA256 değerinin küçük harf onaltılık gösterimidir.

  1. Başlıktan t ve v1 değerlerini ayırın.
  2. İsteğin ham gövdesini (ayrıştırıp yeniden üretmeden) alın ve t + "." + gövde üzerinde HMAC-SHA256 hesaplayın.
  3. Sonucu v1 ile sabit zamanlı karşılaştırın.
  4. Tekrar saldırılarına karşı t değerinin güncel olduğunu denetleyin (örneklerde 5 dakika tolerans kullanılır).
import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';

const SECRET = process.env.TWOPEOPLE_WEBHOOK_SECRET; // whsec_…
const TOLERANCE_SECONDS = 300;

export function verifySignature(rawBody, header, secret = SECRET) {
  const parts = Object.fromEntries(
    String(header ?? '')
      .split(',')
      .map((p) => p.trim().split('=')),
  );
  const t = Number(parts.t);
  if (!Number.isInteger(t) || !parts.v1) return false;
  if (Math.abs(Date.now() / 1000 - t) > TOLERANCE_SECONDS) return false;
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1, 'hex');
  return given.length === expected.length && timingSafeEqual(given, expected);
}

const app = express();
// Verify against the raw bytes: re-serialised JSON will not match.
app.post('/hooks/2people', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verifySignature(req.body, req.get('X-2people-Signature'))) {
    return res.sendStatus(400);
  }
  const event = JSON.parse(req.body.toString('utf8'));
  // Deduplicate on event.id, then process asynchronously.
  res.sendStatus(204);
});

#Yanıtlar ve yeniden denemeler

10 saniye içinde dönen herhangi bir 2xx yanıtı başarı sayılır. Yönlendirmeler izlenmez. Başarısız teslimler şu aralıklarla yeniden denenir (bir önceki denemeden sonra geçen süre):

DenemeBekleme
1—
21 dk
35 dk
430 dk
52 sa
66 sa
712 sa

Yedinci deneme de başarısız olursa teslim başarısız olarak işaretlenir. Panelin teslim kaydından herhangi bir teslimi elle yeniden deneyebilirsiniz.