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
| Olay | Ne zaman tetiklenir |
|---|---|
message.received | Hesaba müşteriden yeni bir mesaj geldiğinde. |
message.sent | Giden 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.status | Teslimat durumu değiştiğinde (iletildi, okundu). Gövdedeki event alanı alt tipi söyler: message.delivered ya da message.read. |
connection.connected | Kanal bağlantısı kurulduğunda. |
connection.disconnected | Kanal 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-Signature | Gövdenin HMAC-SHA256 imzası (hex). |
X-Zenwapp-Timestamp | Unix zaman damgası (saniye). |
X-Zenwapp-Event | Olay tipi. |
X-Zenwapp-Event-Id | Benzersiz olay kimliği (tekrar tespiti için). |
X-Zenwapp-Account-Id | Olayın ait olduğu kanal hesabı. |
User-Agent | Zenwapp-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
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
200dönün, işi kuyruğa alın. Yanıt süresitimeout_secondsdeğerini aşarsa teslimat başarısız sayılır. - Tekrarlı teslimata hazır olun. Aynı olay yeniden gönderilebilir;
X-Zenwapp-Event-Iddeğerini saklayıp mükerrer işlemeyi engelleyin. - Sıra garantisi varsaymayın. Olaylar sırasız gelebilir; karar verirken
timestampalanı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;
200dönerseniz olay kaybolur.