> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flextell.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Rehber: mesaj gönderme ve dinleme

> Bir hastayla sohbet başlatıp mesaj göndermeyi ve gelen mesajları gerçek zamanlı dinlemeyi örnekle anlatır.

Bu rehber, entegrasyonunuzun bir hastaya WhatsApp gibi bir kanaldan mesaj göndermesini ve hastanın yanıtını canlı yakalamasını uçtan uca kurar. Akış üç adımdır: sohbeti bulun ya da oluşturun, mesajı gönderin, olayları dinleyip mesajı REST API'den okuyun.

## Senaryo

Randevu yazılımınız, "Ayşe Yılmaz" adlı hastaya (`+90 5xx xxx xx xx`) kliniğin WhatsApp hattından bir hatırlatma gönderecek. Hasta yanıt verdiğinde yazılımınız yanıtı hemen görecek ve sohbeti okundu yapacak.

```mermaid theme={null}
flowchart LR
  A[Sohbeti bul veya oluştur] --> B[Mesajı gönder]
  B --> C[Olayı dinle]
  C --> D[Mesajı API'den oku]
  D --> E[Sohbeti okundu yap]
```

Gerekenler:

| Gereken | Ayrıntı |
| - | - |
| Access token | `conversations:read` ve `conversations:write` scope'ları. Bkz. [Scope'lar](/api/kimlik-dogrulama/scopelar). |
| Klinik kimliği | Her isteğe `X-Tenant: <tenant_id>` başlığı olarak eklenir. Bkz. [Klinikler ve X-Tenant](/api/klinikler-ve-x-tenant). |
| İzinler | Sohbet başlatmak için `Create:Conversation`, mesaj göndermek için `Send:Message`, okumak ve dinlemek için `View:Conversation`. |
| Kanal kimliği | Mesajın çıkacağı kanalın `id` değeri. `GET /channels` ile klinikteki kanalları listeleyin. Bkz. [Kanallar](/api/kaynaklar/kanallar). |

<Info>
  `ViewAllConversations` izni olmayan bir kullanıcı yalnızca lead'i kendisine atanmış kişilerin sohbetlerini görür. Bu kısıt hem API çağrılarını hem de dinleyebileceğiniz kanalı etkiler. Ayrıntı için [İzinler ve görünürlük](/api/izinler-ve-gorunurluk) sayfasına bakın.
</Info>

## Sohbeti bulma veya oluşturma

Sohbetin `id` değerine ihtiyacınız var. En kısa yol `POST /conversations` çağrısıdır: sohbet varsa aynısını döndürür, yoksa oluşturur. Çağrı her iki durumda da `201` verir.

Kişiyi üç yoldan biriyle belirtebilirsiniz: `customer_id`, `lead_id` veya `channel_id` ile `address`. Kişi henüz kayıtlı değilse `channel_id` ve `address` kullanın.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://app.flextell.ai/api/v1/conversations \
    -H "Authorization: Bearer <access_token>" \
    -H "X-Tenant: <tenant_id>" \
    -H "Content-Type: application/json" \
    -d '{
      "channel_id": 12,
      "address": "+90 5xx xxx xx xx",
      "first_name": "Ayşe",
      "last_name": "Yılmaz"
    }'
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch('https://app.flextell.ai/api/v1/conversations', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'X-Tenant': String(tenantId),
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      channel_id: 12,
      address: '+90 5xx xxx xx xx',
      first_name: 'Ayşe',
      last_name: 'Yılmaz',
    }),
  })
  const { data } = await res.json()
  const conversationId = data.id
  ```
</CodeGroup>

Yanıttaki `data.id` sohbet kimliğidir. Bu yanıt kısa şekillidir: `tags`, `lines`, `blacklist` ve sayaçlar gelmez, `unread_count` 0'dır.

* `first_name` ve `last_name` gönderirseniz kişinin mevcut adını değiştirir. Yalnızca yeni kişi için göndermek isterseniz önce sohbeti arayın.
* Aynı kanal ve adres için tekrar çağrı aynı sohbeti ve aynı kişiyi döndürür. Numara normalize edilir; `+90 555 123 45 67` ile `905551234567` aynı adrestir.
* Adres başka bir hastanın sohbetine aitse `409 CONVERSATION_ADDRESS_TAKEN` döner.

Var olan sohbetleri aramak için `GET /conversations?search=Ayşe` kullanın. Arama yalnızca kişi adı, soyadı ve adreste yapılır; mesaj metninde aramaz. Varsayılan olarak arşivdeki sohbetler listelenmez. Filtreler için [Sohbetler](/api/kaynaklar/sohbetler) sayfasına bakın.

<Warning>
  Lead'i size atanmamış bir kişiyle sohbet başlatırsanız istek hiçbir şey yazılmadan `403 CONVERSATION_NOT_ASSIGNED` ile reddedilir. Yanıttaki `data.assigned_user`, lead'in sahibinin `id` ve `name` değerini ya da `null` verir.
</Warning>

## Mesaj gönderme

Metin mesajı için `POST /conversations/{id}/messages` kullanın. `message` zorunludur ve en çok 5000 karakterdir; kanalın kendi sınırı daha düşükse o geçerlidir (WhatsApp 65536, Telegram 4096, Instagram 1000, web sitesi için sınır yok).

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://app.flextell.ai/api/v1/conversations/123/messages \
    -H "Authorization: Bearer <access_token>" \
    -H "X-Tenant: <tenant_id>" \
    -H "Content-Type: application/json" \
    -d '{"message": "Merhaba Ayşe Hanım, yarın 14:30 randevunuzu hatırlatırız."}'
  ```

  ```javascript JavaScript theme={null}
  await fetch(`https://app.flextell.ai/api/v1/conversations/${conversationId}/messages`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'X-Tenant': String(tenantId),
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      message: 'Merhaba Ayşe Hanım, yarın 14:30 randevunuzu hatırlatırız.',
    }),
  })
  ```
</CodeGroup>

Yanıt `202 Accepted` olur. Mesaj kuyruğa alınmıştır, henüz hastaya iletilmemiş olabilir:

```json theme={null}
{
  "status": "queued",
  "message_id": "manual_...",
  "conversation_id": 123
}
```

`message_id` geçici bir kimliktir. Kanal mesajı kabul ettiğinde gerçek mesaj oluşur ve bir sonraki bölümde anlatılan olay yayınlanır.

<Note>
  Düz metin gönderimlerinde tekrar koruması yoktur. Ağ hatasında isteği hemen tekrarlamayın; önce `GET /conversations/{id}/messages` ile mesajın oluşup oluşmadığına bakın. Aynı sohbete art arda gönderilen mesajlar en az 2 saniye arayla iletilir.
</Note>

<Warning>
  Sohbetin kanalı pasifse (panelde **Aktif** kapalıysa) istek `202` yerine `422 CHANNEL_INACTIVE` ile reddedilir; mesaj kuyruğa alınmaz ve saklanmaz. Kanalın sağlayıcı bağlantısı kopuksa dönen `CHANNEL_NOT_CONNECTED` ise başka bir durumdur: `CHANNEL_INACTIVE` kanalın kapatılmış olduğunu, `CHANNEL_NOT_CONNECTED` bağlantının kopuk olduğunu gösterir.
</Warning>

Mesaj kabul edildikten (`202`) sonra hat kapatılırsa mesaj iletilmez. Bu durumda mesaj yine de saklanır, `metadata.delivery_status` değeri `failed` olur ve `ChatLogUpdated` olayı yayınlanır.

Medya, sesli not ve Hızlı Yanıt göndermek için [Mesajlar](/api/kaynaklar/mesajlar) sayfasına bakın.

<Info>
  API ile mesaj gönderdiğinizde, çalışmakta olan AI asistan bu sohbet için 30 dakika durdurulur ve sohbete bir sistem mesajı yazılır. Bkz. [AI asistan kontrolü rehberi](/api/rehberler/ai-asistan-kontrolu).
</Info>

## Gelen mesajları dinleme

Hastanın yanıtı, abone olduğunuz kanala bir olay olarak düşer. Hangi kanala abone olacağınız yetkinize bağlıdır.

| Kanal | Kim abone olabilir | Taşıdığı olay |
| - | - | - |
| `private-chat-log.{tenantId}` | `View:Conversation` iznine sahip ve tüm sohbetleri görebilen kullanıcı (`ViewAllConversations:Conversation` veya platform yöneticisi) | `ChatLogCreated` (mesajın tamamı) |
| `private-chat-log-activity.{tenantId}` | `View:Conversation` iznine sahip her klinik kullanıcısı | `ChatLogChanged` (yalnızca kimlikler) |

Tüm sohbetleri göremeyen kullanıcı `private-chat-log` için `/api/broadcasting/auth` üzerinden `403` alır. Her yetki düzeyinde çalışan yol, `private-chat-log-activity` kanalına abone olmak ve olay gelince mesajı REST API'den okumaktır. Bağlantı kurma ve yetkilendirme kodu için [Örnek istemci](/api/gercek-zamanli/ornek-istemci) sayfasına bakın.

`ChatLogChanged` olayının yükü `{conversation_id, chat_log_id}` biçimindedir. `chat_log_id` `null` olabilir. Olay, sohbete bağlı her yeni mesajda ve her mesaj güncellemesinde gelir.

```javascript theme={null}
const channel = pusher.subscribe(`private-chat-log-activity.${tenantId}`)

channel.bind('pusher:subscription_succeeded', () => {
  // İlk abonelikte ve her yeniden bağlanmada çalışır: kaçan olayları kapatın.
  loadLatestMessages(conversationId)
})

channel.bind('Modules\\Chat\\Events\\ChatLogChanged', ({ conversation_id }) => {
  if (conversation_id === conversationId) loadLatestMessages(conversation_id)
})

async function loadLatestMessages(id) {
  const res = await fetch(
    `https://app.flextell.ai/api/v1/conversations/${id}/messages?per_page=20`,
    { headers: { Authorization: `Bearer ${accessToken}`, 'X-Tenant': String(tenantId) } },
  )
  const page = await res.json()
  // page.data en yeni mesajdan başlar. Mesajları `id` ile tekilleştirin.
  // Hasta mesajları için is_customer true'dur.
}
```

Dikkat edilecekler:

* Tüm sohbetleri görebiliyorsanız `private-chat-log` kanalındaki `Modules\Chat\Events\ChatLogCreated` olayı mesajı `chatLog` alanında tam olarak taşır; ek istek gerekmez.
* Olaylar tekrar gönderilmez. Bağlantı koptuğunda kaçan mesajları her abonelikte `GET /conversations/{id}/messages` ile kapatın.
* Olayların sırası garanti değildir ve kendi işleminizin olayı size de gelir. Mesajları `id` ile tekilleştirin.
* Medya içeren mesajda `ChatLogCreated` dosya hazır olmadan gelir. Dosya hazır olunca `ChatLogUpdated` yayınlanır; `ChatLogChanged` ikisinde de gelir.
* Gönderdiğiniz mesajın teslim durumu (`delivery_status`) değiştiğinde de olay gelir. Mesaj kuyruğa alındıktan sonra hat kapatılırsa `delivery_status` `failed` olur ve `ChatLogUpdated` yayınlanır.

## Okundu işaretleme

Hasta mesajlarını okuduktan sonra sohbeti okundu yapın. Okunmamış sayısı (`unread_count`) klinik ekibi için ortaktır: bir kullanıcı okuyunca herkes için okunmuş olur.

| Çağrı | Ne yapar |
| - | - |
| `POST /conversations/{id}/read` | Sohbetteki hasta mesajlarını okundu yapar. Gövde yoktur. |
| `POST /conversations/{id}/open` | Mesajların ilk sayfasını getirir ve hasta mesajlarını okundu yapar. İki scope da (`conversations:read`, `conversations:write`) gerekir. |
| `POST /conversations/read-all` | Görebildiğiniz tüm sohbetleri okundu yapar. |

```bash theme={null}
curl -X POST https://app.flextell.ai/api/v1/conversations/123/read \
  -H "Authorization: Bearer <access_token>" \
  -H "X-Tenant: <tenant_id>"
```

Bu çağrılar hastaya "görüldü" bilgisi (mavi tik) göndermez ve giden mesajları okundu yapmaz. Her okundu işlemi bir `ConversationRead` olayı yayınlar; bu olay kuyruktan geçtiği için daha sonra gelen bir mesaj olayından sonra ulaşabilir.

## Hata durumları

| Kod | HTTP | Ne zaman |
| - | - | - |
| `CONVERSATION_NOT_ASSIGNED` | 403 | Sohbet başlatılan kişinin lead'i size atanmamış ya da mevcut sohbeti göremiyorsunuz. |
| `LEAD_NOT_ASSIGNED` | 403 | Başka bir temsilcinin lead'ine ait sohbete yazmaya çalışıyorsunuz. |
| `FORBIDDEN` | 403 | İzin veya scope eksik. |
| `RESOURCE_NOT_FOUND` | 404 | Sohbet yok, başka kliniğe ait ya da size görünmüyor. |
| `CONVERSATION_ADDRESS_TAKEN` | 409 | Adres başka bir hastanın sohbetine ait. |
| `CUSTOMER_ERASED` | 409 | Hastanın verileri imha edilmiş; sohbet salt okunurdur. |
| `CONVERSATION_CUSTOMER_UNREACHABLE` | 422 | Hastaya ulaşılabilecek bir kanal ya da adres bulunamadı. |
| `LEAD_NO_ACTIVE_PIPELINE` | 422 | `contact_type=lead` gönderdiniz ama etkin pipeline yok. |
| `CONVERSATION_CHANNEL_MISSING` | 422 | Sohbetin kanalı artık yok. |
| `CHANNEL_INACTIVE` | 422 | Sohbetin kanalı pasif (panelde **Aktif** kapalı). Metin, medya, sesli mesaj, Hızlı Yanıt, tepki ve AI önerisi gönderimi bu kodla reddedilir; mesaj kuyruğa alınmaz ve saklanmaz. Kanalı yeniden etkinleştirip tekrar deneyin. |
| `MESSAGE_TOO_LONG` | 422 | Mesaj kanalın karakter sınırını aşıyor. |
| `QUOTA_EXHAUSTED` | 422 | WhatsApp mesaj hakkı dolmuş. Yanıtta `data.billing_type` `whatsapp` olur. |
| `SUBSCRIPTION_REQUIRED` | 402 | Abonelik süresi dolmuş. |
| `VALIDATION_FAILED` | 422 | İstek gövdesi geçersiz (ör. `message` boş). |

Hata yanıtının biçimi için [Hatalar](/api/istekler/hatalar) ve [Hata kodları](/api/istekler/hata-kodlari) sayfalarına bakın.

<CardGroup cols={2}>
  <Card title="Sohbetler" icon="messages-square" href="/api/kaynaklar/sohbetler">
    Sohbet listeleme, filtreler, arşiv ve etiketler.
  </Card>

  <Card title="Mesajlar" icon="message-square" href="/api/kaynaklar/mesajlar">
    Medya, sesli not, düzenleme, silme ve tepki.
  </Card>

  <Card title="Kanallar ve olaylar" icon="radio" href="/api/gercek-zamanli/kanallar-ve-olaylar">
    Tüm kanalların ve olayların listesi.
  </Card>

  <Card title="Örnek istemci" icon="code" href="/api/gercek-zamanli/ornek-istemci">
    Bağlanma, yetkilendirme ve yeniden bağlanma.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.