PushizoDocs
Kanallar

WhatsApp (Meta Cloud API): kurulum, şablonlar, oturum penceresi ve gelen kutusu

Pushizo, WhatsApp mesajlarını Meta'nın Cloud API'si üzerinden doğrudan gönderir; BSP veya ek barındırma gerekmez (ADR-0029). Kanal, plan özelliği olarak SMS gerektirir. Dış hesap olmadan geliştirme ve test için mock…

Pushizo, WhatsApp mesajlarını Meta'nın Cloud API'si üzerinden doğrudan gönderir; BSP veya ek barındırma gerekmez (docs/adr/0029-whatsapp-rcs-iki-yonlu-sms-eposta-isindirma.md). Kanal, plan özelliği olarak SMS gerektirir. Dış hesap olmadan geliştirme ve test için mock sağlayıcı aynı akışı (şablon senkronu, gelen webhook, gelen kutusu) uçtan uca çalıştırır.

1. Meta tarafında hazırlık

  1. Meta Business hesabında bir WhatsApp Business Account (WABA) ve telefon numarası oluşturun.
  2. Business Settings → System users altında kalıcı bir sistem kullanıcısı belirteci üretin (whatsapp_business_messaging ve whatsapp_business_management izinleri).
  3. Uygulamanızın App Secret değerini (App Settings → Basic; 32 karakterlik onaltılık) not edin. Gelen webhook imzası (X-Hub-Signature-256) bu anahtarla doğrulanır; gerçek kanalda zorunludur.

2. Kanalı bağlama

Dashboard'da App → Kanallar → WhatsApp kartından veya API ile:

PUT /internal/v1/apps/{appId}/channels/whatsapp
{
  "provider": "meta_whatsapp",
  "waba_id": "102030405",
  "phone_number_id": "1098765432",
  "graph_api_version": "v23.0",
  "access_token": "EAAG…",
  "app_secret": "0123456789abcdef0123456789abcdef"
}
```text

- Belirteç ve app secret zarf şifreli saklanır, yanıtta asla dönmez (`has_secret: true`). Güncellemede gizli alanları
  boş bırakırsanız mevcut değerler korunur.
- `app_secret` olmadan yeni kayıt reddedilir (400). Bu kural öncesinde app secret'sız kaydedilmiş bir kanal silinmez;
  **Doğrula** işleminde `WHATSAPP_APP_SECRET_MISSING` hatasıyla `invalid` işaretlenir ve gönderime kapanır — kanalı
  app secret ile yeniden kaydedin.
- Kayıt sonrası doğrulama telefon numarasını okur (görünen numara, doğrulanmış ad, kalite puanı).

**Webhook:** Kanal kartındaki **Webhook adresi** (`/hooks/whatsapp/meta_whatsapp/{token}`) ve **doğrulama
belirteci** değerlerini Meta App Dashboard → WhatsApp → Configuration → Webhook alanına girin ve `messages` alanına
abone olun. Meta'nın `GET` el sıkışması belirteç eşleşirse `hub.challenge` ile yanıtlanır. Adres sızarsa
**Yenile** ile yeni belirteç üretin; eski adres hemen geçersiz olur.

## 3. Açık rıza (opt-in) zorunludur
WhatsApp aboneliğine rıza kaydı olmadan hiçbir mesaj gönderilmez:

```http
POST /v1/apps/{appId}/users
{ "identity": { "external_id": "elif-1" },
  "subscriptions": [{ "channel": "whatsapp", "token": "+905321234567", "opt_in": { "source": "web_form" } }] }
```text

- `opt_in` verilmeyen yeni WhatsApp aboneliği `pending` başlar; kampanyalar ona gitmez (`no_opt_in` bastırması).
- Size yalnızca yazmış (gelen mesajla oluşan) kişi de `pending`dir. Rıza sonradan verildiğinde `subscribed` olur.
- `opt_in` eylemli anahtar sözcük ve yasal **BAŞLA/START** da rıza kaydeder (kaynak `keyword`), bkz.
  [İki yönlü SMS ve anahtar sözcükler](/docs/channels/two-way-sms).

## 4. Şablonlar ve 24 saatlik oturum penceresi
Meta kuralı: kişinin son gelen mesajından sonraki **24 saat** içinde serbest metin, medya ve etkileşimli mesaj
gönderilebilir; pencere dışında yalnızca **onaylı şablon** gider.

- **Senkron:** Şablonlar Meta'dan saatlik ve **Gelen kutusu → WhatsApp şablonları → Senkronla** ile anında okunur
  (`POST /v1/apps/{appId}/whatsapp/templates/sync`). Durumlar: `APPROVED`, `PENDING`, `REJECTED`…
- **Gönderim yolu denetimi:** Bildirim (`POST /v1/notifications`), kampanya gönderimi/zamanlaması ve gelen kutusu
  yanıtı onaylı olmayan şablonla `422 HS_CONTENT_INVALID` (`details.code = WHATSAPP_TEMPLATE_NOT_APPROVED`) döner;
  gövde değişken sayısı (`{{1}}`, `{{2}}`…) `body_params` ile eşleşmezse `WHATSAPP_TEMPLATE_PARAMS` (`expected`).
  Journey yayınında aynı sorunlar WhatsApp adımında `content_invalid` issue olarak raporlanır.
- **Kanal açıkça verilmeli:** Yalnızca WhatsApp içeriği (şablon/medya) olan bildirim ve kampanyalarda hedefte
  `channels: ["whatsapp"]` zorunludur; aksi halde 422 (push abonelerine metinsiz mesaj gitmez).

```json
{
  "app_id": "app_…",
  "whatsapp_template": { "name": "siparis_durumu", "language": "tr", "body_params": ["{{ user.tags.order }}"] },
  "target": { "included_segments": ["seg_…"], "channels": ["whatsapp"] }
}
```text

Şablon parametreleri Liquid ile kişiselleştirilir; boş parametre veya 1024 karakteri aşan değer gönderimde reddedilir.
Pencere dışındaki alıcılara giden serbest metin/medya/etkileşimli teslimatlar `whatsapp_window_closed` ile bastırılır.

## 5. Medya ve etkileşimli mesajlar (oturum içi)
- **Medya:** `whatsapp_media` (`image`, `video`, `document`, `audio`; yalnızca https); `contents` başlık olur.
- **Etkileşimli:** `whatsapp_interactive` ile yanıt düğmeleri veya liste; gövde metni `contents` alanıdır (en fazla
  1024 karakter). Meta sınırları API'de doğrulanır:

| Tür | Sınır |
|---|---|
| `button` | 1–3 düğme; düğme başlığı ≤ 20, kimliği ≤ 256 karakter; başlıklar ve kimlikler benzersiz |
| `list` | Liste düğmesi metni ≤ 20; toplam 1–10 satır; satır başlığı ≤ 24, açıklama ≤ 72, kimliği ≤ 200; birden çok bölümde bölüm başlığı zorunlu (≤ 24) |
| Ortak | `header` ve `footer` ≤ 60 karakter (Liquid desteklenir) |

```json
{
  "contents": { "tr": "Teslimat saatini seçin" },
  "whatsapp_interactive": {
    "type": "button",
    "header": "Sipariş {{ user.tags.order }}",
    "buttons": [{ "id": "sabah", "title": "Sabah" }, { "id": "aksam", "title": "Akşam" }]
  },
  "target": { "include_aliases": { "external_id": ["elif-1"] }, "channels": ["whatsapp"] }
}
```text

Kişinin seçtiği düğme/satır gelen kutusuna yanıt olarak düşer; seçilen `id` mesajın `content.payload` alanındadır.
Dashboard'da mesaj/şablon/journey editörünün **Oturum mesajı** modunda ve gelen kutusu yanıtında **Etkileşimli**
sekmesinde hazırlanır.

## 6. Gelen kutusu
- Gelen mesajlar (metin, düğme/liste yanıtı, medya, konum, tepki) **Gelen kutusu**nda kişi bazlı konuşma olarak
  görünür; başlıkta rıza durumu ve pencerenin bitiş zamanı gösterilir.
- **Gelen medya:** Görseller konuşmada önizlenir, tüm türler **indir** bağlantısıyla alınır. Dosya, kanalın
  belirteciyle Meta'dan yetkili bir proxy üzerinden akıtılır
  (`GET /internal/v1/apps/{appId}/conversations/{id}/messages/{messageId}/media`, `?download=true` ek olarak indirir).
  İndirme adresi yalnızca Meta medya alan adlarından (`lookaside.fbsbx.com`, `*.fbcdn.net`, `*.whatsapp.net`) kabul
  edilir; 16 MB üstü dosyalar `MEDIA_TOO_LARGE`, süresi dolmuş medya 404 döner.
- **Yanıt:** Pencere açıkken metin, şablon veya etkileşimli mesaj; kapalıyken yalnızca onaylı şablon (serbest metin
  `422 WHATSAPP_WINDOW_CLOSED`). Rıza kaydı olmayan kişiye yanıt `422 WHATSAPP_NO_OPT_IN`.

## 7. Durumlar ve hatalar
Meta durum bildirimleri teslimata işlenir: `delivered` → teslim edildi, `read` → açıldı, `failed` → başarısız.
Numara WhatsApp kullanmıyorsa (131026) abonelik geçersiz yapılır; belirteç/izin hataları kanalı `invalid` yapar;
hız sınırları `Retry-After` ile yeniden denenir. Ayrıntılı eşleme ADR-0029 §2.3'tedir.

## 8. Mock ile yerel geliştirme ve E2E
`provider: "mock"` (isteğe bağlı `app_secret`): şablon senkronu örnek onaylı/bekleyen/reddedilmiş şablonlar yükler,
`/hooks/whatsapp/mock/{token}` Meta biçimindeki webhook'u kabul eder (anahtar tanımlıysa imza zorunlu), gelen medya
proxy'si küçük bir örnek dosya döndürür. Mock webhook üretimde imzasız kabul edilmez.

Bu sayfada