Bir WhatsApp ajanı inşa et
OpenClaw + TimelinesAI ile
WhatsApp'ı OpenClaw ajanının çalıştırdığı bir müşteri kanalına dönüştür: gelen mesajlara otomatik yanıt ver, transactional ve olay tetikli bildirimler gönder, CRM'inle senkronize et, inbox'ı ekiple paylaş. TimelinesAI WhatsApp geçidini yürütür. Ajanın muhakemeyle ilgilenir.
Otomatik yanıtTransactional gönderimlerCRM senkronuPaylaşılan inbox
Ajanın WhatsApp ile neler yapabilir
Bir TimelinesAI skill'i kuruldu mu, OpenClaw ajanın WhatsApp'ı gerçek bir müşteri kanalı olarak çalıştırabilir. Bu listenin uzun versiyonu aşağıdaki bölümlerde — her yetenek ihtiyaç duyduğu spesifik API çağrılarına sahip.
Gelen müşteri mesajlarını otomatik yanıtla — 7/24 autoresponder, mesai dışı yanıtlayıcı veya takılınca insana devreden tam bir AI chatbot.
Transactional ve olay tetikli mesajlar gönder — sipariş onayları, kargo güncellemeleri, randevu hatırlatmaları, ödeme makbuzları ve diğer araçlardaki olaylar (HubSpot, Stripe, Calendly) tarafından tetiklenen bildirimler. Sadece mesajı bekleyen müşterilere.
Çok turlu konuşmalarla lead nitelendir — bir dizi soru sor, cevapları sakla, sohbeti qualified ya da değil olarak etiketle.
WhatsApp aktivitesini CRM'inle senkronize et — gelen numaraları HubSpot/Pipedrive'da ara, deal aşamalarını güncelle, notları geri yaz.
Konuşmaları özetle ve puanla — “ACME geçen hafta ne sordu”, “bu sohbeti niyet için 1-10 puanla”.
Inbox'ı ekiple paylaş — ajanın yanıtları özel not olarak hazırlasın, insanlar göndersin; ya da ajan göndersin ve insanlar izlesin.
Medyayı işle — makbuz fotoğrafları, PDF'ler, sesli notlar — OpenClaw tarafından işlenir, otomatik yanıtlanır.
Bu rehber nasıl kullanılır
Bu rehber dual-mode. Kendin okuyup Setup'ı manuel olarak yapabilirsin — aşağıdaki bölümlerin serdiği yol — ya da tüm rehberi OpenClaw agent'ına (veya URL okuyabilen herhangi bir agent'a) verebilir ve setup'ı senin yerine yapmasına izin verebilirsin. Her iki yol da aynı yere varıyor: kurulmuş dört WhatsApp skill'i, bağlanmış token ve webhook, müşteri mesajlarına cevap veren agent'ın.
Eğer bunu kendin okuyorsan
Aşağıdaki capability gruplarını — Incoming, Outbound, CRM & analytics, Operations — gözden geçir ve hangisinin kullanım durumun için önemli olduğuna karar ver. Sonra Setup'ı manuel olarak takip et; dört adım, yaklaşık on dakika. Her capability yaptığı tam API çağrılarını listeler ve bu rehberdeki her code block yayınlanmadan önce gerçek TimelinesAI API'ına karşı çalıştırıldı. Tıkanırsan Bilmen gerekenler, önceden bilmezsen seni bir saate mal olacak her gotcha'yı toplar.
Setup'ı agent'ın yapmasını istiyorsan
Bu prompt'u OpenClaw'a (ya da Claude Code, Cursor, Claude desktop, URL okuyabilen herhangi bir agent'a) yapıştır. Bu rehbere URL ile işaret eder ki agent live sürümü okusun, sonra seni install, token, webhook, smoke-test ve ilk skill'ini etkinleştirme boyunca gezdirir — sadece gerçekten senden bir şey gereken yerlerde input ister.
Read https://timelines.ai/guide/openclaw-whatsapp-skills end to end.
Then help me set up an OpenClaw + TimelinesAI WhatsApp agent:
1. Clone InitechSoftware/openclaw-whatsapp-skills into ~/.openclaw/workspace/
and symlink the four skills into ~/.openclaw/skills/.
2. Ask me for my TimelinesAI API token (Integrations → Public API → Copy
on app.timelines.ai) and write it to ~/.openclaw/workspace/.env.timelinesai
as TIMELINES_AI_API_KEY.
3. Run the smoke-test curl from Setup. If it fails, walk me through the
"If you see something else" failure block in the guide.
4. Help me deploy examples/vercel-webhook-receiver from the companion repo
and register the webhook with TimelinesAI.
5. Ask which of the four skills I want to enable first — whatsapp-autoresponder,
whatsapp-lead-qualifier, whatsapp-send, or whatsapp-delivery-check — and
walk me through its capability section plus any relevant gotchas from
Things-to-know.
6. Before enabling anything that sends outbound messages, show me Channel
choice so I pick the right WhatsApp channel (personal number vs Business
API) for my use case.Prompt bilinçli olarak herhangi bir outbound göndermeyi açmadan önce durur — gerçekten yapmaya çalıştığın şey için doğru WhatsApp kanalını (personal numara vs Business API) seçmenin için seni önce Kanal seçimi'nden geçirir. Ban riski gerçek ve upstream'de yaşar, gateway'in seni koruyabileceği bir şey değil.
Kurulum
Bağlanacak dört şey. Sonrasında skill'in sadece API çağrıları yapar.
- 1
WhatsApp numaranı bağla
app.timelines.ai'a giriş yap, iş numaranı taşıyan telefonla QR kodu tara. TimelinesAI bundan sonra geçidi yürütür. Standart personal-number kurulumuyla.
- 2
Bir API token'ı al
Integrations → Public API → Copy. TIMELINES_AI_API_KEY olarak kaydet. Tek token tüm workspace'i kapsar.
- 3
Companion repodan bir skill kur
InitechSoftware/openclaw-whatsapp-skills'i ~/.openclaw/workspace/ altına klonla, sonra her skill dizinini ~/.openclaw/skills/ altına symlinkle. Dört satırlık quickstart companion README’de.
- 4
Bir webhook kayıt et
message:received:new'i bir public HTTPS URL'sine yönlendir. TimelinesAI gelen mesajları oraya pushlar. Receiver'ın OpenClaw'ı çağırır.
Token smoke testi
Herhangi bir şey inşa etmeden önce auth'un çalıştığını onayla:
curl -sS -w "\nHTTP: %{http_code}\n" \
-H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
https://app.timelines.ai/integrations/api/whatsapp_accountsŞunu görmelisin
{
"status": "ok",
"data": {
"whatsapp_accounts": [
{
"id": "<phone>@s.whatsapp.net",
"phone": "+<phone>",
"status": "connected",
"account_name": "<your label>"
}
]
}
}Başka bir şey görüyorsan
- HTTP 401 ve çıplak Unauthorized metni — token geçmemiş. Adım 2'yi tekrar kontrol et, Bearer önekini eklediğinden ve tokenin yapıştırırken satır atlamadığından emin ol. Cevap düz metin, JSON değil — jq'ya pipe edersen hata verir.
- HTTP 404 ile bootstrap stilinde HTML “Page not found” sayfası — sonda slash ya da path'de typo var. API hatalı path'ler için HTML döner (JSON error değil), yani çıktın <!DOCTYPE html> ile başlıyorsa sondaki slash'ı kaldır ve path'i tekrar kontrol et.
Webhook'u bir kez kayıt et
# Generate a random secret once. Add it as a query param so only
# TimelinesAI's registered URL can reach your receiver.
WEBHOOK_SECRET=$(openssl rand -hex 16)
WEBHOOK_URL="https://your-app.example.com/api/webhook?secret=${WEBHOOK_SECRET}"
curl -sS -X POST \
-H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"event_type\":\"message:received:new\",\"url\":\"${WEBHOOK_URL}\",\"enabled\":true}" \
https://app.timelines.ai/integrations/api/webhooksMinimal webhook receiver (Node / Vercel)
~40 satır. ?secret= query param'ını doğruluyor, giden echo'ları düşürüyor, 5 saniyelik retry penceresi içinde 2xx döndürüyor, sonra mesajı OpenClaw host'una iletir.
// api/webhook.js
export default async function handler(req, res) {
// Path-segment auth — TimelinesAI doesn't sign webhooks yet, so the
// ?secret=<token> you registered is the only thing stopping strangers
// from invoking this endpoint.
if (req.query.secret !== process.env.WEBHOOK_SECRET) {
return res.status(404).end();
}
if (req.method !== "POST") return res.status(405).end();
const { event_type, data } = req.body || {};
if (event_type !== "message:received:new") {
return res.status(200).json({ ignored: event_type });
}
// Drop your agent's own sends echoing back as "received".
// TimelinesAI also fires message:received:new for outbound messages
// synced from another WhatsApp client on the same number.
if (data.from_me === true) {
return res.status(200).json({ ignored: "from_me" });
}
// Ack FAST. TimelinesAI retries 3x with a 5-second timeout per attempt —
// if you take longer than 5s the same event hits you up to 3 times.
res.status(200).json({ ok: true });
// TL webhook payloads sometimes arrive flat (data.chat_id) and sometimes
// nested (data.chat.id) depending on event flavor and version. Destructure
// with fallback so both shapes work — see Things to know below.
const chatId = data.chat_id ?? data.chat?.id;
const whatsappAccountId =
data.whatsapp_account_id ?? data.chat?.whatsapp_account_id;
// Fire-and-forget handoff to wherever your OpenClaw host accepts
// inbound messages. For production durability replace this with a
// push to a queue (QStash, Inngest, SQS) your agent drains separately.
try {
await fetch(process.env.OPENCLAW_HOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: chatId,
whatsapp_account_id: whatsappAccountId,
text: data.text,
sender_phone: data.sender_phone,
message_uid: data.message_uid,
}),
});
} catch (err) {
console.error("[timelinesai-webhook] handoff failed:", err);
}
}OPENCLAW_HOOK_URL, OpenClaw dağıtımının gelen mesajlar için açtığı endpoint — agent'ı nasıl host ettiğine bağlı. Durability notları olan production-ready bir referans için companion repodaki examples/vercel-webhook-receiver'a bak.
OpenClaw workspace'inin içinde ne var
Bir TimelinesAI token'ı bağladın ve companion repodan skill'leri symlink ettin. İşte gerçekte ne kurduğun ve agent'ının nasıl düşündüğünü, neyi bildiğini ve nasıl davrandığını değiştirmek istediğinde hangi dosyaları düzenleyeceğin. Burada sihir yok — iki dizinde basit markdown ve env dosyaları hepsi bu.
Skills — ~/.openclaw/skills/<skill>/
Symlink'lediğin her skill, bir zorunlu dosya (SKILL.md) ve o dosyanın referans verdiği kaynakları olan bir dizindir. FAQ handler'ının tonunu değiştirmek veya lead qualifier'ın sorularını yeniden sıralamak istiyorsan, düzenleme o skill'in SKILL.md'sinde yaşar. Derlenecek kod yok, build step yok.
| Dosya | Neyi kontrol ediyor |
|---|---|
| SKILL.md | Skill'in tümü. Üstünde YAML frontmatter (name, description, user-invocable ve dispatch flag'leri) artı altında instructions/prompt body. Burada skill'in ne yaptığını, OpenClaw'un onu ne zaman seçtiğini ve nasıl davrandığını düzenlersin. Companion repodaki dört WhatsApp skill'inin her biri tek bir SKILL.md — derlenecek kod yok, sadece düzenleyip yeniden başlatırsın. |
| README.md | Skill için insan tarafına dokümantasyon. OpenClaw bu dosyayı OKUMAZ — bu sana, takımına ve GitHub'da skill'e bakan herkes için. Opsiyonel; companion repodaki skill'ler hepsi bir tane taşır. |
| Scripts, resources, schemas | SKILL.md içinden {baseDir}/... yoluyla referans verilen her şey — küçük helper script'ler, prompt template'leri, JSON schema'lar, test fixture'ları. SKILL.md'in onlara açıkça işaret etmediği sürece OpenClaw tarafından göz ardı edilir. Skill'in neye ihtiyacı varsa SKILL.md'nin yanına koy ve path ile referans ver. |
Dört WhatsApp skill'i için — whatsapp-autoresponder, whatsapp-lead-qualifier, whatsapp-send, whatsapp-delivery-check — her SKILL.md'nin üst kısmında role ve izin verilen tool'lar var; body'de agent'ının çalıştığı gerçek prompt var. Düzenlemeden önce birini baştan sona oku; pattern'ler tekrar ediyor.
Workspace — ~/.openclaw/workspace/
Workspace dizini OpenClaw'un skill olmayan her şey için evidir: agent'ının kim olduğu, hakkında neyi bildiği, hangi tool'lara sahip olduğu, nasıl boot ettiği. OpenClaw'un kendi dokümanları “bu klasör evdir. Öyle davran” der. Bu dosyalar install ile gelir — zamanla doldurursun:
| Dosya | Neyi kontrol ediyor |
|---|---|
| IDENTITY.md | Agent'ın kim olduğu — isim, creature (kendisini nasıl kavramsallaştırdığı), vibe, imza emojisi, avatar. Erken doldur; diğer dosyaların çoğu buna referans verir. |
| SOUL.md | Temel çalışma prensipleri ve kişilik. Kimsenin bakmadığı zaman agent'ın nasıl davranması gerektiği — ne zaman samimi vs performatif, nasıl güven kazanacağı, gizlilik sınırlarının nerede olduğu. OpenClaw dokümanları buna agent'ın vicdanı der. |
| USER.md | Agent'ın yardım ettiği kişinin kim olduğu (sen). İsim, zamirler, timezone, ilgi alanları, proje bağlamı. Agent'lar genel bir “user” yerine belirli birine yardım etmekte daha iyidir — bu dosya onların nasıl hatırladığıdır. |
| TOOLS.md | Hiçbir skill'in içinde istemediğin ortama özgü config — cihaz adları, host adresleri, yerel tercihler. Workspace'te yaşar ki paylaşabileceğin hiçbir şeye dokunmadan düzenleyebilesin. |
| AGENTS.md | Agent'lar için workspace README'si. OpenClaw'un bu workspace'in nasıl çalıştırılmasını beklediğini tanımlar. Genelde install ile gelir ve nadiren düzenlenir. |
| BOOT.md | OpenClaw'un başlangıçta ne yapması gerektiğine dair kısa, açık talimatlar. Varsayılan olarak boş; yeniden başlatmalar arasında deterministik boot davranışı istiyorsan doldur. |
| BOOTSTRAP.md | İlk boot'ta onboarding konuşması. Taze bir workspace'i kimliğini oluşturmaya yönlendirir, sonra sonuçları IDENTITY.md, SOUL.md, USER.md'ye kaydetmen için yönlendirir. Diğerlerini doldurduktan sonra bu dosyayı sil. |
| HEARTBEAT.md | Periyodik task tanımları. Boş dosya = heartbeat yok; agent'ın bir şeyi aralıkla kontrol etmesini istediğinde (örn. beş dakikada bir queue poll'u) buraya task'lar ekle. |
Entegrasyon başına env dosyaları
Yukarıdaki canonical dosyaların yanında, bağladığın her entegrasyon için bir env dosyası eklersin. Bunlar OpenClaw install'ın PARÇASI DEĞİL — bunlar senin, ve public bir repoya asla commit edilmemesi gereken secret'ları tutar:
| Dosya | Neyi kontrol ediyor |
|---|---|
| .env.<integration> | Bir entegrasyon için secret'lar ve config, entegrasyon başına bir dosya. WhatsApp işi için Setup adım 2'de TIMELINES_AI_API_KEY ve ALLOWED_SENDER_JID ile .env.timelinesai oluşturdun. HubSpot, Stripe, Pipedrive veya başka bir tool eklerken bu pattern'i tekrarla — bir .env.<name> ve ihtiyacı olan skill'de source eder. |
Personal numaralar vs WhatsApp Business API
Giden akışları inşa etmeden önce kullanım durumunun hangi WhatsApp kanalına ait olduğunu anla. Yanlış seçmek numaraları banlattırır.
Bu rehberdeki skill'lerle güvenli (personal numara)
- •Gelen konuşmalar. Müşteri sana önce yazar, sen yanıtlarsın. Risk yok.
- •Transactional gönderimler. Sipariş onayları, kargo güncellemeleri, teslimat bildirimleri, ödeme makbuzları, randevu hatırlatmaları — müşterinin işinle az önce bir şey yaptığı için beklediği her mesaj.
- •Diğer araçlardaki olaylardan tetiklenen bildirimler. HubSpot deal → demo onayı, Stripe başarısız ödeme → recovery notu, Calendly rezervasyonu → toplantı öncesi hatırlatma. Müşteri upstream aracı kullandığında opt-in vermiş olur.
- •WhatsApp'ın 24 saatlik müşteri hizmetleri penceresinde yanıtlamak. Bir müşteri sana yazdığında, serbestçe yanıtlamak için 24 saatin var. Autoresponder, FAQ yöneticisi ve lead qualifier skill'leri bu pencere içinde çalışır.
Personal numaralarda GÜVENLİ DEĞİL
- •Satın aldığın veya scrap'ledığin listelere cold outreach — WhatsApp bunun için numaraları saatler içinde banlar.
- •Açıkça opt-in vermemiş müşterilere marketing broadcast'leri.
- •Promosyon kampanyaları, satış teklifleri, mevsimsel pushlar, ürün lansmanları.
- •Bir marketing blast'a benzeyen her şey. “Ne kadar throughput alabilirim” diye sorduysan, yanlış kanaldasın.
Hangi kanala ihtiyacın olduğunu nasıl anlarsın
- 1.Müşteri sana önce yazdı mı, ya da aktif bir 24 saatlik oturum içinde mi yanıtlıyorsun? → Personal numara, bu rehber.
- 2.Müşteri açıkça beklediği bir şey almak üzere mi (sipariş, randevu, ödeme, teslimat)? → Personal numara, transactional gönderim.
- 3.CRM'in veya billing aracındaki bir müşteri-opt-in olayı tarafından mı tetiklendi? → Personal numara, olay tetikli gönderim.
- 4.Broadcast, cold outreach veya promosyon kampanyası mı? → Business API, TimelinesAI dashboard'u kullan.
- 5.Emin değil misin? → Gönderme. Promosyon olarak kabul et ve Business API yoluna yönlendir.
Aşağıdaki her giden yeteneği bu testi geçtiğini varsayar. Emin değilsen, shipping yapmadan önce bu bölümü tekrar oku. TimelinesAI seni WhatsApp katmanındaki ban'dan koruyamaz — ban upstream'de, WhatsApp'ın altyapısında uygulanır, geçitte değil.
API referansı
Aşağıdaki her yeteneği inşa etmek için ihtiyaç duyacağın her endpoint. Base URL https://app.timelines.ai/integrations/api. Auth Authorization: Bearer $TIMELINES_AI_API_KEY.
Okuma
| Metot | Yol | Ne dönüyor |
|---|---|---|
| GET | /whatsapp_accounts | Bağlı WhatsApp numaralarınız, her biri JID, telefon, status ve account name ile. |
| GET | /chats | Sohbet listesi. ?phone=... ve ?label=... filtrelerini kabul eder. ?page=N ile sayfala (sayfa başına 50, sabit). Her sohbette sahibi numaranın JID'ini tutan whatsapp_account_id ve chatgpt_autoresponse_enabled var — kendi agent'ını açmadan önce Bilmen gerekenler'e bak. |
| GET | /chats/{id} | Bir sohbetin tam detayı. |
| GET | /chats/{id}/messages | Mesaj geçmişi, sayfa başına 50. ?page=N ile sayfala ve data.has_more_pages'e bak. Her mesaj from_me, sender_phone, text, timestamp, message_type, status (Sent/Delivered/Read) ve origin (Public API vs WhatsApp app) taşır. |
| GET | /chats/{id}/labels | Sohbetin etiketleri. |
| GET | /messages/{uid}/status_history | Giden bir mesajın Sent / Delivered / Read zaman çizgisi. |
| GET | /messages/{uid}/reactions | Bir mesaja verilen reaksiyonlar. {data: {users: [{name, phone, reaction, current}], reactions: {<emoji>: count}, total: N}} döner — düz array değil, obje. users kimin reaksiyon verdiğini listeler (her biri seçtiği emojiyi ve kendi workspace'ini işaret eden current boolean'ını taşır); reactions emoji'ye göre indekslenmiş bir histogramdır. Boş durum: {users: [], reactions: {}, total: 0}. |
| GET | /files | API üzerinden yüklediğiniz dosyalar. |
| GET | /webhooks | Kayıtlı webhook abonelikleriniz. |
Yazma
| Metot | Yol | Ne yapıyor |
|---|---|---|
| POST | /messages | Bir telefon numarasına gönderir. Body: {"phone":"+...","text":"..."}. {"message_uid":"..."} döner. |
| POST | /chats/{id}/messages | Var olan bir sohbete gönderir. Body: {"text":"..."}. Gönderici, sohbete sahip olan WhatsApp numarasıdır. |
| POST | /chats/{id}/notes | Bir sohbete özel not ekler. WhatsApp'a gönderilmez, sadece TimelinesAI içinde görünür. Ajan durumu ve incelemeli iş akışları için kullanılır. |
| POST | /chats/{id}/labels | Sohbete bir etiket ekler. Aşama izleme, yönlendirme, stop-reply bayrakları için. |
| PATCH | /chats/{id} | Sohbet metadata'ını günceller — sorumlu (responsible_email), okunma durumu ve chatgpt_autoresponse_enabled. Agent'ın yanıt vermeye başlamadan önce sonuncuyu kapat, yoksa TL'nin yerleşik responder'ı seninkiyle yarışır. |
| PATCH | /messages/{uid}/reactions | Bir mesaja reaksiyon emojisi koyar. Body gerçek emoji karakterini taşır, shortcode değil. |
| POST | /files_upload | Bir dosyayı multipart/form-data ile yükler (alan: file). data.uid döndürür, bunu chat/messages'a file_uid olarak geçirirsin. URL ile upload varyantı yok. |
| POST | /webhooks | Bir webhook aboneliği kaydeder. |
| PUT | /webhooks/{id} | Bir aboneliği günceller veya etkinleştirir/devre dışı bırakır. |
| DELETE | /webhooks/{id} | Bir aboneliği kaldırır. |
Bilmen gerekenler
Referansta gözden kaçırması kolay olan ve bilmediğin her biri sana bir saat mal olacak birkaç detay.
API çağrıları başarısız olduğunda
Beş hata modu sonunda skill'ine çarpacak. Her biri yanıtta farklı görünür ve her birinin farklı bir doğru yanıtı vardır.
Her status koduna göre dallanan küçük bir shell pattern'ı:
$ # Capture the HTTP status into a variable, then branch
$ http_code=$(curl -sS -o /tmp/resp.json -w "%{http_code}" \
-X POST \
-H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @/tmp/send.json \
https://app.timelines.ai/integrations/api/messages)
$ case "$http_code" in
200|201) ;; # ok, carry on
400) echo "bad body: $(cat /tmp/resp.json)"; exit 1 ;;
401) echo "token expired - rotate"; exit 2 ;;
429) sleep 30; retry_once ;; # respect Retry-After
5*) sleep $((2 ** attempt)); retry_with_backoff ;;
esacÜretimde güvenli çalıştırma
Hata olmayan, ama daha sonra kötü bir gününden seni kurtaracak iki şey daha.
Konuşma bağlamı
Webhook skill'ine tek bir mesaj verir — yeni gelen mesajı. Önceki turlar yok, sohbet geçmişi yok, akışta bir transkript yok. Durumsuz bir SSS veya mesai dışı yanıtlayıcı için bu yeterli. Müşterinin üç tur önce ne dediğini hatırlaması gereken bir konuşma ajanı içinse bağlamı skill'in kendisi çekmek zorunda. Üç desen tüm yelpazeyi kapsar — mümkün olan en ucuz döngüden gerçekten hatırlayanına kadar.
Durumsuz
Sadece son mesaja yanıt ver. Tur başına bir POST, GET yok, geçmiş yok. Bu en basit döngüdür ve hazır skill'lerdeki varsayılandır. SSS botları, mesai dışı yanıtlayıcılar ve sınıflandırma veya yönlendirme için kullan — yanıtın yalnızca mevcut mesaja bağlı olduğu her yerde. Ajan turlar arasında her şeyi unutur: müşteri daha önce söylenen bir şeye atıf yapan bir takip yazarsa anlamaz.
Tam bağlam penceresi
Her yanıttan önce son 20 mesajı çek ve modele önceki konuşma olarak ver. Tur başına bir yerine iki API çağrısı, artı her çağrıda ek prompt token'ları. Konuşma ajanları, çok turlu nitelendirme ve ajanın konunun ip ucunu hatırlaması gereken her yer için kullan. Yirmi son tur genellikle yeter — daha genişe gitmek nadiren yardımcı olur ve prompt'u pahalılaştırır; daha dara gitmek ajanın müşterinin bir dakika önce söylediğini unutmasına yol açar.
Uyumlu bağlam
Sadece gelen mesaj bir takip gibi görünüyorsa bağlamı çek. Metin üzerinde ucuz bir sezgi — zamirle başlıyor, “bu” veya “o”'ya atıf yapıyor, önceki yanıtından sonra 30 saniye içinde geliyor, tek kelimelik onaylar — geçmişi mi çekeceğine yoksa durumsuz mu yanıt vereceğine karar verir. Turların çoğu ucuz kalır; takipler bellek kazanır. Tam pencereyle başla; maliyet bütçeni bilip sezgi ayarlamak için gerçek prod trafiğin olunca uyumluya geç.
Pencereyi çekmek tek bir GET. Yanıt sohbetin düz bir mesaj dizisidir, en yenisi sonda, müşterinin gelen turları, kendi giden yanıtların ve skill'inin yazdığı her not karışık gelir:
$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
"https://app.timelines.ai/integrations/api/chats/12345678/messages?limit=20"
{"status":"ok","data":[
{"message_uid":"INBOUND-UID-PLACEHOLDER",
"text":"hi, is the blue one still available?",
"sender_phone":"+15550200",
"message_type":"text","origin":"WhatsApp",
"created_at":"2026-04-14T10:22:03Z"},
{"message_uid":"OUTBOUND-UID-PLACEHOLDER",
"text":"Yes, one left. Want me to hold it for you?",
"sender_phone":"",
"message_type":"text","origin":"Public API",
"created_at":"2026-04-14T10:22:41Z"},
{"message_uid":"INBOUND-UID-PLACEHOLDER-2",
"text":"yes please, until tomorrow",
"sender_phone":"+15550200",
"message_type":"text","origin":"WhatsApp",
"created_at":"2026-04-14T10:23:08Z"}
]}Prod'da ısıran tuzaklar
Durum kalıcılığı
OpenClaw skill'leri invocation'lar arası hafızada durum tutmaz. WhatsApp konuşmaları çok turludur. Çözüm, durumu sohbete kaydetmektir:
- •Etiketler ayrık aşamayı tutar — discovery/q1, qualified, escalate. POST /chats/{id}/labels ile ekle, GET /chats/{id}/labels ile oku.
- •Notlar yapılandırılmış veriyi tutar — team_size=8, yanıt taslakları, lead skorları. POST /chats/{id}/notes ile ekle. GET /chats/{id}/messages üzerinde iterate ederek ve message_type == "note" ile filtreleyerek oku.
Avantaj: crash güvenliği, insan ekip arkadaşlarına görünürlük, temiz handoff — bir insan akışı geri sarmak için bir etiketi temizleyebilir veya devralmak için escalate ekleyebilir. Trade-off: her durum geçişi bir HTTP çağrısıdır. Müşteri yüzüne dönük akışlar için bu sorun değil.
Doğru numaradan gönder
Workspace'ine birden fazla WhatsApp numarası bağlıysa, skill'inin amaca uygun olandan gönderdiğinden emin olması gerekir. Her sohbette sahibi olan numaranın tam JID'ini (TELEFON@s.whatsapp.net gibi) tutan whatsapp_account_id alanı vardır. POST /chats/{id}/messages yaptığında, gönderici her zaman o JID'dir — sen seçmezsin, sohbet seçer.
Pattern:
- 1Her skill'in environment'ında izin verilen gönderici JID'i hardcode et (örn. ALLOWED_SENDER_JID).
- 2Göndermeden önce GET /chats/{id} ile whatsapp_account_id'i izin verilen JID'inle karşılaştır.
- 3Eşleşmiyorlarsa, gönderimi atla — bir insana eskalasyon yap veya olayı bırak.
Tur başına iki ek HTTP çağrısı, yanlış persona'dan gönderme şansı sıfır. Tek numaralı workspace'ler için geçerli değil.
Gelen
Gelen mesajlar — ajanın neyi yönetebilir
Bir müşteri WhatsApp numarana her mesaj attığında, TimelinesAI webhook'una sohbet id'si, metin, gönderici telefonu ve eklerle bir message:received:new olayı tetikler. Skill'in olayı okur, ne yapacağına karar verir ve POST /chats/{chat_id}/messages ile yanıtlar. Aşağıdaki her şey bu döngünün bir varyasyonudur.
Her gelen mesaja otomatik yanıt ver
“Kargo, iade ve açılış saatleriyle ilgili sorulara yanıt ver. Başka her şey için bir yanıt taslakla ve sohbeti incelenmek üzere etiketle.”
“22:00 ile 08:00 arası otomatik yanıtla. Mesai içinde sadece gelen sohbetleri bana işaretle.”
“Bu toplantıdayken WhatsApp yanıtlarımı sen hallet.”
skill webhook payload'ını alır, bir yanıt oluşturur ve POST /chats/{id}/messages çağırır {"text":"..."}. Varsayılan olarak durumsuz — tur başına bir API çağrısı. Ajanın önceki mesajları hatırlaması gereken çok turlu konuşmalar için aşağıdaki Konuşma bağlamına bak.
İnsana eskalasyonlu FAQ yöneticisi
“Kargo, iade ve açılış saatleriyle ilgili sorulara yanıt ver. Başka her şey için sohbete needs-human etiketi koy ve ben kaldırana kadar yanıt verme.”
yanıtlamadan önce GET /chats/{id}/labels'ı kontrol et. Sohbette needs-human varsa gönderme yapmadan çık. Gelen metin bir FAQ konusuyla eşleşiyorsa yanıtla. Aksi halde POST /chats/{id}/labels ile eskalasyon etiketini koy ve sessizce çık. Ekibinin inbox'ı etikete göre filtreler.
Konuşmaları doğru kişiye yönlendir
“Her gelen sohbet için satış mı, destek mi yoksa faturalama mı olduğunu çıkar ve etiketle. Satış sohbetlerini alex@ours'a, faturalamayı jamie@ours'a ata.”
metinden niyeti sınıflandır, POST /chats/{id}/labels ile intent/sales gibi bir etiket koy, sonra PATCH /chats/{id} ile {"responsible_email":"..."} göndererek sohbeti TimelinesAI inbox'ında devret.
Lead'i bir soru dizisiyle nitelendir
“Facebook kampanyamızdan gelen her yeni sohbet için kullanım durumunu, ekip büyüklüğünü ve zaman çizelgesini sor. Yanıtları sohbete not olarak kaydet. Ekip 5 ya da daha büyükse qualified diye etiketle.”
etiketler şu an hangi soruda olduğunu izler (discovery/q1, q2, q3); notlar yanıtları tutar. Her tur: şu anki aşama etiketini oku, gelen metni yanıt olarak parse et, POST /chats/{id}/notes ile yaz, etiketi ilerlet, sonraki soruyu sor. Dış veritabanı yok — aşağıdaki Durum kalıcılığına bak.
$ # 1. Read current stage from the chat's labels. Labels nest under
$ # data.labels as a string array — see Response shapes in Things to know.
$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
https://app.timelines.ai/integrations/api/chats/12345678/labels
{"status":"ok","data":{"labels":["discovery/q1"]}}
$ # 2. Save the customer's answer as a structured note. Notes are stored
$ # as messages with message_type=note and aren't pushed to WhatsApp.
$ cat > /tmp/note.json <<'JSON'
{"text":"team_size=12"}
JSON
$ curl -sS -X POST \
-H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @/tmp/note.json \
https://app.timelines.ai/integrations/api/chats/12345678/notes
{"status":"ok","data":{"message_uid":"NOTE-UID-PLACEHOLDER"}}
$ # 3. Advance the stage label. POST /labels REPLACES the full set, so
$ # read existing labels, swap q1 for q2, POST the combined list. The
$ # body key is "labels" (plural, array) — not "label" singular.
$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
https://app.timelines.ai/integrations/api/chats/12345678/labels \
| jq -c '.data.labels | map(if . == "discovery/q1" then "discovery/q2" else . end) | {labels: .}' \
> /tmp/labels.json
$ cat /tmp/labels.json
{"labels":["discovery/q2"]}
$ curl -sS -X POST \
-H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @/tmp/labels.json \
https://app.timelines.ai/integrations/api/chats/12345678/labels
{"status":"ok","data":{"labels":["discovery/q2"]}}Fotoğrafları, PDF'leri ve makbuzları anla
“Bir müşteri makbuz fotoğrafı gönderdiğinde, tutarı ve satıcıyı çıkar ve not olarak ekle.”
“Biri PDF gönderirse, fatura / sözleşme / kimlik olarak sınıflandır ve sohbeti ona göre etiketle.”
webhook payload'ları bir ek URL'si içerir. Handler içinde indir (hızlı süresi dolar), OpenClaw'ın vision veya document araçlarıyla işle, çıkarılan veriyi POST /chats/{id}/notes ile geri yaz.
$ # The attachment URL in the webhook payload expires in ~15 minutes.
$ # Download it inline in the receiver, before queuing async work.
$ ATTACH_URL="https://files.timelines.ai/abc123/receipt.jpg?expires=..."
$ curl -sS "$ATTACH_URL" -o /tmp/receipt.jpg
$ # Process /tmp/receipt.jpg with OpenClaw vision tools, then write
$ # the extracted fields back as a structured note on the chat:
$ curl -sS -X POST \
-H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
-H "Content-Type: application/json" \
--data-binary '{"text":"receipt: amount=$42.50 vendor=Cafe Madrid"}' \
https://app.timelines.ai/integrations/api/chats/12345678/notesSesli notları transcribe et ve yanıtla
“Gelen sesli notları transcribe et. Metin olarak yanıtla — gerçekten sesli yanıt lazımsa, TimelinesAI dashboard'undan gönder.”
webhook ses dosyasına bir URL teslim eder. İndir, transcribe et, POST /chats/{id}/messages ile bir metin yanıtı oluştur. Sesli yanıtlar bugün TimelinesAI dashboard özelliğidir ve güncel public API referansında yer almaz — eğer skill'inin programatik olarak sesli yanıt göndermesi gerekiyorsa, legacy voice_message endpoint'inin workspace'inde hala mevcut olup olmadığını doğrulamak için TimelinesAI destek ekibiyle iletişime geç.
Müşterinin diliyle eşleş
“Müşteri İspanyolca yazıyorsa İspanyolca yanıtla. Konuşma ortasında dil değiştirirse onunla birlikte değiş.”
gelen metin üzerine tamamen OpenClaw tarafında muhakeme. TimelinesAI sadece yanıtı taşır.
Tam yanıt göndermeden mesajlara reaksiyon ver
“Her gelen mesaja 👀 ile reaksiyon ver ki müşteriler gördüğümü bilsin, sonra gerçek yanıtı sakın yettişmeden oluştururum.”
PATCH /messages/{uid}/reactions ile {"reaction":"👀"}. reaction alanı gerçek emoji karakterini taşımalı — "eyes" veya ":eyes:" gibi kısa kodlar HTTP 400 "Reaction has invalid format" ile reddedilir. Mesaj kredisi tüketmez — reaksiyonlar hafiftir.
$ # Send the raw emoji character in the body. Save to a UTF-8 file and
$ # use --data-binary so shells can't downgrade it.
$ printf '{"reaction":"👀"}' > /tmp/react.json
$ curl -sS -X PATCH \
-H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @/tmp/react.json \
https://app.timelines.ai/integrations/api/messages/INBOUND-UID-PLACEHOLDER/reactions
{"status":"ok"}Giden
Giden — ajanın başlattığı mesajlar
Ajanın başlattığı mesajlar, yanıt değil. Diğer araçlarındaki bir olay (yeni bir sipariş, başarısız bir ödeme, planlanmış bir toplantı) veya belirli bir kişi hakkında doğrudan bir insan talimatı tarafından tetiklenir.
Bu bölümdeki her yetenekten önce: müşteri ya thread'i yakın zamanda açtı (WhatsApp'ın 24 saatlik oturum penceresi içinde) ya da bu mesajı açıkça bekliyor. Hiçbiri doğru değilse, personal numaradan gönderme — o Business API bölgesidir.
İsme göre veya yeni bir alıcıya transactional mesaj gönder
“John'a faturasının hazır olduğunu söyle.”
“Tesisatçıya yeni ofis adresimizi mesaj olarak gönder ki parçaları teslim edebilsin.”
“İmzalı sözleşmeyi az önce kapora yatıran müşteriye gönder.”
var olan bir sohbet için GET /chats?name=John ile (veya CRM'inden) ara ve POST /chats/{id}/messages çağır. Yeni bir alıcı için POST /messages ile {"phone":"+...","text":"..."}. Companion repodaki whatsapp-send skill'i her iki modu da UTF-8 güvenli serializasyon ile yönetir.
$ cat > /tmp/send.json <<'JSON'
{"phone":"+15550200",
"text":"Hi - your order shipped. Tracking: ABC123."}
JSON
$ curl -sS -X POST \
-H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @/tmp/send.json \
https://app.timelines.ai/integrations/api/messages
{"status":"ok","data":{"message_uid":"OUTBOUND-UID-PLACEHOLDER"}}Aktif bir konuşmada zamanlanmış follow-up'lar
“Her pazartesi 9'da, son 24 saatte müşteri aktivitesi olan ama bizden yanıt gelmeyen to-follow-up etiketli sohbetleri kontrol et ve nazik bir hatırlatma gönder.”
bir OpenClaw cron job'u veya standing order ile zamanla (OpenClaw'ın automation dokümanlarına bak). Job GET /chats?label=to-follow-up&read=false ile kitleyi çeker, her sohbetin last_message_timestamp'ini okur ve sadece son müşteri turu 24 saatten az önceyse gönderir. Bu pencerenin dışında follow-up bir re-engagement haline gelir ve Business API bölgesine düşersün — Channel choice'a bak.
Diğer araçlarındaki olaylardan mesaj tetikle
“Bir HubSpot deal'ı 'demo scheduled' aşamasına gelirse, toplantı linkiyle birlikte WhatsApp onayı gönder.”
“Stripe başarısız ödeme bildirirse, kartı güncellemek için bir linkle nazik bir recovery mesajı gönder.”
“Bir Calendly rezervasyonu oluşturulduğunda, aynı sabah toplantı öncesi hatırlatma gönder.”
var olan aracın (HubSpot, Stripe, Calendly, Pipedrive) kendi webhook'unu Setup'ta kurduğun aynı receiver'a atar — event source veya path'e göre yönlendirilen bir handler ekle, TimelinesAI handler'ından branch al, ve POST /messages (yeni alıcı) veya POST /chats/{id}/messages (var olan bir sohbete) çağır. WhatsApp zaten sahip olduğun her iş akışı için bir teslimat kanalı olur. Gönderiler upstream araçtaki bir müşteri eyleminin arkasından geldiği için doğası gereği transactional'dır — personal number kurallarının tam içinde.
İstek üzerine dosya ve döküman gönder
“Teklif PDF'ini oluştur ve az önce fiyat soran müşteriye gönder.”
“Sözleşmeyi PDF olarak emaille ve müşterinin WhatsApp sohbetine de bırak.”
iki adım. Dosyanın baytlarını POST /files_upload ile multipart/form-data olarak yükle (URL ile upload endpoint'i yok — ajanın dosyayı önce kendi indirmeli sonra POST etmeli). Yanıt bir uid döndürür, bunu POST /chats/{id}/messages içindeki file_uid alanına geçirirsin. Müşteri dökümanı istedi — bu onun talebine yanıttır, cold outreach değil.
$ # Step 1 - upload the file bytes as multipart/form-data.
$ # The form field name is "file". The response gives you a uid.
$ curl -sS -X POST \
-H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
-F "file=@/path/to/quote.pdf" \
https://app.timelines.ai/integrations/api/files_upload
{"status":"ok","data":{
"uid":"FILE-UID-PLACEHOLDER",
"filename":"quote.pdf",
"size":128453,
"mimetype":"application/pdf",
"uploaded_by_email":"you@yourcompany.com",
"uploaded_at":"2026-04-14 10:22:03 +0000",
"temporary_download_url":"https://tl-prod-data.s3.amazonaws.com/..."
}}
$ # Step 2 - attach the uploaded file to a chat message using file_uid.
$ cat > /tmp/send.json <<'JSON'
{"text":"Quote attached for your review.",
"file_uid":"FILE-UID-PLACEHOLDER"}
JSON
$ curl -sS -X POST \
-H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @/tmp/send.json \
https://app.timelines.ai/integrations/api/chats/12345678/messages
{"status":"ok","data":{"message_uid":"OUTBOUND-UID-PLACEHOLDER"}}Bir mesajın gerçekten teslim edilip edilmediğini kontrol et
“John bu sabah gönderdiğim fatura mesajını gerçekten aldı mı?”
her gönderim bir message_uid döner. Sonra GET /messages/{uid}/status_history Sent / Delivered / Read zaman çizgisini döner. Aktif bir numarada teslimat genelde bir iki saniye içinde olur. Companion repodaki whatsapp-delivery-check skill'i bunu sarmalıyor.
$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
https://app.timelines.ai/integrations/api/messages/OUTBOUND-UID-PLACEHOLDER/status_history
{"status":"ok","data":[
{"status":"Sent", "timestamp":"2026-04-12 12:28:40 +0000"},
{"status":"Delivered","timestamp":"2026-04-12 12:28:41 +0000"}
]}CRM
CRM ve analiz
Okuma endpoint'leri ajanına analitik sorulara yanıt verecek ve doğal dilde CRM'inle durumu senkronize edecek kadar veri verir.
Yanıt süresi raporlaması
“Bu hafta WhatsApp'ta ortalama ilk yanıt süremiz neydi?”
“Ekibimde yanıtlamada en yavaş kim?”
yakın sohbetleri çek, sonra her sohbet için mesaj zaman çizgisini çek, her gelenden sonraki ilk gideni bul ve delta'ları client tarafında toplullaştır. İki endpoint, özel analitik çağrısı yok.
$ # Pull the last page of a chat's messages (50 per page, fixed).
$ # Messages nest under data.messages — not a flat .data[] array.
$ # FILTER message_type=="whatsapp" to exclude notes (see Things to know
$ # below — notes live in the same timeline with from_me=false and would
$ # otherwise corrupt the response-time calculation).
$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
"https://app.timelines.ai/integrations/api/chats/12345678/messages?page=1" \
| jq -r '.data.messages[] | select(.message_type=="whatsapp") | "\(.timestamp)\tfrom_me=\(.from_me)\t\(.text[0:40])"'
# 2026-04-12 12:28:40 +0300 from_me=false Hi - is the order shipped?
# 2026-04-12 12:30:15 +0300 from_me=true Yes - tracking ABC123, ETA 2-3
# 2026-04-12 14:02:11 +0300 from_me=false Got it, thanks!
# ...
$ # Compute first-reply latency from each consecutive (false -> true)
$ # pair, then average across all chats. Pure client-side aggregation.
$ # For full history walk ?page=2, ?page=3, ... until data.has_more_pages is false.Yanıtlanmamış mesaj tespiti
“Dün kaç mesaj aldık? Kaçı hala yanıtsız?”
“Son 24 saatte gelen mesajı olan ve yanıtsız her sohbeti göster.”
Okunmayan sohbetler için GET /chats?read=false, sonra .data.messages[] içinde message_type=="whatsapp" VE from_me==false olanları filtrele — notlar da from_me=false taşır ve yanıtsız sayısını şişirirdi. Bilmen gerekenler'e bak.
Talep üzerine konuşmaları özetle
“ACME Corp ile tüm konuşmayı özetle. Acı noktaları neler?”
“Yanıtlamadığım her sohbet hakkında bir paragraflık brief ver.”
GET /chats/{id}/messages çek, .data.messages[] içinde message_type=="whatsapp" olanları filtrele (yoksa notlar, agent'ının önceki kendi karıştırmalarını sanki müşteri sözüymüş gibi özete taşır — Bilmen gerekenler'e bak), sonra OpenClaw'a özetlettir. Uzun thread'ler için ?page=N ile sayfala.
CRM'ini WhatsApp aktivitesiyle zenginleştir
“Bu haftaki her yeni sohbet için numarayı HubSpot'ta ara. Bir contact'sa, sohbeti deal aşamasıyla etiketle. Değilse, contact'ı oluştur.”
TimelinesAI okumalarını CRM'inin API'siyle birleştir. Sonuçları sohbete POST /chats/{id}/labels ve POST /chats/{id}/notes ile geri yaz.
$ # 1. Pull recent chats with their phone numbers. /chats returns 50 per
$ # page under data.chats — paginate with ?page=N if you need more.
$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
"https://app.timelines.ai/integrations/api/chats?page=1" \
| jq -r '.data.chats[] | "\(.id)\t\(.phone)"'
# 12345678 +15550100
# 12345679 +15550200
# ...
$ # 2. For each phone, look up your CRM (HubSpot/Pipedrive/Close).
$ # Pseudo-code:
$ # contact = hubspot.search_by_phone(phone)
$ # stage = contact.deal_stage if contact else "unknown"
$ # 3. Add the deal stage label WITHOUT wiping existing labels.
$ # POST /labels is REPLACE semantics (full set), so GET current labels,
$ # append the new one, and POST the combined list.
$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
https://app.timelines.ai/integrations/api/chats/12345678/labels \
| jq -c '.data.labels + ["hubspot/qualified-lead"] | unique | {labels: .}' \
> /tmp/labels.json
$ curl -sS -X POST \
-H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @/tmp/labels.json \
https://app.timelines.ai/integrations/api/chats/12345678/labelsKonuşma içeriğinden lead'leri puanla
“inbound-lead etiketli her sohbeti uyum ve aciliyet için 1-10 arası puanla. Puanı not olarak yaz.”
GET /chats/{id}/messages üzerine LLM muhakemesi — önce message_type=="whatsapp" ile filtrele, yoksa kendi önceki lead-score notların müşteri sözü olarak tekrar okunur ve skor her pass'te kayar. Sonucu POST /chats/{id}/notes ile öngörülebilir bir prefix'le yaz (örn. "lead_score: fit=8 urgency=6") ki bir sonraki pass'in onu bulup üzerine yazabilsin.
Operasyon
Ölçekleme ve handoff
Human-in-the-loop iş akışları, multi-agent yönlendirme ve konuşma hafızası için pattern'lar. Aşağıdaki dört yeteneğin tamamı, TimelinesAI paylaşılan inbox'ından aynı sohbetler üzerinde çalışan insanlarla bir arada var olacak şekilde tasarlanmıştır.
Göndermek yerine insan incelemesi için yanıt taslakla
“Her gelen yeni mesaj için bir yanıt taslakla ve not olarak kaydet. Gönderme — ben inceler ve gönderirim.”
POST /chats/{id}/notes'a /messages yerine taslak metnini yaz. Not, ekibinin zaten kullandığı sohbet görünümünde belirir.
Ajan takılınca bir insana devret
“Konuşma 5 turdan fazla çözüme ulaşmadan giderse, ya da müşteri insan isterse, escalate diye etiketle ve ben temizleyene kadar yanıt verme.”
GET /chats/{id}/messages ile turları say, her gönderim öncesi GET /chats/{id}/labels ile stop-reply etiketlerini kontrol et. Eskalasyon tetiklenirse POST /chats/{id}/labels ile escalate koy ve çık.
$ # 1. Count outbound 'whatsapp' messages (skip notes) in this chat.
$ # Messages nest under data.messages — not a flat .data[] array.
$ TURNS=$(curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
"https://app.timelines.ai/integrations/api/chats/12345678/messages?page=1" \
| jq '[.data.messages[] | select(.from_me==true and .message_type=="whatsapp")] | length')
$ echo $TURNS
8
$ # 2. Past the threshold? Append 'escalate' to the chat's labels and stop.
$ # POST /labels REPLACES the full set, so read existing first, combine,
$ # then POST — otherwise you wipe every other label on the chat.
$ if [ "$TURNS" -ge 5 ]; then
curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
https://app.timelines.ai/integrations/api/chats/12345678/labels \
| jq -c '.data.labels + ["escalate"] | unique | {labels: .}' \
> /tmp/labels.json
curl -sS -X POST \
-H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @/tmp/labels.json \
https://app.timelines.ai/integrations/api/chats/12345678/labels
fi
$ # 3. Before every future send, GET /chats/{id}/labels and bail
$ # if .data.labels contains 'escalate' or 'needs-human'.Tek bir inbox'ta birden fazla özelleşmiş ajan çalıştır
“Satış AI fiyat sorularını yönetsin, destek AI ürün sorularını yönetsin. Niyete göre yönlendir; ikisi de emin değilse bana eskalasyon yap.”
bir OpenClaw workspace'inde iki uzman skill (biri satış, biri destek) artı üçüncü bir niyet sınıflandırma skill'i — bu üçüncü gelen her mesajda ilk çalışır, seçilen uzmanla sohbeti etiketler ve çıkar. Her uzman yanıtlamadan önce niyet etiketini okur ve diğerine işaret ediyorsa çıkar — böylece mesaj başına tam olarak bir skill ateşlenir.
Aynı müşteriyle geçmiş konuşmaları hatırla
“Geçen hafta seyahatte olduğunu söylemiştin — nasıl geçti?”
OpenClaw'ın kendi hafızası artı tüm WhatsApp geçmişi için GET /chats/{id}/messages. Geçmiş, TimelinesAI'da yaşadığı için invocation'lar arası yaşar.
Agent'ını ikinci bir WhatsApp numarasıyla uçtan uca test et
Setup'tan sonra burada listelenen capability'leri görebilirsin, ama gerçek bir müşteri agent'ına yazana kadar onu gerçekten çalışırken göremezsin. Bu, iterasyon için kötü bir loop. İşte daha iyisi: aynı TimelinesAI workspace'ine ikinci bir WhatsApp numarası bağla, onu scripted bir müşteri olarak kullan ve gerçek skill'inin konuşmayı uçtan uca yönettiğini izle. Mock yok, sahte webhook yok — gerçek agent'ın, gerçek API'ın, sadece diğer tarafta insan rolünü oynayan ikinci bir numarayla.
Pattern
TimelinesAI birden fazla WhatsApp numarasının tek bir workspace'te yaşamasına izin verir ve onların gelen webhook'larını aynı receiver'dan yönlendirir. İşin püf noktası her webhook payload'ındaki whatsapp_account_id alanı — mesajı alan numaranın JID'ini taşır. Onu oku, üzerinde switch yap ve ya persona adımına (agent müşteriye yeni cevap verdiğinde) ya da agent adımına (müşteri agent'a yeni mesaj yazdığında) yönlendir. Her iki taraf da diğer tarafın perspektifinden normal POST /messages veya POST /chats/{id}/messages ile gönderir.
Mimari
[+1 555 0100] [+1 555 0200]
persona agent under test
| |
| one TimelinesAI workspace |
| |
+-------> Public API <-------+
|
message:received:new
|
v
+--------------------------+
| Your test receiver |
| switch (accountJid) { |
| persona_jid -> next |
| agent_jid -> skill |
| } |
+--------------------------+Persona sender
Persona tarafı scripted bir müşteri: sıradaki söylenecek cümlelerin listesi. Sıradaki tur zamanı geldiğinde, alıcı olarak agent'ın telefon numarasıyla /messages'a POST et. Her iki numara tek workspace'te olduğu için TimelinesAI routing'i halleder.
// persona-sender.js — send the next scripted customer turn from the
// persona WhatsApp number to the agent-under-test number. TimelinesAI
// routes the send because both numbers share one workspace.
const PERSONA_SCRIPT = [
"hi I saw your ad",
"we want to reduce customer churn",
"we are 25 people",
"we need to roll out in Q3",
];
let idx = 0;
export async function sendNextPersonaTurn() {
if (idx >= PERSONA_SCRIPT.length) return;
const text = PERSONA_SCRIPT[idx++];
await fetch("https://app.timelines.ai/integrations/api/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.TIMELINES_AI_API_KEY}`,
"Content-Type": "application/json",
},
body: Buffer.from(JSON.stringify({
phone: process.env.AGENT_UNDER_TEST_PHONE,
text,
}), "utf-8"),
});
console.log(`[persona] sent: ${text}`);
}JID-switch'li receiver
Bir receiver, taraf başına bir branch. Setup'tan zaten bildiğin geri kalan her şey — ?secret= auth'u, from_me echo filtresi, düz-vs-iç içe payload fallback'i — burada değişmeden uygulanır.
// api/webhook.js — route events by which number received them
export default async function handler(req, res) {
if (req.query.secret !== process.env.WEBHOOK_SECRET) return res.status(404).end();
const { event_type, data } = req.body || {};
if (event_type !== "message:received:new") return res.status(200).end();
if (data?.from_me === true) return res.status(200).end();
res.status(200).json({ ok: true });
const accountJid = data.whatsapp_account_id ?? data.chat?.whatsapp_account_id;
if (accountJid === process.env.AGENT_UNDER_TEST_JID) {
// Customer just messaged the agent — run your agent-under-test skill
await handleAgentTurn(data);
} else if (accountJid === process.env.PERSONA_JID) {
// Agent just replied to the persona — advance the scripted scenario
await advancePersona(data);
}
}Bu iki snippet işin özü. Tam harness — persona script'leri, scenario loader, CLI kickoff, renkli split-pane logger, Vercel config — companion repoda examples/test-harness/ altında yaşar, kendi README'sinde deploy, env var'lar, webhook kaydı ve ilk scenarionu uçtan uca çalıştırma rehberi var.
Olduğu gibi izle
Harness otonom çalışır — her yanıtı onaylaman gerekmez. Gözetimin TimelinesAI dashboard'da olur: persona numaran ile agent numaran arasındaki sohbeti başka bir browser sekmesinde aç ve her tur canlı görünür. Agent aptalca bir şey söylerse, konuşmayı yönlendirmek için agent'ın inbox'ından manuel bir yanıt gönder. Bir run'ın ortasında davranışı değiştirmen gerekirse, sohbete label ekle veya not yaz — her ikisi de agent'ın bir sonraki turda skill'ine görünür. Kayıtlı webhook'u silerek veya Vercel fonksiyonunu kapatarak run'ı durdur; scenario anında durur.
Örnek: tam bir tur
Pratikte API'nin nasıl göründüğünü gösteren beş adımlık somut bir döngü — ajanın bir outbound gönderir, teslimatı onaylar, müşterinin yanıtını işler ve bir follow-up gönderir. Aşağıdaki telefon numaraları, sohbet ID'leri ve message UID'leri placeholder — kendininkilerle değiştir.
Bu örnek boyunca kullanılan placeholder'lar:
Your business number → +1 555 0100 (JID: 15550100@s.whatsapp.net) Your customer's number → +1 555 0200 API token → $TIMELINES_AI_API_KEY
Token'ının çalıştığını onayla ve bağlı numaralarını listele
$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
https://app.timelines.ai/integrations/api/whatsapp_accounts
{"status":"ok","data":{"whatsapp_accounts":[
{"id":"15550100@s.whatsapp.net","phone":"+15550100",
"status":"active","account_name":"Your Business"}
]}}Bağlı numaralarının bir listesini görmelisin. Status active değilse, devam etmeden önce TimelinesAI dashboard'undan düzelt.
Bir giden mesaj gönder
Payload'ı açık UTF-8 encoding ile bir dosyaya yaz, sonra curl'e ver. Bu, her giden için pattern'dir.
$ cat > /tmp/send.json <<'JSON'
{"phone":"+15550200",
"text":"Hi - your order shipped. Tracking: ABC123."}
JSON
$ curl -sS -X POST \
-H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @/tmp/send.json \
https://app.timelines.ai/integrations/api/messages
{"status":"ok","data":{"message_uid":"OUTBOUND-UID-PLACEHOLDER"}}Yanıt bir makbuzdur, teslimat onayı değildir. Bir sonraki adım için message_uid'i sakla.
Teslimat durumunu kontrol et
$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
.../messages/OUTBOUND-UID-PLACEHOLDER/status_history
{"status":"ok","data":[
{"status":"Sent", "timestamp":"2026-04-12 12:28:40 +0000"},
{"status":"Delivered","timestamp":"2026-04-12 12:28:41 +0000"}
]}Sent → Delivered aktif bir numarada genelde bir saniye içinde olur. Read durumu daha sonra, alıcı sohbeti gerçekten açtığında belirir.
Müşteri yanıtlar, webhook'un tetiklenir
Müşteri yanıtladığında, TimelinesAI kaydettiğin webhook URL'sine POST yapar:
{
"event_type": "message:received:new",
"data": {
"chat_id": 12345678,
"message_uid": "INBOUND-UID-PLACEHOLDER",
"sender_phone": "+15550200",
"sender_name": "Customer",
"text": "Thanks! When will it arrive?",
"timestamp": "2026-04-12 12:29:20 +0000"
}
}Receiver'ın hemen 200 ile ack verir, sonra payload'ı OpenClaw skill'ine devreder.
Aynı sohbetten geri yanıtla
$ cat > /tmp/reply.json <<'JSON'
{"text":"Estimated delivery is 2-3 business days. You'll get tracking updates to this chat."}
JSON
$ curl -sS -X POST \
-H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @/tmp/reply.json \
https://app.timelines.ai/integrations/api/chats/12345678/messages
{"status":"ok","data":{"message_uid":"REPLY-UID-PLACEHOLDER"}}Var olan bir sohbete /chats/{id}/messages üzerinden gönderdiğin için, gönderici otomatik olarak sohbete sahip olan WhatsApp numarası olur — sen seçmezsin, sohbet kaydı seçer. Çoklu numaralı workspace'lerde yanlışlıkla yanlış numaradan göndermemenin nedeni budur.
Sınırlar ve uyarılar
Bu rehberin kapsamadığı şeyler ve nedenleri.
İlgili rehberler
Bunu gerçek bir müşteri kanalı için inşa ediyorsan, bu üç sayfa er ya da geç dokunacağın bitişik araziyi kaplıyor.
Birden fazla WhatsApp numarası bağla →
Tek bir workspace'ten birden fazla personal numara çalıştır, paylaşılan inbox yönlendirme ve numara başına JID pinning ile.
HubSpot + WhatsApp entegrasyonu →
Konuşmaları HubSpot deal'larına senkronize et — yetenek #11 (olay tetikli gönderimler) için en yakın hazır companion.
WhatsApp için ChatGPT ajanları →
Aynı fikrin OpenClaw olmayan versiyonu — dashboard'dan yapılandırdığın yönetilen bir autoresponder, skill gerekmez.
Yapı taşları
Companion skill bundle GitHub'da →4 çalışan skill, bir Vercel webhook receiver, compliance docs ve bu rehberin tam mirror'ı. MIT lisanslı.
TimelinesAI API Docs · OpenClaw Skills Docs · v0.1.0 release
Yetenek rehberi · 2026 · Kanonik URL timelines.ai/guide/openclaw-whatsapp-skills