API ile tek mesaj gönderme: WhatsApp, Telegram ve Telegram botları

API ile mesaj gönderme: WhatsApp ve Telegram'da tek müşteriye tek istekle, kendi metniniz ve ekinizle, toplu gönderim olmadan. Kodlar ve hatırlatmalar için.

WSENDER ile WhatsApp ve Telegram'a API üzerinden tek mesaj gönderme
Sisteminizde bir olay oluyor, birkaç saniye sonra mesaj müşteride

Bazı mesajlar akşamki toplu gönderimi bekleyemez. Müşteri siparişinin ödemesini yaptı. Randevu aldı. Telefonda, operatör hattı tutarken bir doğrulama kodu istedi.

Böyle bir mesaj tek bir kişiye gider, birkaç dakika işe yarar ve bir daha tekrarlanmaz. Bunun için toplu gönderim kurmak tuhaf olur: tek numaralık bir kitle, tek bir duruma göre yazılmış metin, hepsi de tek mesaj için.

Artık bunun için ayrı bir metot var. Tek bir HTTP isteği, belirli bir kişiye kendi metni ve ekiyle tek bir mesaj gönderiyor. Burada yalnızca tekil gönderimden söz edeceğiz; toplu gönderim, toplu gönderim API'si yazısında anlatılıyor.

API ile mesaj gönderme nasıl işler

Akış basit: sisteminizde bir olay → API'ye istek → mesaj müşteride.

Metodun tamamı tek bir POST /v1/message isteği ve içinde üç bilgi var: hangi hesaptan, kime ve ne gönderileceği. Anahtar, gönderimlerde kullandığınızla aynı ve paneldeki "API" bölümünden alınıyor:

bash
curl -X POST https://api.wsender.io/v1/message \
  -H "Authorization: key TOKENINIZ" \
  -H "Content-Type: application/json" \
  -d '{
    "channelId": "HESAP_ID",
    "phone": "+90 532 123 45 67",
    "message": { "kind": "text", "text": "84421 numaralı siparişinizin ödemesi alındı, yarın saat 12.00 itibarıyla teslim alabilirsiniz." },
    "idempotencyKey": "order-84421-paid"
  }'

Yanıt olarak kendi kimliği ve durumuyla birlikte mesaj gelir:

json
{
  "id": "0f3c9a6e-4b12-4d8a-9f31-6c5a71e08d42",
  "mid": null,
  "status": "Queued",
  "channelId": "HESAP_ID",
  "target": "Phone",
  "phone": "905321234567",
  "dialogId": null,
  "errorReason": null,
  "sendDate": null,
  "failDate": null,
  "wait": null,
  "expiresAt": "2026-09-07T21:15:04Z",
  "idempotencyKey": "order-84421-paid"
}

Hesap kimliği, bağlı hesaplar listesinden GET /v1/channel ile alınıyor. idempotencyKey ise sizin belirlediğiniz talep anahtarı, ona aşağıda değineceğiz. dialogId alanı, servis bu kişiyle olan yazışmayı bulduğunda dolar ve sonra bu kimlikle aynı sohbete yanıt verebilirsiniz. Kişiye daha önce yazdıysanız alan hemen, bu yanıtın içinde dolu gelir. mid gönderimden sonra görünür: mesajın yazışmadaki kimliğidir. wait mesajı kuyrukta neyin tuttuğunu açıklar, expiresAt ise gönderilmemiş mesajın ne zamana kadar bekleyeceğini gösterir. Bu süre dolunca mesaj Expired nedeniyle sonlanır.

İsteğe yanıt hemen döner ve yalnızca "kuyruğa alındı" anlamına gelir. Mesajı, bağladığınız gerçek hesap mesajlaşma uygulamasının içinden gönderir. Bu birkaç saniye sürer, tanınmayan bir numarada ise birkaç dakikayı bulabilir: mesajlaşma uygulamaları numaraların art arda hızla kontrol edilmesinden hoşlanmaz. Botla daha hızlı olur, çünkü bot canlı bir uygulama olmadan doğrudan Bot API üzerinden gönderir.

Bu fark, hattaki operatör için önemli. Telefonda söylenecek doğru cümle "size şimdi mesaj gönderiyorum" olur. "Mesaj gitti" demek için henüz erken.

WhatsApp, Telegram ve botlarda alıcı nasıl belirtilir

Alıcı şu yollardan biriyle belirtilir ve numaraya gerek olup olmadığını da bu seçim belirler:

  • Telefon numarası, alışık olduğunuz herhangi bir biçimde. En sık durum bu: numara zaten müşteri kartınızda duruyor.
  • Kullanıcı adı, Telegram'da, hesabınız bu kişiyi önceki yazışmalardan tanıyorsa.
  • Sohbet, API'den gelen kimlikle ya da sohbetin mesajlaşma uygulamasındaki adresiyle. Sohbetin hesabınız tarafından zaten biliniyor olması gerekir: tanınmayan bir adresle gelen istek reddedilir, servis "şansını denemez".

İstekte bunlar dört ayrı alan ve yalnızca biri doldurulur. Servis kime yazmak istediğinizi tahmin etmeye çalışmaz:

text
"phone": "+90 532 123 45 67"
"username": "@mehmetkaya"
"dialogId": "API_SOHBET_ID"
"dialogMid": "123456789"

Sonuncusu, sohbetin mesajlaşma uygulamasındaki adresi. Telegram botlarında bu chat_id oluyor. Sisteminiz mesajlaşma adreslerini zaten saklıyorsa ve bizim kimliklerimizi ayrıca tutmak istemiyorsa işinize yarar.

Botlarda yalnızca sohbete göre adresleme çalışır. Sebep serviste değil: botun abonesinin numarası da kullanıcı adı da olmayabilir, ama sohbeti her zaman vardır. Bot zaten ilk mesajı kendisi atamaz, bu da platformun koyduğu bir kısıt.

WhatsApp ve Telegram'a API ile neler gönderilebilir

Metin, görsel, video, ses, belge ya da kişi kartı. Metin, köşeli parantez içinde ifade varyantlarını destekliyor: [Merhaba|İyi günler] ikisinden birine dönüşür, böylece mesajlar kalıp gibi görünmez.

Burada ilk istekten önce bilmeniz gereken bir tuzak var: köşeli parantezler içinde varyant olmasa bile spintax sayılır. [Önemli] kodunuz 1234 metni müşteriye parantezsiz, Önemli kodunuz 1234 olarak gider. Şablonlarınızda köşeli parantezi süs olarak kullanıyorsanız başka bir işaretle değiştirin.

Dosya ayrı bir istekle bir kez yüklenir, sonra kimliğiyle istediğiniz sayıda mesajda kullanılır. Yüz müşteriye giden tek bir fiyat listesi, tek bir dosyalık yer kaplar. Sınırlar basit: dosya başına 10 MB'a kadar, kullanılmayan dosya bir gün saklanır ve kendiliğinden silinir.

Yükleme normal bir dosya formuyla yapılır, yanıttaki asıl alan id:

bash
curl -X POST https://api.wsender.io/v1/file \
  -H "Authorization: key TOKENINIZ" \
  -F "file=@fiyat-listesi-2026.pdf"

Dosya zaten herkese açık bir bağlantıda duruyorsa form yerine url alanıyla POST /v1/file/url kullanılabilir, sunucu dosyayı kendisi indirir. Ardından kimlik mesaja yazılır, tür ise kind alanıyla belirlenir: text, image, video, audio, file veya contact. Görsel, video ve belgenin caption açıklaması olur, her ekin ise alıcının göreceği bir fileName adı olur:

json
{
  "channelId": "HESAP_ID",
  "phone": "+90 532 123 45 67",
  "message": {
    "kind": "file",
    "fileId": "YUKLENEN_DOSYA_ID",
    "fileName": "Sonbahar fiyat listesi",
    "caption": "Merhaba! Konuştuğumuz fiyat listesi ekte. Fiyatlar ay sonuna kadar geçerli."
  },
  "idempotencyKey": "price-request-1187"
}

Ad zorunlu değil: verilmezse yüklenen dosyanın adı kullanılır. Uzantı her durumda otomatik eklenir, onu siz göndermezsiniz. Görsel de aynı şekilde, "kind": "image" ile gönderilir. Kişi kartı ise dosya gerektirmez: { "kind": "contact", "phone": "+90 532 000 00 00" }.

Tek istisna: kişi kartı bottan gönderilemiyor, servis onu yalnızca mesajlaşma hesabından gönderiyor. Böyle bir istek, gönderim sırasında sessizce kaybolmak yerine hemen reddediliyor.

Mesajın ulaştığını nasıl anlarsınız

İsteğin yanıtı "kabul edildi" dedi. Sonucu servis size kendisi, üç webhook olayıyla bildirir:

  • message.sent. Mesaj mesajlaşma uygulamasına gitti.
  • message.failed. Mesaj gönderilemedi ve olayda nedeni yazıyor: alıcı hesabı engellemiş, ona ilk mesajı atmak mümkün değil ya da gönderen hesabın bağlantısı kesilmiş. Bir ihtimal daha var: gönderim birkaç dakika boyunca mesajlaşma uygulamasındaki geçici bir arızaya takılmış ve denemeler tükenmiş. Böyle bir mesajı yeniden kuyruğa koymanız yeterli.
  • message.notfound. Alıcı mesajlaşma uygulamasında yok: ne böyle bir numara var ne de böyle bir hesap. Bunun için ayrı bir olay var, çünkü düzeltilecek bir şey yok. Kişiyi kendi veritabanınızda işaretlemeniz yeterli.

Bu olaylara abonelik açıkça yapılır. Webhook adresini girin ve bu olayları filtresinde listeleyin. Bunu yapmazsanız olaylar gelmez, webhook gönderim olaylarına zaten ayarlı olsa bile. Bu bilinçli bir tercih. Günde yüz mesaj gönderen biri için her mesajın olay akışı faydalı, on binlerce alıcıya gönderim yapan biri ise bu akışı yanlışlıkla almamalı.

Olayda şunlar bulunur: mesaj kimliği, gönderen hesap, sizin belirttiğiniz biçimde alıcı, durum, başarısızlık nedeni ve tarihler. En önemlisi de kendi anahtarınız, yani istekle birlikte gönderdiğiniz anahtarın ta kendisi. Onunla talebi kendi sisteminizde bulursunuz ve bizim kimliklerimizi hiç saklamanız gerekmez.

Alıcının hesabınızı engellediği bir başarısızlık olayı şöyle görünür:

json
{
  "id": "7d1e0b58-3a44-4f2c-9c81-15b0f7a9d6e3",
  "type": "message.failed",
  "date": "2026-09-07T09:15:42Z",
  "payload": {
    "message": {
      "id": "0f3c9a6e-4b12-4d8a-9f31-6c5a71e08d42",
      "channelId": "HESAP_ID",
      "phone": "905321234567",
      "status": "Failed",
      "errorReason": "Blocked",
      "failDate": "2026-09-07T09:15:41Z",
      "idempotencyKey": "order-84421-paid"
    }
  }
}

Başarısızlık nedeni, ne yapmanız gerektiğini de söyler. Blocked ve RestrictedSend: bu kişiye bu hesaptan yazılamaz. ChannelDisconnected: hesabın bağlantısı kesilmiş, yeniden bağlayıp mesajı tekrar gönderin. Expired ve Unavailable: mesaj ayrılan süre içinde gönderilemedi, yeni bir anahtarla yeniden kuyruğa koyabilirsiniz.

Olay işleyicisini kurarken üç şeyi hesaba katın:

  1. Aynı olay tekrar gelebilir. Örneğin sunucunuz olayı almış, ama yanıtı yolda kaybolmuş olabilir. Olayın id değerini kontrol edin ve sisteminizdeki işlemi ikinci kez yapmayın.
  2. Kendi yaptığınız iptal olay üretmez. Mesajı siz iptal ettiniz ve bu isteğin yanıtını aldınız. Kendi işleminizi size ikinci kez bildirmeye gerek yok.
  3. Sonucu API üzerinden kontrol edebilirsiniz. Gönderimdeki geçici arızaları servis kendisi ele alır. Mesaj ayrılan süre içinde gönderilemezse Failed durumunu alır ve ayarlı, etkin bir webhook için nedeniyle birlikte message.failed olayı oluşturulur. Olayları alan sunucunuza ulaşılamıyorsa olay size gelmeyebilir, ama güncel sonuç GET /v1/message/{id} ile alınabilir.

Webhook teslimatı giderek uzayan aralıklarla yeniden denenir, ama her olay en fazla bir gün saklanır. Bir gün boyunca hiç başarılı teslimat olmazsa webhook askıya alınır ve yeni olaylar birikmez.

GET /v1/webhook yanıtındaki health alanına bakın, olayları alan sunucuyu düzeltin ve POST /v1/webhook/resume çağırın. Kontrol başarılı olursa hâlâ saklanan olayların teslimatı yeniden başlar. Askıdayken kaçırılan ve süresi dolan olaylar geri gelmez.

Webhook kurmak istemiyorsanız mesajın durumunu kimliğiyle sorgulayabilirsiniz. GET /v1/message/{id}, gönderim yanıtıyla aynı yapıyı güncel status değeriyle döndürür: Queued, Sending, Sent, Failed ya da NotFound. Ama açık konuşalım: istekten hemen sonra durum "kuyrukta" olur ve sonucu tek sorguyla öğrenemezsiniz, tekrar tekrar sormanız gerekir. Metotta "gönderilene kadar bekle" modu bilerek yok, çünkü HTTP isteğinizi bir dakika açık tutmak, size olay göndermekten daha kötü. Bu yüzden burada önerdiğimiz yol webhook'lar.

Gelen mesaja aynı metotla yanıt vermek

Metot yalnızca giden mesajlar için değil. Müşteriden yeni bir mesaj geldiğinde servis bunu message.received olayıyla bildirir: içinde metin, gönderen ve sohbet var. Ardından aynı gönderim isteğiyle, alıcı olarak olaydaki sohbeti belirterek yanıt verirsiniz. Böylece tek bir metotla iki yönlü bir iletişim kurulur: kendi gelen kutunuz, bir sohbet botu ya da CRM entegrasyonu. Kendi gelen kutunuzu geliştirmeye gerek yoksa operatörlere panelden açılan Dialogs yeter. Aynı yazışma orada görünür, API üzerinden gönderilen yanıtların yanında da "API" etiketi olur.

Yanıtı belirli bir mesaja alıntı olarak verebilirsiniz, tıpkı mesajlaşma uygulamasında konuşmanın ortasındaki bir cümleye yanıt verirken olduğu gibi. Bunun için istekte alıntılanan mesajın kimliği gönderilir. Bu kimlik hem gelen mesaj olayında hem de API'nin döndürdüğü sohbet geçmişinde bulunur.

Yine aynı POST /v1/message, yalnız alıcı olarak sohbet veriliyor ve replyToMid alanı ekleniyor:

json
{
  "channelId": "HESAP_ID",
  "dialogId": "OLAYDAKI_SOHBET_ID",
  "replyToMid": "MUSTERI_MESAJ_MID",
  "message": { "kind": "text", "text": "Evet, stokta var. Akşama kadar sizin için ayırayım mı?" },
  "idempotencyKey": "reply-5c2e-1"
}

İki kimlik de gelen mesaj olayında bulunur: sohbet payload.dialog.id alanında, mesajın kendisi payload.message.mid alanında. Yalnızca aynı sohbetteki bir mesaj alıntılanabilir. Başka bir sohbetten mesajı servis kabul etmez: kontrol istek anında yapılır, hata gönderim sırasında sessizce kaybolmaz. WhatsApp'ta, Telegram'da ve Telegram botlarında her türden mesaj alıntılanabilir.

Alıntılanan mesaj, isteğinizden sonra silinebilir. Bu durumda mesajlaşma hesabı yanıtı alıntısız, normal bir mesaj olarak gönderir: biçim yüzünden metni kaybetmenin anlamı yok. Botlar bunu yapamaz. Mesaj hatayla sonlanır ve nedeni ne yapılacağını açıkça gösterir: mesajı alıntısız yeniden göndermek.

Tekrarlanan istek ikinci bir mesaj oluşturmaz

İşlemsel mesajların ayrı bir derdi var: yanıt anında bağlantı koptu, kodunuz isteği tekrarladı ve müşteri aynı mesajı iki kez aldı.

İstekle birlikte kendi benzersiz anahtarınızı gönderin. Aynı anahtarla tekrarlanan istek ikinci bir mesaj göndermez, aynı mesajı güncel durumuyla döndürür. Bu standart idempotency mekanizması ve burada doğrudan mesaj düzeyinde çalışıyor.

Mesaj kuyrukta beklerken ayrı bir istekle iptal edilebilir: DELETE /v1/message/{id}. Gönderen hesap mesajı işleme aldıysa iptal için geç kalmışsınız demektir. 409 kodu ve gövdede güncel durum gelir, sonucu gönderimin kendisi belirler. İptal edilen mesaj bundan sonra Canceled nedeniyle Failed olarak okunur, ayrı bir "iptal edildi" durumu yok.

Servisin izin vermedikleri

Gereksiz bir şey tasarlamamanız için sınırları baştan söyleyelim.

  • Yasaklı konu denetimi burada da çalışır. Mesaj istek anında kontrol edilir, reddedilen mesaj kuyruğa girmez ve yanıtta nedeni yazar. Doğrudan gönderimle moderasyonun etrafından dolaşamazsınız.
  • Tarifenin günlük limiti toplu gönderimlerle ortaktır. Doğrudan mesajlar bu limitten aynı şekilde düşer: gönderilen bir mesaj, tarifeden bir mesaj demek. Metodun ayrı bir limiti yok. Limit aşımı için ek ücret de öngörülmemiş: limit dolduysa istek reddedilir, ücret karşılığında gönderim yapılmaz. Kalan limitten, projenin kuyruğunda bekleyen mesajlar da düşülür.
  • Aynı hesaptan yapılan gönderimler arasında birkaç saniyelik ara bırakılır. Bu, hesabı bir döngüden art arda gelen mesajlara karşı koruyan bir sigorta. Müşteriyle canlı bir yazışma buna takılmaz, ama bu metotla bin mesaj göndermeye kalkarsanız takılırsınız.
  • Servis kendi numarasını vermez. Mesaj sizin bağladığınız hesaptan ya da bottan gider. Numaranın itibarı sizde kalır, müşterilerin yanıtları da size, her zamanki mesajlaşma uygulamanıza gelir.

Sırada ne var

Metot normal tarifelerde çalışıyor ve şu anda test aşamasında. Henüz ayrı bir fiyatı yok, mesajlar tarifenin sıradan mesajları gibi sayılıyor. Ücretsiz tarifede günde 15 mesaj gönderebilirsiniz, entegrasyonu kurmak için bu yeterli.

Kendi numaranıza tek bir istekle başlayın: bir hesap bağlayın, paneldeki "API" bölümünden anahtar alın ve kendinize bir deneme mesajı gönderin. Ardından olaylara abone olun ve çağrıyı sisteminizde olayın ortaya çıktığı yere ekleyin.

Metotların tam açıklamasını ve isteği doğrudan tarayıcıdan çalıştırma imkânını API belgelerinde, istemci üretmek için spesifikasyonu ise OpenAPI dosyasında bulabilirsiniz.