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
Uç noktayı ekleyin
Yönetim panelinde Webhook’lar sayfasında adresinizi ve almak istediğiniz olayları seçin (
webhooks:manageyetkisi gerekir). Adreshttpsolmalıdır; URL’de kullanıcı adı/parola veya#parçası olamaz, özel ağ adresleri velocalhostgibi yerel adlar reddedilir. Tenant başına en fazla 20 uç nokta eklenebilir.İ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.Test gönderin
Test gönder,
webhook.testolayını hemen gönderir ve sonucu (HTTP durumu, süre, hata) gösterir.
#Olaylar
| Olay | Ne zaman | data içindeki başlıca alanlar |
|---|---|---|
conversation.opened | Yeni bir konuşma açıldı. | conversation { id, status, websiteId, createdAt } |
conversation.status_changed | Konuşmanın durumu değişti (ör. AI_ACTIVE, HUMAN_ACTIVE, CLOSED). | status, previousStatus, ownerType |
conversation.assigned | Konuşma bir temsilciye atandı veya aktarıldı. | assignedAgent { id, displayName }, previousAgentId, reason, status |
message.created | Konuşmaya mesaj eklendi (ziyaretçi, temsilci, yapay zekâ veya sistem). İç notlar gönderilmez. | message (MessageDto), sequence |
action.updated | Bir bonus işleminin durumu değişti. | action { id, actionType, status, amountMinor, currency, campaignName, … } |
incident.opened | Operasyon izleme bir olay açtı. | incidentId, kind, subject, rule, severity, title |
incident.resolved | Bir olay çözüldü. | incidentId, kind, subject, rule, severity, title |
#İstek ve gövde
Her teslim bir POST isteğidir:
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" }
}| Ad | Tür | Açıklama |
|---|---|---|
idzorunlu | string (uuid) | Olay kimliği; X-2people-Event-Id ile aynıdır. Yinelenenleri ayıklamak için kullanın. |
typezorunlu | string | Olay türü; X-2people-Event ile aynıdır. |
occurredAtzorunlu | string (date-time) | Olayın gerçekleştiği zaman. |
tenantIdzorunlu | string (uuid) | Olayın ait olduğu tenant. |
datazorunlu | object | Olaya ö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.
- Başlıktan
tvev1değerlerini ayırın. - İsteğin ham gövdesini (ayrıştırıp yeniden üretmeden) alın ve
t + "." + gövdeüzerinde HMAC-SHA256 hesaplayın. - Sonucu
v1ile sabit zamanlı karşılaştırın. - Tekrar saldırılarına karşı
tdeğ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):
| Deneme | Bekleme |
|---|---|
| 1 | — |
| 2 | 1 dk |
| 3 | 5 dk |
| 4 | 30 dk |
| 5 | 2 sa |
| 6 | 6 sa |
| 7 | 12 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.