Webhooks
Hesap başına tek imzalı uç nokta. İmza doğrulama, olay kataloğu, yeniden deneme politikası ve gizli anahtar rotasyonu.
WASync'i uç noktanıza yönlendirin; karşılığında bir imzalama gizli anahtarı alırsınız. whatsapp.events kullanılır. Uygulama kaydı ve onay adımı gerekmez.
Hesap başına tek webhook yuvası
Bir hesabın yalnızca bir webhook URL'si vardır. PUT /webhook bu URL'yi ayarlar. Farklı bir URL ile tekrar çağırırsanız ikinci bir uç nokta eklenmez, mevcut uç nokta taşınır; silme işlemi de yoktur. Birden fazla sistem olay almak istediğinde bu önem kazanır: bir n8n tetikleyicisi ile kendi backend'iniz yuvayı aynı anda kullanamaz. Olayları kendi tarafınızda dağıtın.
Uç noktayı ayarlayın
curl -X PUT https://developers.wasync.app/api/v1/webhook \
-H "Authorization: Bearer $WASYNC_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/wasync/webhook"}'{
"url": "https://example.com/wasync/webhook",
"events": [
"message.received",
"message.status",
"connection.disconnected",
"connection.connected"
],
"secret": "whsec_3f9a…"
}Gizli anahtarı saklayın. Doğrulama kodunuzu yazarken ihtiyaç duyacağınız an bu olduğu için anahtar burada verilir. GET /webhook onu bir daha döndürmez; bu uç nokta bilerek gizli anahtar olmadan { url, events, secretSet } bildirir, çünkü okuma yolları günlüklere düşer ve destek taleplerine yapıştırılır. Anahtarı mı kaybettiniz? Yenisi için POST /webhook/rotate çağırın. URL, herkese açık bir ana makinede HTTPS olmalıdır; http, localhost ve özel aralıklar 400 invalid_webhook ile reddedilir.
Rotasyonda çakışma süresi yoktur: yeni gizli anahtar verildiği anda eskisi geçersiz olur. Yeni anahtarı hemen dağıtın. Aşağıdaki yeniden deneme politikası saatleri değil, saniyelerle ölçülen bir geçişi karşılar.
Olay kataloğu
| Event | Fires when | Direction |
|---|---|---|
message.received | Yetki verilmiş bir bağlantıya WhatsApp mesajı geldiğinde (metin veya medya). | Yalnızca gelen. Sizin gönderdiğiniz mesajlar bunu tetiklemez. |
message.status | Gönderdiğiniz bir mesajın teslim durumu değiştiğinde. | Yalnızca giden. |
connection.disconnected | Bir bağlantı çalışmayı bıraktığında ve sahibinin yeniden eşleştirmesi gerektiğinde. | Bozuk kaldığı sürece tekrar tekrar değil, kesinti başına bir kez. |
connection.connected | Bozulmuş bir bağlantı yeniden çalıştığında. | Bir kez ve yalnızca bozulduğu size bildirilmiş bağlantı için. |
Bilinmeyen event değerlerini hata saymayın, yok sayın. Yeni türler yayına girmeden önce changelog sayfasında duyurulur.
Olaylar yalnızca qr bağlantıları için tetiklenir. cloud_api bağlantıları bugün webhook göndermez; bunlar için GET /messages ve GET /connections/{id} uç noktalarını sorgulayın.
Payload
{
"event": "message.received",
"connection_id": "conn_8f3a21",
"message": {
"id": "cmqj3k2ab0001xyz",
"wa_id": "[email protected]_3EB0A1B2C3",
"from": "40700000000",
"text": "Hello!",
"media_url": null,
"media_type": null,
"timestamp": 1766138640000
}
}message.id: WASync'in kalıcı kimliği (GET /messagesbu değeriidolarak döndürür). Teslimat en az bir kez yapıldığı için tekilleştirmeyi ve eşleştirmeyi bununla yapın.message.wa_id: WhatsApp'ın kendi kimliği; bilinmiyorsanull. Yalnızca destek ve hata ayıklama içindir, asla tekilleştirme veya eşleştirme anahtarı olarak kullanılmaz.from: yalnızca uluslararası biçimde rakamlar,+yok.- Yalnızca medya içeren mesajlarda
textdeğerinullolur. Metin mesajlarında veya dosya herkese açık biçimde alınamıyorsamedia_url/media_typedeğerlerinullolur. timestamp: ISO dizesi değil, sayı olarak epoch milisaniye.
message.status teslimatı, giden mesajlarınızdan birini günceller:
{
"event": "message.status",
"connection_id": "conn_8f3a21",
"message": {
"id": "cmqj3k2ab0001xyz",
"wa_id": "[email protected]_3EB0A1B2C3",
"status": "read",
"timestamp": 1766138641000
}
}İmzayı doğrulayın
Her teslimat X-WASync-Timestamp (epoch ms) ve X-WASync-Signature: sha256=<hex> başlıklarını taşır; imza HMAC-SHA256(timestamp + "." + rawBody, secret) değeridir. İmzayı ham gövde üzerinden, herhangi bir JSON ayrıştırmasından önce hesaplayın ve sabit zamanlı karşılaştırma yapın. Zaman damgası yaklaşık 5 dakikadan eski olan teslimatları reddedin; tekrar saldırılarını bu engeller.
import crypto from "node:crypto";
// rawBody MUST be the exact bytes you received (verify BEFORE JSON.parse).
export function verifyWASync(headers, rawBody, secret) {
const ts = headers["x-wasync-timestamp"];
const sig = headers["x-wasync-signature"]; // "sha256=<hex>"
const expected =
"sha256=" +
crypto.createHmac("sha256", secret)
.update(`${ts}.${rawBody}`)
.digest("hex");
return Boolean(sig) &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}import express from "express";
import { verifyWASync } from "./verify.js";
const app = express();
// Capture the RAW body — signature verification needs the exact bytes.
app.post("/webhooks/wasync", express.raw({ type: "application/json" }), (req, res) => {
if (!verifyWASync(req.headers, req.body.toString("utf8"), process.env.WASYNC_WEBHOOK_SECRET)) {
return res.status(401).end();
}
// Reject stale deliveries (replay window) — 5 minutes is a sane tolerance.
if (Math.abs(Date.now() - Number(req.headers["x-wasync-timestamp"])) > 5 * 60_000) {
return res.status(401).end();
}
res.status(200).end(); // ACK fast — under 10s — then process async
const { message } = JSON.parse(req.body.toString("utf8"));
// ... your logic (dedupe on message.id — retries can deliver duplicates)
});Teslimat ve yeniden deneme politikası
| Property | Value |
|---|---|
| Success criterion | Herhangi bir 2xx. Yönlendirmeler dahil diğer her şey başarısızlıktır. |
| Attempts | Olay başına 3: hemen, ardından 500 ms sonra, ardından 1 sn sonra. |
| Per-attempt timeout | 10 saniye. Hızlı yanıt verin (ACK), işlemi asenkron yapın. |
| After the last failure | Olay düşürülür. Uzun süreli bir yeniden teslim kuyruğu yoktur. |
| Duplicates | Mümkündür. message.id ile tekilleştirin. |
| Ordering | Garanti edilmez. Önemliyse timestamp ile sıralayın. |
| Signature on retries | Aynı gövde ve aynı imza yeniden gönderilir; her denemeyi aynı şekilde doğrulayın. |
Durum merdiveni
message.status değerleri tek yönlü bir merdiven oluşturur: sent (1) → delivered (2) → read (3). failed ise read ile devam etmeyen, sonlandırıcı bir sonuçtur.
Güncellemeleri tek yönlü uygulayın. Yeni durumu yalnızca sırası, sakladığınız durumdan yüksekse uygulayın. Bir mesaj delivered olmadan önce read ile gelebilir (gecikmiş teslimat) ve yinelenen olaylar mümkündür. Bir mesajı arayüzünüzde asla geriye taşımayın.
Mutabakat: güvenlik ağı olarak sorgulama
Teslimatlar üç kez denenir ve ardından düşürülür; bu nedenle webhook işleyiciniz hızlı yoldur, garanti değildir. Her 30–60 saniyede bir GET /messages?connectionId=… çağıran bir arka plan döngüsü çalıştırın ve işleyicinin görmediği her şeyi imleci (cursor) kullanarak artımlı çekimle ekleyin veya güncelleyin. Aynı döngü, uç noktanız kapalıyken gelen durum güncellemelerini de geriye dönük tamamlar.
Webhook'lar = gerçek zamanlı arayüz güncellemeleri. Sorgulama = doğruluk. İkisini birlikte çalıştırın.
Bağlantı yaşam döngüsü
{
"event": "connection.disconnected",
"connection_id": "conn_8f2a…",
"connection": {
"id": "conn_8f2a…",
"phone_number": "393331234567",
"label": "Studio Rossi",
"status": "disconnected",
"needs_reconnect": true,
"license_status": "active",
"license_expires": "2027-08-08T09:00:00.000Z",
"reason": "AUTH_LOST"
},
"timestamp": 1786000000000
}Bir qr bağlantısı kimsenin kontrolünde olmayan nedenlerle çalışmayı bırakabilir: telefon bir gün kapalı kalır, WhatsApp başka bir cihazda açılır, oturum zaman aşımına uğrar. Numara bu durumda her iki yönde de sessizleşir ve bir olay olmazsa fark eden ilk kişi müşteriniz olur.
QR kodu bilerek payload'da yer almaz. WhatsApp QR'ı, taranınca oturum açtıran bir kimlik bilgisidir; onu okuyan herkes oturumu ele geçirebilir. Olay yalnızca yeniden eşleştirme gerektiğini bildirir. QR'ın kendisini kimliği doğrulanmış GET /connections/{id} çağrınızla alın ve ilk kurulumdaki gibi numaranın sahibine gösterin. Arayüzünüzü needs_reconnect alanına göre yönetin; yeni değerler ortaya çıkabileceği için reason alanını yalnızca bilgilendirici bir günlük ipucu olarak değerlendirin.
Tespit gecikmesi: bağlantı kopması, 5 dakikalık bir durum yoklamasıyla bulunur (~5 dk). Oturum kurtarılabilir görünüyorsa önce otomatik onarım denenir; bu yol yaklaşık 15–20 dakika sürer.
Olay almıyor musunuz?
- Teslimat URL'si herkese açık bir ana makinede HTTPS mi? Localhost, özel IP'ler ve
http://reddedilir. Geliştirme sırasında bir tünel kullanın ve gerçekte neyin kayıtlı olduğunuGET /webhookile kontrol edin. - Kimlik bilgisi
whatsapp.eventskapsamını taşıyor mu ve mesajın geldiği bağlantıyı kapsıyor mu? - Bağlantının lisansı hâlâ etkin mi? Süresi dolmuş lisans, olay gelmemesi ve gönderimlerde
402anlamına gelir. - Uç noktanız 10 saniye içinde
2xxyanıtı veriyor mu? Yavaş işleyiciler başarısız görünür ve üç denemenin hepsini tüketir. - Bu bir
qrbağlantısı mı?cloud_apibağlantıları webhook göndermez. message.receivedyalnızca gelen mesajlarda tetiklenir; kendi gönderimleriniz asla tetiklemez.
MCP
Claude, Cursor veya herhangi bir MCP istemcisini uzak WASync sunucusuna bağlayın. Altı araç, sizin tarafınızda tek satır kod yok.
Eşleştirme kodu ve QR
QR kodu her 20 saniyede bir yenilenir. Her iki yolu da sunun; çünkü tek cihazla yapılan bir kurulumda kullanıcı kendi ekranının fotoğrafını çekemez.