İçeriğe geç
ZZenWapp Docs

Webhook ve entegrasyon

Webhook rehberi

Webhook, gelen mesajları ve teslimat durumlarını anlık olarak sizin sisteminize iletir. Sürekli sorgulama yapmak yerine bunu kullanın.

1. Webhook tanımlayın

Uç noktanızı kaydedin. Production ortamında URL HTTPS olmak zorundadır.

curl -X POST "https://api.zenwapp.com/api/v1/whatsapp/external/webhooks" \
  -H "Authorization: Bearer $ZENWAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://sizin-sunucunuz.com/zenwapp/webhook",
    "events": ["message.received", "message.status"],
    "timeout_seconds": 30
  }'

2. Olay tipleri

OlayNe zaman tetiklenir
message.receivedHesaba müşteriden yeni bir mesaj geldiğinde.
message.sentGiden mesajı WhatsApp kabul ettiğinde. Panelden, API'den ve ZenWapp'ın diğer gönderim yollarından giden her mesaj için gelir — API ile kendi gönderdikleriniz dahil. Evolution hatlarında gönderilir; whatsapp-web.js hatlarında gönderilmez.
message.statusTeslimat durumu değiştiğinde (iletildi, okundu). Gövdedeki event alanı alt tipi söyler: message.delivered ya da message.read.
connection.connectedKanal bağlantısı kurulduğunda.
connection.disconnectedKanal bağlantısı koptuğunda.

message.status aboneliğiyle gelen olayların gövdesinde event alanı message.delivered ya da message.read olur; abonelikte ayrıca bu alt tipleri yazmanız gerekmez. Bilinmeyen bir olay tipi geldiğinde hata vermeyin — sessizce yok sayın; ileride yeni tipler eklenebilir.

Evolution hatlarında giden mesaj olaylarının (message.sent, message.delivered, message.read) gövdesi queue_id alanını da taşır. Mesaj gönderim sırasından geçtiyse bu değer POST /messages/send yanıtındaki (202) queue_id ile aynıdır; böylece sıraya alınan isteği gerçek message_id ile eşleştirirsiniz. Sıradan geçmeyen gönderimlerde (örneğin medya) null gelir. Webhook kullanamıyorsanız aynı sonucu GET /messages/queue/{queueId} ile sorgulayabilirsiniz (sonuçlar 24 saat tutulur).

message.sent olayına mesajla cevap vermeyin

message.sent, otomasyonunuzun API ile gönderdiği mesajlar dahil hattan giden her mesaj için gelir. n8n ya da benzeri bir akış bu olaya cevap mesajı gönderirse, o cevap yeni bir message.sent doğurur ve akış kendini durmadan tetikler: sonsuz döngü oluşur, müşteriye arka arkaya mesaj gider. Müşteriden gelen mesajlara cevap veren akışları yalnız message.received olayına bağlayın; message.sent ve message.status olaylarını yalnız kayıt ve takip için kullanın. Webhook kaydında events alanını göndermezseniz varsayılan liste message.sent'i de içerir — ihtiyacınız yoksa listeyi açıkça yazın.

3. İstek başlıkları

ZenWapp her çağrıda şu başlıkları gönderir:

Başlıkİçerik
X-Zenwapp-SignatureGövdenin HMAC-SHA256 imzası (hex).
X-Zenwapp-TimestampUnix zaman damgası (saniye).
X-Zenwapp-EventOlay tipi.
X-Zenwapp-Event-IdBenzersiz olay kimliği (tekrar tespiti için).
X-Zenwapp-Account-IdOlayın ait olduğu kanal hesabı.
User-AgentZenwapp-Webhook/1.0 (Evolution hatlarında Zenwapp-Evolution-Webhook/1.0)

4. İmzayı doğrulayın

İmza, ham istek gövdesi üzerinden hesaplanır. Gövdeyi JSON'a çevirdikten sonra yeniden serileştirirseniz imza tutmaz — framework'ünüzde ham gövdeyi saklayın.

const crypto = require('crypto')
const express = require('express')

const app = express()

// Ham gövdeyi sakla — imza bunun üzerinden hesaplanıyor.
app.use('/zenwapp/webhook', express.raw({ type: 'application/json' }))

function verify(rawBody, signature, secret) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
  const received = Buffer.from(signature || '', 'utf8')
  const computed = Buffer.from(expected, 'utf8')
  // Uzunluk farklıysa timingSafeEqual istisna atar; önce kontrol et.
  if (received.length !== computed.length) return false
  return crypto.timingSafeEqual(received, computed)
}

app.post('/zenwapp/webhook', (req, res) => {
  const signature = req.get('X-Zenwapp-Signature')
  const timestamp = Number(req.get('X-Zenwapp-Timestamp'))

  if (!verify(req.body, signature, process.env.ZENWAPP_WEBHOOK_SECRET)) {
    return res.status(401).send('invalid signature')
  }

  // Replay koruması: 5 dakikadan eski olayları reddet.
  if (!Number.isFinite(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > 300) {
    return res.status(401).send('stale timestamp')
  }

  const event = JSON.parse(req.body.toString('utf8'))

  // Önce 200 dön, ağır işi kuyruğa al.
  res.sendStatus(200)
  enqueue(event)
})

İmza doğrulaması isteğe bağlı değildir

Doğrulama yapmayan bir uç nokta, sizin adınıza sahte mesaj olayları enjekte edilmesine açıktır. Secret'ı POST /webhooks/rotate-secret ile alın ve secret yöneticinizde saklayın.

5. Örnek gövdeler

{
  "event": "message.received",
  "account_id": "b3d1e009-...",
  "chat": {
    "id": "[email protected]",
    "name": "Ahmet Yılmaz",
    "phone": "905551234567",
    "type": "individual"
  },
  "message": {
    "id": "3EB0A1B2C3",
    "from": "[email protected]",
    "to": "[email protected]",
    "body": "Merhaba, fiyat listesi alabilir miyim?",
    "type": "chat",
    "raw_type": "conversation",
    "timestamp": 1785400000,
    "from_me": false,
    "has_media": false,
    "media": null
  }
}

Giden mesaj olaylarında timestamp ISO 8601 biçimindedir (başlıktaki X-Zenwapp-Timestamp ise unix saniyedir). chat_id bireysel sohbetlerde telefon numarasının rakamlarıdır; grup ya da numarası çözülemeyen sohbetlerde sohbet kimliği (JID, örneğin [email protected]) olduğu gibi gelir. queue_id yalnız gönderim sıradan geçtiyse doludur, aksi halde null.

6. Test edin

Canlı trafiği beklemeden kurulumunuzu sınayın — bu uç kayıtlı URL'inize örnek bir olay gönderir:

curl -X POST "https://api.zenwapp.com/api/v1/whatsapp/external/webhooks/test" \
  -H "Authorization: Bearer $ZENWAPP_API_KEY"

Uç nokta tasarım kuralları

  • Hızlı yanıt verin. Önce 200 dönün, işi kuyruğa alın. Yanıt süresi timeout_seconds değerini aşarsa teslimat başarısız sayılır.
  • Tekrarlı teslimata hazır olun. Aynı olay yeniden gönderilebilir; X-Zenwapp-Event-Id değerini saklayıp mükerrer işlemeyi engelleyin.
  • Sıra garantisi varsaymayın. Olaylar sırasız gelebilir; karar verirken timestamp alanına bakın.
  • Bilinmeyen alanları yok sayın. Gövdeye zamanla yeni alanlar eklenir; katı şema doğrulaması entegrasyonunuzu kırar.
  • Hata durumunda 5xx dönün. Böylece teslimat başarısız kabul edilir; 200 dönerseniz olay kaybolur.