# Hızlı başlangıç Veri Merkezi WhatsApp Business API ile 5 dakikada API anahtarı alın ve ilk mesajınızı gönderin. Bu rehberde bir API anahtarı oluşturacak, hesabınızı doğrulayacak ve ilk WhatsApp mesajınızı göndereceksiniz. Toplam süre yaklaşık 5 dakikadır. ## Başlamadan önce Şunlara ihtiyacınız var: - Bir Veri Merkezi hesabı ([verimerkezi.app](https://verimerkezi.app) üzerinden ücretsiz kayıt). - Panelde Meta Embedded Signup ile bağlanmış bir WhatsApp Business numarası. - Meta tarafından onaylanmış en az bir mesaj şablonu (24 saat penceresi dışında mesaj göndermek için). > **Not:** Tüm istekler `https://api.verimerkezi.app/wa` taban adresine yapılır. API yalnız HTTPS ile çalışır. ## 1. API anahtarı oluşturun 1. Panelde **API → Anahtarlar** sayfasını açın. 2. **Yeni Anahtar Oluştur**'a tıklayın, anahtara bir ad verin. 3. Mod olarak önce **test** seçin. Test anahtarı `vmk_test_` ile başlar; mesajları Meta'ya göndermez ve kredi düşmez. 4. Anahtarı kopyalayın. Anahtar **yalnız bir kez** gösterilir. Anahtarı kodunuza yazmayın; ortam değişkeninde saklayın: ```bash export VM_API_KEY="vmk_test_..." ``` ## 2. Bağlantıyı doğrulayın `GET /me` çağrısı anahtarın kime ait olduğunu ve kredi bakiyenizi döndürür. ```bash cURL curl https://api.verimerkezi.app/wa/me \ -H "Authorization: Bearer $VM_API_KEY" ``` ```javascript Node.js const res = await fetch('https://api.verimerkezi.app/wa/me', { headers: { Authorization: `Bearer ${process.env.VM_API_KEY}` }, }); console.log(await res.json()); ``` ```python Python import os, requests res = requests.get( "https://api.verimerkezi.app/wa/me", headers={"Authorization": f"Bearer {os.environ['VM_API_KEY']}"}, ) print(res.json()) ``` ```php PHP true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('VM_API_KEY')], ]); print_r(json_decode(curl_exec($ch), true)); ``` Yanıtta `api_key.mode` değeri `test` olmalıdır: ```json { "ok": true, "user": { "id": 35, "email": "musteri@example.com", "name": "Ahmet Yılmaz", "role": "customer" }, "api_key": { "id": 12, "name": "Deneme", "key_prefix": "vmk_test_k3m9q2", "mode": "test", "scopes": ["*"], "expires_at": null }, "account_settings": { "revoke_edit_clean": false, "default_automation_enabled": null, "default_opt_out_autoreply_enabled": null }, "credits": { "balance": 9876, "packages": [] } } ``` ## 3. Numaranızın kimliğini alın Mesajlar bir numaradan gönderilir. Numaranızın Meta kimliğini (`phone_number_id`) öğrenin: ```bash curl https://api.verimerkezi.app/wa/numbers \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıttaki `numbers[0].phone_number_id` değerini not edin. Bu rehberde `1234567890` kullanılmıştır. ## 4. İlk mesajı gönderin Onaylı bir şablonla mesaj gönderin. `Idempotency-Key` başlığı, ağ hatasında isteği güvenle tekrarlamanızı sağlar. ```bash cURL curl -X POST https://api.verimerkezi.app/wa/messages \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "type": "template", "template": { "name": "siparis_onayi", "language": { "code": "tr" }, "components": [ { "type": "body", "parameters": [{ "type": "text", "text": "Ahmet" }] } ] } }' ``` ```javascript Node.js import { randomUUID } from 'node:crypto'; const res = await fetch('https://api.verimerkezi.app/wa/messages', { method: 'POST', headers: { Authorization: `Bearer ${process.env.VM_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': randomUUID(), }, body: JSON.stringify({ phone_number_id: '1234567890', to: '905550000000', type: 'template', template: { name: 'siparis_onayi', language: { code: 'tr' }, components: [{ type: 'body', parameters: [{ type: 'text', text: 'Ahmet' }] }], }, }), }); console.log(res.status, await res.json()); ``` ```python Python import os, uuid, requests res = requests.post( "https://api.verimerkezi.app/wa/messages", headers={ "Authorization": f"Bearer {os.environ['VM_API_KEY']}", "Idempotency-Key": str(uuid.uuid4()), }, json={ "phone_number_id": "1234567890", "to": "905550000000", "type": "template", "template": { "name": "siparis_onayi", "language": {"code": "tr"}, "components": [{"type": "body", "parameters": [{"type": "text", "text": "Ahmet"}]}], }, }, ) print(res.status_code, res.json()) ``` ```php PHP '1234567890', 'to' => '905550000000', 'type' => 'template', 'template' => [ 'name' => 'siparis_onayi', 'language' => ['code' => 'tr'], 'components' => [['type' => 'body', 'parameters' => [['type' => 'text', 'text' => 'Ahmet']]]], ], ]; $ch = curl_init('https://api.verimerkezi.app/wa/messages'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('VM_API_KEY'), 'Content-Type: application/json', 'Idempotency-Key: ' . bin2hex(random_bytes(16)), ], CURLOPT_POSTFIELDS => json_encode($govde), ]); print_r(json_decode(curl_exec($ch), true)); ``` Başarılı istek `201 Created` döner: ```json { "ok": true, "id": 48213, "wamid": "test.4f9a1c2b7e3d5a6081c2d3e4", "to": "905550000000", "type": "template", "status": "sent", "mode": "test", "simulated": true, "credits_used": 0, "balance": 9876 } ``` Test anahtarında `simulated: true` döner ve mesaj gerçek alıcıya gitmez. Gerçek gönderim için aynı isteği `vmk_live_` anahtarıyla yapın; her başarılı gönderim 1 kredi düşer. ## Sonraki adımlar - [Mesaj gönderme](mesaj-gonderme.html): metin, medya, tepki ve form mesajları. - [Webhooklar](webhooklar.html): gelen mesajları ve teslim durumlarını sunucunuzda alın. - [Kimlik doğrulama](kimlik-dogrulama.html): kapsamlar, IP kısıtı ve anahtar güvenliği. - [API referansı](referans/messages.html#sablon-mesaji-gonder): tüm alanlar ve hata kodları. --- # Test modu vmk_test_ anahtarıyla Meta'ya mesaj göndermeden, kredi harcamadan entegrasyonunuzu ve webhook alıcınızı deneyin; hangi uçların simüle edildiğini öğrenin. Test anahtarı, entegrasyonunuzu gerçek müşterilere dokunmadan geliştirmenizi sağlar. Bu sayfa test modunun neyi simüle ettiğini ve neyi etkilemediğini anlatır. ## Test anahtarı nedir? `vmk_test_` ile başlayan anahtarlar test modundadır. Panelde **API → Anahtarlar** sayfasından anahtar oluştururken modu **test** seçin. Test modunda: - `POST /messages` Meta'ya istek göndermez. Mesaj gerçek alıcıya gitmez. - Yanıtta `mode: "test"`, `simulated: true` ve `credits_used: 0` döner. - `wamid` sahtedir ve `test.` ile başlar. - `POST /media` Meta'ya dosya yüklemez; `test.media.` önekli bir kimlik döner. - Arama eylemleri Meta'ya gitmeden simüle edilir; arama kimliği `test.wacid.` ile başlar. - Okuma uçları (`GET`) canlı anahtarla aynı veriyi döner. - Kapsam, hız sınırı ve idempotency kuralları canlı anahtardaki gibi çalışır. - Test mesajları raporlara (`GET /reports/summary`) dahil edilmez. > **Uyarı:** Test anahtarıyla gönderilen sahte `wamid` için Meta durum olayı (`message.status.*`) gelmez. Teslim akışını uçtan uca görmek için canlı anahtar ve kendi numaranızla deneyin. ## Uç bazında davranış Hiçbir yazma ucu test anahtarıyla gerçek veriye ya da Meta'ya dokunmaz. İstek normal doğrulanır (aynı `4xx` hataları döner), sonra ya gerçekçi bir başarı yanıtı simüle edilir (`simulated: true`, `mode: "test"`) ya da `403 test_mode_unsupported` döner. İki durumda da yanıtta `X-Test-Mode: 1` başlığı bulunur. | Uç | Test anahtarıyla | |---|---| | `POST /messages`, `POST /media`, `POST /messages/read`, arama eylemleri, `POST /calls/permission-request`, `PATCH /numbers/{phone_number_id}/calling` | Simüle edilir. | | `POST /templates` (`202`, `PENDING`, `id: 0`), `PATCH /templates/{id}`, `DELETE /templates/{id}` | Simüle edilir; kayıt ve Meta'ya gönderim yok. | | `PATCH /profile/{phone_number_id}`, `PATCH /numbers/{phone_number_id}/settings`, `PATCH /account/settings`, `PATCH /account/retention` | Simüle edilir; istenen değerler mevcutlarla birleştirilip döner, kaydedilmez. | | `POST /contacts` (`id: 0`), `POST /contacts/bulk` | Simüle edilir; mükerrer kontrolü gerçek rehbere göre yapılır. | | `POST /webhooks/redeliver` | Simüle edilir; teslimatlar sayılır, kuyruğa alınmaz. | | `POST`, `PATCH`, `DELETE /webhooks`, `POST /webhooks/{id}/rotate-secret` | `403 test_mode_unsupported` — abonelikleri canlı anahtarla yönetin. `POST /webhooks/{id}/test` çalışır. | | `POST /privacy/erasure` | `403 test_mode_unsupported` (doğrulama hataları yine döner). | | `POST /numbers/{phone_number_id}/history-import`, `POST /numbers/{phone_number_id}/detach`, form yazma uçları | `403 test_mode_unsupported` | | `POST /templates/sync` | Çalışır; yalnız Meta'daki şablon durumlarını yerel listeye okur. | | `POST /templates/{id}`, `POST /templates/{id}/copy` (`202`, `id: 0`), `DELETE /templates?name=…`, `POST /templates/media` (`201`, `header_handle: "4::test…"`, `header_media_ref: null`) | Simüle edilir; kayıt ve Meta'ya gönderim yok. Doğrulama, sahiplik ve durum kuralları (404 / 409 / 422) canlıdaki gibi çalışır. `POST /templates/media` multipart girişinde tür ve boyut denetimi yapılır; `url` ve `media_id` girişinde indirme yapılmaz. | ## Sahte gelen mesaj oluşturun Webhook alıcınızı denemek için `POST /test/inbound` kullanın. Bu uç yalnız test anahtarıyla çalışır; sahte mesajı gerçek gelen mesaj hattından geçirir ve imzalı olayı aktif webhook aboneliklerinize gönderir. ```bash cURL curl -X POST https://api.verimerkezi.app/wa/test/inbound \ -H "Authorization: Bearer $VM_TEST_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "type": "text", "from": "905550000000", "text": "Merhaba, bu bir test mesajıdır." }' ``` ```javascript Node.js const res = await fetch('https://api.verimerkezi.app/wa/test/inbound', { method: 'POST', headers: { Authorization: `Bearer ${process.env.VM_TEST_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ phone_number_id: '1234567890', type: 'text', from: '905550000000', text: 'Merhaba, bu bir test mesajıdır.' }), }); console.log(res.status, await res.json()); ``` ```python Python import os, requests res = requests.post( "https://api.verimerkezi.app/wa/test/inbound", headers={"Authorization": f"Bearer {os.environ['VM_TEST_KEY']}"}, json={"phone_number_id": "1234567890", "type": "text", "from": "905550000000", "text": "Merhaba, bu bir test mesajıdır."}, ) print(res.status_code, res.json()) ``` ```php PHP true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('VM_TEST_KEY'), 'Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode(['phone_number_id' => '1234567890', 'type' => 'text', 'from' => '905550000000', 'text' => 'Merhaba, bu bir test mesajıdır.']), ]); print_r(json_decode(curl_exec($ch), true)); ``` `type` alanı dört değer alır: - `text`: `message.received` olayı üretir. `text` zorunludur. - `edit`: `message.edited` olayı üretir. `text` ve `original_message_id` gönderin. - `revoke`: `message.revoked` olayı üretir. `original_message_id` gönderin. - `app_contact_sync`: `add`, `update` ve `remove` içeren örnek bir `contact.synced` olayı üretir. Rehbere kayıt yazılmaz. Bkz. [Uygulama rehberi](uygulama-rehberi.html). Başarılı istek `202 Accepted` döner. Canlı anahtarla çağrılırsa `403 test_only` alırsınız. ## Webhook bağlantısını sınayın Abonelik oluşturduktan sonra `POST /webhooks/{id}/test` ile anında imzalı bir `test.ping` olayı gönderebilirsiniz. Bu uç test ve canlı anahtarla çalışır. Ayrıntı: [Webhooklar](webhooklar.html). ## Canlıya geçiş 1. Panelden `vmk_live_` anahtarı oluşturun ve yalnız gereken kapsamları seçin. 2. Ortam değişkenini canlı anahtarla değiştirin. Kodda başka değişiklik gerekmez. 3. Kredi bakiyenizi `GET /credit/balance` ile kontrol edin ve `credit.low` olayına abone olun. --- # Kimlik doğrulama Bearer API anahtarı, anahtar türleri, kapsamlar, IP kısıtı ve anahtar güvenliği. Veri Merkezi API'si her istekte bir API anahtarı ister. Bu sayfa anahtarı nasıl göndereceğinizi, hangi kapsamları seçeceğinizi ve anahtarı nasıl koruyacağınızı anlatır. ## Anahtarı gönderin Anahtarı `Authorization` başlığında `Bearer` şemasıyla gönderin: ```bash curl https://api.verimerkezi.app/wa/me \ -H "Authorization: Bearer vmk_live_..." ``` - API yalnız `https://api.verimerkezi.app/wa` adresinde çalışır. Başka bir adresten yapılan çağrı `404 endpoint_moved` döner. - Başlık yoksa `401 unauthorized`, anahtar geçersizse `401 invalid_api_key` döner. ## Anahtar türleri | Önek | Mod | Davranış | |---|---|---| | `vmk_live_` | Canlı | Mesajlar Meta'ya gönderilir; her başarılı mesaj 1 kredi düşer. | | `vmk_test_` | Test | Mesaj Meta'ya gönderilmez, kredi düşmez; sahte `test.` kimlikleri döner. Hangi uçların simüle edildiğini [Test modu](test-modu.html) sayfasında görün. | Anahtarlar panelde **API → Anahtarlar** sayfasında oluşturulur ve **yalnız bir kez** gösterilir. Sunucuda yalnız anahtarın SHA-256 özeti saklanır; kaybederseniz yeni anahtar oluşturun. ## Kapsamlar Her anahtar yalnız verilen kapsamlardaki işlemleri yapabilir. Kapsam dışı istek `403 insufficient_scope` döner ve yanıt gereken kapsamı `error.required_scope` alanında belirtir. Birden çok kapsamdan birini kabul eden uçlarda (`GET /messages`, `GET /media`, `POST /webhooks/redeliver`, `POST /templates/sync`) kabul edilen kapsamlar ayrıca `error.required_scopes` dizisinde gelir. | Kapsam | İzin verdiği işlemler | |---|---| | `*` | Tüm kapsamlar (tam yetki). | | `messages:send` | Mesaj gönderme, okundu/yazıyor göstergesi, medya yükleme. | | `messages:read` | Mesaj kayıtları, gelen medyayı listeleme ve indirme, ölü teslimleri yeniden kuyruğa alma. | | `inbox:read` | `messages:read` ile aynı uçlar. | | `templates:read` | Şablon listeleme, ayrıntı, doğrulama, senkron. | | `templates:write` | Şablon oluşturma, düzenleme, silme, senkron. | | `contacts:read` | Kişi listesi ve abonelikten çıkanlar. | | `contacts:write` | Kişi ekleme (tekil ve toplu). | | `profile:read` | Numaralar, işletme profili, geçmiş aktarımı durumu. | | `profile:write` | Profil, numara ve hesap ayarları, saklama süreleri, KVKK silme, numara ayırma. | | `reports:read` | Rapor özeti, kredi kullanımı ve kredi hareketleri. | | `webhooks:read` | Webhook aboneliklerini listeleme. | | `webhooks:write` | Abonelik oluşturma, güncelleme, silme, test, anahtar döndürme, yeniden teslim. | | `calls:read` | Arama ayarları, izin durumu ve arama geçmişi. | | `calls:write` | Arama başlatma, kabul, ret, sonlandırma, izin isteme, arama ayarları. | | `flows:read` | Form listesi, ayrıntı, önizleme, metrikler. | | `flows:write` | Form oluşturma, güncelleme, yayınlama, kaldırma, silme. | `GET /me`, `GET /health`, `GET /credit/balance` ve `GET /credit/packages` için ek kapsam gerekmez. > **İpucu:** En az yetki ilkesini uygulayın. Yalnız mesaj gönderen bir bot için `messages:send` ve `templates:read` yeterlidir. Kapsam seçmeden oluşturulan anahtar `*` (tam yetki) alır; geçersiz kapsam adlarıyla yapılan oluşturma isteği reddedilir. ## IP kısıtı ve son kullanma Panelde anahtara isteğe bağlı olarak şunları tanımlayabilirsiniz: - **IP izin listesi:** tek IP ya da CIDR aralığı. Liste dışından gelen istek `401 invalid_api_key` döner. - **Son kullanma tarihi:** varsayılan olarak anahtar süresizdir. Süresi dolan anahtar `401 invalid_api_key` döner. ## Kaba kuvvet koruması Geçersiz ya da eksik anahtarla gelen istekler IP başına dakikada 60 ile sınırlıdır. Sınır aşılınca `429` ve `Retry-After` döner. Geçerli anahtarla yapılan istekler bu sınırdan etkilenmez; kalıcı yasak yoktur. ## Anahtar güvenliği - Anahtarı ortam değişkeninde ya da gizli anahtar yöneticisinde saklayın; kaynak koduna ve sürüm kontrolüne eklemeyin. - Anahtarı tarayıcı ya da mobil uygulama koduna gömmeyin. İstekleri kendi sunucunuz üzerinden yapın. - Her uygulama ve ortam için ayrı anahtar kullanın. - Anahtarları düzenli aralıklarla yenileyin: yeni anahtar oluşturun, sisteminize ekleyin, test edin, eskisini iptal edin. ## Anahtar sızarsa 1. Panelden anahtarı hemen **iptal edin**. İptal geri alınamaz; anahtar `401` dönmeye başlar. 2. Yeni anahtar oluşturup sisteminize ekleyin. 3. Panelde **API → Kullanım** kayıtlarından şüpheli çağrıları inceleyin. 4. Destek için bilgi@verimerkezi.app adresine yazın. ## İstek kimliği Her yanıt `X-Request-Id` başlığı taşır. İsterseniz kendi kimliğinizi aynı başlıkla gönderebilirsiniz. Destek talebinde bu kimliği paylaşın. ## Tarayıcıdan çağrı (CORS) API, CORS için her kaynağa izin verir (`Access-Control-Allow-Origin: *`). İzin verilen istek başlıkları `Authorization`, `Content-Type`, `Accept`, `Idempotency-Key` ve `X-Request-Id`; okunabilen yanıt başlıkları `X-Request-Id`, `X-RateLimit-*` ve `Idempotent-Replay`'dir. > **Uyarı:** CORS açık olsa da API anahtarını tarayıcıda çalışan koda koymayın. Sayfayı açan herkes anahtarı görebilir. Tarayıcıdan gelen istekleri kendi sunucunuz üzerinden API'ye iletin. > **Not (2.28.0):** Doğrulanmış anahtar bilgisi sunucuda en fazla 15 saniye önbellekte tutulur. Panelden iptal ya da IP listesi değişikliği önbelleği hemen temizler; yönetim tarafındaki diğer değişiklikler en geç 15 saniyede etkili olur. --- # Hatalar Hata yanıtının yapısı, HTTP durum kodları, sık görülen hata kodları ve doğru yeniden deneme stratejisi. API, başarısız isteklerde uygun bir HTTP durum kodu ve makine tarafından okunabilir bir hata nesnesi döndürür. Bu sayfa hataları nasıl yorumlayacağınızı ve ne zaman yeniden deneyeceğinizi anlatır. ## Hata yanıtının yapısı ```json { "ok": false, "error": { "code": "invalid_phone", "message": "Geçersiz alıcı numarası (E.164 format gerekli).", "field": "to" } } ``` | Alan | Açıklama | |---|---| | `error.code` | Makine tarafından okunabilir hata kodu. Mantığınızı bu alana göre kurun. | | `error.message` | Türkçe açıklama. Kullanıcıya gösterilebilir; metni değişebilir. | | `error.field` | Hata belirli bir alandan kaynaklanıyorsa alan adı (ör. `to`, `text`, `audio.voice`). | Bazı hatalarda `error` içinde ek alanlar gelir: | Alan | Ne zaman | |---|---| | `retryable` | Mesaj gönderimi, arama ve form hatalarında. İsteğin tekrar denenip denenemeyeceğini söyler. | | `meta_code`, `meta_subcode` | Meta kaynaklı hatalarda Meta'nın kodu. Meta kaynaklı her hatada vardır; Meta'ya bağlanılamadıysa `0`. `meta_subcode` metin olarak döner. Şablon uçlarında ayrıca `meta_message`, `meta_user_title`, `meta_user_message` ve `fbtrace_id` gelir. | | `retry_after` | `429` ve `409 idempotency_in_progress` yanıtlarında beklenecek saniye. | | `balance` | `402 insufficient_credits` yanıtında güncel bakiye. | | `required_scope` | `403 insufficient_scope` yanıtında gereken kapsam. | | `required_scopes` | Birden çok kapsamdan birini kabul eden uçlarda (ör. `GET /messages`) kabul edilen kapsamların dizisi; `required_scope` ile birlikte gelir. | | `meta_message` | Arama hatalarında Meta'nın açıklaması (en fazla 300 karakter). | | `phone_number_id`, `detached_at` | `409 number_detached` yanıtında ayrılan numara ve ayrılma zamanı. | | `max_bytes`, `type` | `POST /media` `413 media_too_large` yanıtında tür ve izin verilen en büyük boyut (`POST /templates/media` için `format`). | | `field` | Şablon doğrulama hatalarında (`422 invalid_request`, `422 template_invalid`, `409 template_exists`) hatalı alanın yolu (ör. `components[0].text`, `name`). | | `next_edit_at`, `retry_after` | `429 template_edit_limit` yanıtında en erken düzenleme zamanı ve beklenecek saniye. | | `template_id` | `409 template_exists` yanıtında mevcut şablonun kimliği. | | `rate_limited`, `errors` | `POST /templates/sync` kısmen başarısız olduğunda WABA bazında ayrıntı. | | `base_url`, `docs` | `404 endpoint_moved` yanıtında doğru taban adres ve doküman adresi. | | `existing_id` | `409 webhook_url_exists` yanıtında mevcut abonelik. | | `message_tr`, `action_tr` | Tüm hata yanıtlarında: kısa Türkçe açıklama ve yapılacak adım (adım yoksa `null`). Kaynak: [hata kodları sözlüğü](referans/numbers.html) (`GET /meta/error-codes`). | | `severity`, `category` | Kod sözlükte varsa önem (`info` | `warn` | `error` | `critical`); Meta kaynaklı hatada kategori (`recipient` | `template` | `account` | `rate` | `payment` | `other`). | | `meta_message`, `meta_user_message`, `meta_message_tr` | Meta'nın ham (çoğunlukla İngilizce) metni yalnız bu alanlarda gelir; `error.message` her zaman Türkçedir. Arama ve form hatalarında `meta_message_tr` Türkçe karşılıktır. | > **Not:** `401`, `429` ve `503` yanıtlarının gövdesinde şu an `ok` alanı yoktur; yalnız `error` nesnesi döner. Başarıyı `ok` alanıyla değil, HTTP durum koduyla belirleyin. Bilmediğiniz ek alanları yok sayın. ## HTTP durum kodları | Durum | Anlamı | |---|---| | `200`, `201`, `202` | Başarılı. `202`, işlemin kabul edildiğini gösterir. | | `206`, `304` | Medya indirmede kısmi içerik ve değişmemiş dosya. | | `400` | Yol parametresi hatalı (ör. `invalid_media_id`). | | `401` | Anahtar yok ya da geçersiz. | | `402` | Mesaj kredisi yetersiz. | | `403` | Kapsam yetersiz ya da işlem bu anahtar türüyle yapılamaz. | | `404` | Kaynak yok ya da size ait değil. | | `409` | Çakışma: kayıt zaten var, numara ayrılmış, işlem sürüyor ya da sonuç belirsiz. | | `413`, `415`, `416` | Medya boyutu, türü ya da bayt aralığı hatası. | | `422` | Doğrulama hatası ya da Meta reddi. | | `429` | Hız sınırı aşıldı. | | `500`, `502`, `503` | Sunucu ya da Meta tarafında geçici sorun. | ## Sık görülen hatalar ### 422 meta_send_failed ve 24 saat penceresi Müşteri size son 24 saat içinde yazmadıysa serbest metin, medya ve form mesajı Meta tarafından reddedilir. Yanıt `422 meta_send_failed` ve `meta_code: 131047` taşır. Bu durumda onaylı bir şablon gönderin. Ayrıntı: [Mesaj gönderme](mesaj-gonderme.html). ### 422 template_not_in_waba Şablon, gönderen numaranın bağlı olduğu işletme hesabında (WABA) yok ya da onaylı değil. Şablonun aynı WABA'da `APPROVED` olduğundan emin olun. Bu hata `retryable: false` döner. ### 402 insufficient_credits Canlı anahtarda kredi bittiğinde döner. Kredi gönderimden önce ayrılır; gönderim başarısız olursa otomatik iade edilir. Bakiyeyi `GET /credit/balance` ile izleyin ve `credit.low` olayına abone olun. ### 409 meta_outcome_unknown İstek Meta'ya iletildi ama yanıt alınamadı (zaman aşımı ya da bağlantı kopması). Mesaj gitmiş olabilir. **Aynı isteği tekrar göndermeyin.** `message.status.*` olayını bekleyin ya da `GET /messages` ile kontrol edin. `Idempotency-Key` kullandıysanız aynı anahtarla yapılan tekrar Meta'ya ikinci kez gitmez. ### 403 insufficient_scope Anahtarda gereken kapsam yok. `error.required_scope` alanındaki kapsamı panelden anahtara ekleyin. ## Yeniden deneme stratejisi | Durum | Yapılacak | |---|---| | `429` | `Retry-After` başlığındaki saniye kadar bekleyin, sonra tekrar deneyin. | | `500`, `502`, `503` | Üstel bekleme ile tekrar deneyin (1 sn, 2 sn, 4 sn, 8 sn). | | `retryable: true` | Aynı `Idempotency-Key` ile güvenle tekrar deneyebilirsiniz. | | `retryable: false` | İsteği değiştirmeden tekrar denemeyin; önce sebebi giderin. | | `409 idempotency_in_progress` | `Retry-After` kadar bekleyip aynı anahtarla tekrar deneyin. | | `409 meta_outcome_unknown` | Tekrar göndermeyin; durum olayını bekleyin. | | Diğer `4xx` | İsteği düzeltin; aynı isteği tekrar göndermek sonucu değiştirmez. | Resmî SDK'lar `5xx` ve `429` yanıtlarında en fazla 3 kez otomatik yeniden dener. ## Hataları kaydedin Üretimde her hatayı en az şu bilgilerle kaydedin: zaman, uç nokta, HTTP durumu, `error.code`, `error.field` ve yanıttaki `X-Request-Id` başlığı. Destek talebinde `X-Request-Id` değerini paylaşın. Tüm kodların listesi: [Hata kodları](referans/hata-kodlari.html). --- # Hız sınırları Anahtar ve hesap düzeyinde dakikalık istek sınırları, birinci taraf önceliği, X-RateLimit başlıkları, 429 ve 503 yanıtlarını doğru işleme. API dakikalık sabit pencereli hız sınırı uygular. Sınırlar iki düzeyde sayılır: - **Anahtar düzeyi:** her API anahtarının kendi sayacı. - **Hesap düzeyi (2.27.0):** hesabınızdaki tüm anahtarların ortak bütçesi. Hesabınızda kaç anahtar olursa olsun toplam bu bütçeyi aşamaz. Sınırlar işlem türlerine göre ayrı sayaçlarda (kovalarda) tutulur; bir işlem türü diğerinin kotasını tüketmez. ## Kovalar ve varsayılan sınırlar | Kova | Kapsadığı istekler | Anahtar sınırı | Hesap bütçesi | |---|---|---|---| | `send` | `POST /messages`, `POST /calls` ve `POST /calls/...`, kampanya başlatma | 120 / dakika | 300 / dakika | | `read` | `POST /messages/read` | 120 / dakika | 300 / dakika | | `media` | `/media` altındaki tüm istekler (indirme, `HEAD`, yükleme) | 60 / dakika | 150 / dakika | | `get`, `post`, `patch`, `put`, `delete` | Kalan istekler, HTTP yöntemi başına ayrı kova | 120 / dakika | 300 / dakika | ### Birinci taraf anahtar önceliği Hesabınıza bağlı bir Veri Merkezi hizmetinin (ör. Sigorta CRM) kurulum anahtarı **birinci taraf** anahtardır: - Anahtar sınırı hesap bütçesine eşittir (JSON kovaları 300, medya 150 / dakika). - Hesap bütçesinin tamamını kullanabilir. Hesapta birinci taraf anahtar varken diğer anahtarlarınız (kendi entegrasyonlarınız) hesap bütçesinin **%40'ını birlikte paylaşır** (JSON kovaları 120, medya 60 / dakika). Böylece kendi sistemleriniz hizmetin çalışması için ayrılan payı tüketemez. Hesapta birinci taraf anahtar yoksa pay sınırı uygulanmaz; anahtarlar yalnız kendi sınırlarına ve hesap bütçesine tabidir. ### Eşzamanlı medya indirme (2.28.0) Dakikalık sınıra ek olarak hesap başına **aynı anda en fazla 6 medya indirmesi** yapılabilir. Birinci taraf anahtar 6 yuvanın tamamını, diğer anahtarlar birlikte en fazla 2 yuvayı kullanır. `HEAD` istekleri ve `?meta=1` sayılmaz. Yuva dolunca `429 rate_limited` döner: `error.scope` = `concurrency`, `X-RateLimit-Scope: concurrency`, `X-Concurrency-Limit` ve `Retry-After: 2`. Bir indirme bitince tekrar deneyin; medyayı aynı anda en fazla 3 istekle indirmeniz önerilir. Ek sınırlar: - **Şablon senkronu:** `POST /templates/sync` işletme hesabı (WABA) başına dakikada 1 kez. Aşılınca `429 sync_rate_limited`. - **Arama izni isteği:** aynı kişiye 24 saatte 1, 7 günde 2. Aşılınca `429 permission_request_limited`. - **Başarısız kimlik doğrulama:** geçersiz anahtarla gelen istekler IP başına dakikada 60. > **Not:** Plan kapsamında bir anahtara özel sınır tanımlanabilir (genel ve medya için ayrı, en fazla dakikada 1000). Tanımlıysa `X-RateLimit-Limit` başlığı o değeri gösterir. Artırım talebi için bilgi@verimerkezi.app adresine yazın. ## Yanıt başlıkları Her başarılı yanıt kalan hakkınızı iki düzeyde bildirir: ``` X-RateLimit-Bucket: get X-RateLimit-Limit: 120 X-RateLimit-Remaining: 117 X-RateLimit-Reset: 1791619260 X-RateLimit-Account-Limit: 300 X-RateLimit-Account-Remaining: 288 X-RateLimit-Account-Reset: 1791619260 ``` | Başlık | Anlamı | |---|---| | `X-RateLimit-Bucket` | İsteğin sayıldığı kova (`send`, `read`, `media`, `get`, `post`, `patch`, `put`, `delete`). | | `X-RateLimit-Limit` | Anahtarın bu penceredeki üst sınırı. | | `X-RateLimit-Remaining` | Anahtarın bu pencerede kalan istek sayısı. | | `X-RateLimit-Reset` | Pencerenin sıfırlanacağı Unix zamanı (saniye). | | `X-RateLimit-Account-Limit` | Hesap düzeyindeki üst sınır. Birinci taraf anahtarın bulunduğu hesapta diğer anahtarlar için ortak payı gösterir. | | `X-RateLimit-Account-Remaining` | Hesap düzeyinde (ya da ortak payda) kalan istek sayısı. | | `X-RateLimit-Account-Reset` | Hesap penceresinin sıfırlanacağı Unix zamanı (saniye). | | `X-RateLimit-Scope` | Yalnız `429` yanıtında: hangi sınırın aşıldığı (`key`, `account`, `account_share`, `concurrency`). | | `X-Concurrency-Limit` | Yalnız eşzamanlı medya sınırında: bu anahtarın kullanabileceği yuva sayısı. | Bu başlıklar tarayıcıdan yapılan isteklerde de okunabilir (`Access-Control-Expose-Headers`). ## Sınır aşılınca (429) ```http HTTP/1.1 429 Too Many Requests Retry-After: 23 X-RateLimit-Scope: account_share Content-Type: application/json { "ok": false, "error": { "code": "rate_limited", "message": "Çok fazla istek (hesaptaki ek anahtarların ortak payı doldu). Lütfen biraz bekleyin.", "scope": "account_share", "bucket": "media", "retry_after": 23 } } ``` | `scope` | Anlamı | |---|---| | `key` | Bu anahtarın kendi sınırı doldu. | | `account_share` | Hesaptaki ek anahtarların ortak payı doldu (birinci taraf anahtar etkilenmez). | | `account` | Hesabın toplam bütçesi doldu. | | `concurrency` | Hesabın eşzamanlı medya indirme yuvaları dolu (2.28.0). | `Retry-After` saniye kadar bekleyip isteği tekrarlayın. Resmî SDK'lar bunu otomatik yapar. ## Geçici yoğunluk (503) Altyapı geçici olarak yanıt veremezse (ör. veritabanı bağlantısı kısa süre reddedilirse) API `500` yerine `503` döner: ```http HTTP/1.1 503 Service Unavailable Retry-After: 5 Content-Type: application/json { "ok": false, "error": { "code": "temporarily_unavailable", "message": "Hizmet geçici olarak yoğun. Retry-After saniye sonra tekrar deneyin.", "retry_after": 5 } } ``` Veritabanı kısa sürede art arda bağlantı reddederse API birkaç saniye bağlantı denemeden doğrudan `503` döner (devre kesici); bu sürede önbellekteki anahtar ve medya bilgisiyle medya indirmeleri çalışmayı sürdürür. `503` her zaman güvenle yeniden denenebilir. Kalıcı bir sorun yoksa `500` dönmez; `500` gerçek bir sistem hatasıdır. Bu durumda `X-Request-Id` değeriyle bize yazın. > **Uyarı:** Gönderim sayacının altyapısı yanıt veremezse gönderim istekleri güvenli tarafta reddedilir ve `503 rate_limit_unavailable` ile `Retry-After: 5` döner. Birkaç saniye sonra aynı `Idempotency-Key` ile tekrar deneyin. ## Önerilen yeniden deneme | Durum | Ne yapmalı | |---|---| | `429` | `Retry-After` kadar bekleyin, en fazla 3 deneme. | | `503` | `Retry-After` kadar bekleyin (yoksa 5 sn), en fazla 3 deneme. | | `500`, `502`, `504` | 2, 5 ve 15 saniye arayla en fazla 3 deneme. Gönderimde aynı `Idempotency-Key` kullanın. | | `4xx` (429 dışı) | Yeniden denemeyin; isteği düzeltin. | ```javascript Node.js async function istekGonder(url, secenekler, deneme = 0) { const res = await fetch(url, secenekler); if ((res.status === 429 || res.status === 503) && deneme < 3) { const bekle = Number(res.headers.get('Retry-After') || 5); await new Promise((r) => setTimeout(r, bekle * 1000)); return istekGonder(url, secenekler, deneme + 1); } return res; } ``` ```python Python import time, requests def istek_gonder(method, url, deneme=0, **kwargs): res = requests.request(method, url, **kwargs) if res.status_code in (429, 503) and deneme < 3: time.sleep(int(res.headers.get("Retry-After", 5))) return istek_gonder(method, url, deneme + 1, **kwargs) return res ``` ```php PHP **Not:** `GET /media` yanıtında imleç iç içe `paging` nesnesindedir: `{ "media": [...], "paging": { "limit": 50, "has_more": true, "next_cursor": 13115 } }`. > **Uyarı:** İmleci değiştirmeyin ve içeriğine güvenmeyin. Yalnız API'nin döndürdüğü değeri olduğu gibi geri gönderin. ## Tüm kayıtları çekin ```javascript Node.js const tum = []; let cursor = null; do { const url = new URL('https://api.verimerkezi.app/wa/contacts'); url.searchParams.set('limit', '200'); if (cursor) url.searchParams.set('cursor', cursor); const r = await (await fetch(url, { headers: { Authorization: `Bearer ${process.env.VM_API_KEY}` } })).json(); tum.push(...r.contacts); cursor = r.has_more ? r.next_cursor : null; } while (cursor); console.log(`${tum.length} kişi`); ``` ```python Python import os, requests tum, cursor = [], None while True: params = {"limit": 200} if cursor: params["cursor"] = cursor r = requests.get("https://api.verimerkezi.app/wa/contacts", params=params, headers={"Authorization": f"Bearer {os.environ['VM_API_KEY']}"}).json() tum.extend(r["contacts"]) if not r["has_more"]: break cursor = r["next_cursor"] print(len(tum), "kişi") ``` ```php PHP 200, 'cursor' => $cursor])); $ch = curl_init('https://api.verimerkezi.app/wa/contacts?' . $q); curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('VM_API_KEY')]]); $r = json_decode(curl_exec($ch), true); $tum = array_merge($tum, $r['contacts']); $cursor = $r['has_more'] ? $r['next_cursor'] : null; } while ($cursor); echo count($tum) . " kişi\n"; ``` ## Süzgeçle birlikte Süzgeçleri (`q`, `status`, `source` vb.) imleçle birlikte kullanabilirsiniz. Tutarlı sonuç için sayfalar boyunca süzgeci değiştirmeyin. ## Önceki sayfa İmleç tabanlı sayfalama geriye gitmez. "Önceki" düğmesi gerekiyorsa gördüğünüz imleçleri istemci tarafında bir yığında saklayın. ## Mesaj kayıtlarında artımlı okuma `GET /messages`, kaçan webhook olaylarını geri almak içindir ve kayıtları eskiden yeniye döndürür. `since` (en fazla 90 gün geriye) ile başlayın; her sayfadaki `next_cursor` değerini saklayıp sonraki çağrıda verin. Ayrıntı: [Mesaj kayıtlarını listele](referans/messages.html#mesajlari-listele). --- # Idempotency Idempotency-Key başlığıyla ağ hatalarında isteği güvenle tekrarlayın, aynı mesajın iki kez gitmesini önleyin. Ağ hatası ya da zaman aşımında isteğinizin sunucuya ulaşıp ulaşmadığını bilemezsiniz. `Idempotency-Key` başlığı, aynı isteği güvenle tekrarlamanızı ve müşterinin aynı mesajı iki kez almamasını sağlar. ## Desteklenen uçlar | Uç | Not | |---|---| | `POST /messages` | Tüm mesaj türleri. | | `POST /numbers/{phone_number_id}/detach` | Numara ayırma. | | `POST /calls` | İşletme başlatmalı arama. | | `POST /calls/permission-request` | Arama izni isteği. | | `POST /flows` | Form oluşturma. | Diğer uçlar başlığı yok sayar. Arama eylemleri (`pre_accept`, `accept`, `reject`, `terminate`) başlık olmadan da arama kimliği ve eylem bazında idempotenttir. ## Kullanım Her yeni işlem için benzersiz bir anahtar üretin (UUID önerilir) ve aynı işlemin tekrarlarında **aynı** anahtarı kullanın. Anahtar 8–128 karakter olmalıdır. ```bash cURL curl -X POST https://api.verimerkezi.app/wa/messages \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: siparis-VM-2026-1234-onay" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "type": "text", "text": { "body": "Siparişiniz onaylandı." } }' ``` ```javascript Node.js import { randomUUID } from 'node:crypto'; const anahtar = randomUUID(); // tekrarlarda aynı değeri kullanın ``` ```python Python import uuid anahtar = str(uuid.uuid4()) # tekrarlarda aynı değeri kullanın ``` ```php PHP **İpucu:** Anahtarı kendi kaydınızdan türetmek (ör. sipariş numarası + mesaj türü) yeniden başlatmalarda da aynı anahtarı üretmenizi kolaylaştırır. ## Davranış | Durum | Sonuç | |---|---| | İlk istek | Normal işlenir; yanıt 24 saat saklanır. | | Aynı anahtar, aynı gövde | Saklanan yanıt aynen döner, `Idempotent-Replay: true` başlığıyla. Mesaj ikinci kez gitmez. | | Aynı anahtar, farklı gövde | `422 idempotency_key_conflict`. | | Aynı anahtarla eşzamanlı ikinci istek | `409 idempotency_in_progress` ve `Retry-After`. | | Önceki deneme kesin başarısız oldu (`retryable: true`) | Aynı anahtarla tekrar denenebilir; yeniden işlenir. | | Önceki denemede Meta sonucu belirsiz kaldı | Tekrar `409 meta_outcome_unknown` döner; Meta'ya ikinci kez gönderilmez. | | 24 saat sonra aynı anahtar | Yeni istek olarak işlenir. | Gövde karşılaştırması ham istek gövdesi üzerinden yapılır. Tekrarlarda gövdeyi bayt bayt aynı gönderin. ## Belirsiz sonuç Meta çağrısı zaman aşımına uğrarsa mesaj gitmiş olabilir. API bu durumda `409 meta_outcome_unknown` ve `retryable: false` döndürür. Aynı anahtarla tekrar denediğinizde mesaj yeniden gönderilmez. Mesajın gidip gitmediğini öğrenmek için: - `message.status.*` olayını bekleyin. Olaydaki `biz_opaque_callback_data` değeri, `Idempotency-Key` değerinizdir. - Ya da `GET /messages` ile mesaj kayıtlarını kontrol edin. Durum olayı geldiğinde sunucu kaydı kendiliğinden "gönderildi" olarak kapatır; aynı anahtarla yapılan sonraki tekrar artık `200` döner. ## Gönderimi durum olayıyla eşleştirin `POST /messages` isteğinde `Idempotency-Key` verdiğinizde sunucu Meta'ya `biz_opaque_callback_data` olarak bu anahtarı iletir. Meta bu değeri `message.status.sent`, `.delivered`, `.read` ve `.failed` olaylarında `data.biz_opaque_callback_data` alanında aynen geri gönderir. Böylece ek alan göndermeden her durum olayını kendi kaydınızla eşleştirebilirsiniz. Farklı bir eşleştirme değeri isterseniz gövdede `biz_opaque_callback_data` (en fazla 512 karakter) gönderin. Bu değer gövdenin parçası olduğu için idempotency karşılaştırmasına dahildir. ## SDK davranışı Resmî SDK'lar her yazma isteğine otomatik bir `Idempotency-Key` ekler ve kendi yeniden denemelerinde aynı anahtarı kullanır. Uygulama düzeyinde (ör. süreç yeniden başlatıldığında) tekrar yapacaksanız anahtarı kendiniz yönetin. --- # En iyi uygulamalar Güvenilir, güvenli ve Meta kurallarına uygun bir WhatsApp entegrasyonu için kontrol listesi. Bu kontrol listesi, üretimde sorunsuz çalışan bir entegrasyon için önerilerimizi toplar. ## Güvenlik - API anahtarını yalnız sunucuda, ortam değişkeninde saklayın. Tarayıcıya ya da mobil uygulamaya koymayın. - Her ortam (geliştirme, test, üretim) için ayrı anahtar kullanın; geliştirmede `vmk_test_` anahtarı tercih edin. - Anahtarlara yalnız gereken kapsamları verin ve mümkünse IP izin listesi tanımlayın. - Webhook isteklerinde imzayı **her zaman** doğrulayın ve 5 dakikadan eski zaman damgalarını reddedin. Bkz. [Webhooklar](webhooklar.html). - Webhook imza anahtarını kesintisiz yenilemek için `POST /webhooks/{id}/rotate-secret` kullanın. ## Güvenilir gönderim - Her `POST /messages` isteğinde `Idempotency-Key` gönderin. Bkz. [Idempotency](idempotency.html). - `409 meta_outcome_unknown` aldığınızda isteği tekrarlamayın; durum olayını bekleyin. - `5xx` ve `429` yanıtlarında üstel bekleme ile yeniden deneyin; `retryable: false` hatalarını tekrar denemeyin. - Gönderim sonucunu yalnız HTTP yanıtına değil, `message.status.*` olaylarına göre işaretleyin. `201` yanıtı mesajın Meta'ya kabul edildiğini gösterir; teslim ve okunma olaylarla gelir. ## Webhook alıcısı - `2xx` yanıtını hızlı döndürün (zaman aşımı 10 saniye); işi bir kuyruğa alın. - `event_id` (ya da `X-VeriMerkezi-Event-Id`) değerini saklayıp yinelenen teslimleri ayıklayın. Yeniden denemeler aynı kimlikle gelir. - Olayların sırayla geleceğini varsaymayın; zaman damgalarına ve durum önceliğine göre birleştirin. - Yeni olaylar `*` aboneliğine kendiliğinden eklenmez; ihtiyaç duyduğunuz olayları açıkça seçin. - Sunucunuz kesintiye uğradıysa `GET /messages` ile kayıtları geri alın ve `POST /webhooks/redeliver` ile ölü teslimleri yeniden kuyruğa alın. - Webhook gecikmesini `GET /health` ile izleyin. ## Meta kuralları - Müşteri size son 24 saat içinde yazmadıysa yalnız onaylı şablon gönderebilirsiniz. - Pazarlama mesajlarını yalnız açık rıza vermiş kişilere gönderin. `GET /contacts/opted-out` listesini ve `contact.opt_out_changed` olayını kullanarak reddedenleri gönderim listenizden çıkarın. - Numara kalitesini (`quality.changed`, `GET /numbers` içindeki `quality_rating`) izleyin. `RED` durumunda gönderimi durdurup içeriği gözden geçirin. - Şablonları işletme hesabı (WABA) düzeyinde yönetin; bir WABA'daki tüm numaralar aynı şablonları kullanır. ## Kredi ve maliyet - `credit.low` ve `credit.exhausted` olaylarına abone olun; kampanya ortasında kredinin bitmesini önleyin. - Bakiyeyi `GET /credit/balance`, kullanımı `GET /credit/usage` ile izleyin. - Okundu işareti (`POST /messages/read`) ve uç yönetim işlemleri kredi düşmez. ## Veri koruma (KVKK) - Silme talebi gelen kişi için `POST /privacy/erasure` çağırın; işlem idempotenttir ve tamamlanınca `privacy.erasure_completed` olayı gelir. - Abonelikten çıkış (opt-out) kanıtları yasal gereklilik olduğu için silinmez. - Gelen medyayı yalnız gerektiğinde indirin; dosyalar API anahtarı olmadan erişilemez. ## Uyumluluk - Bilmediğiniz yanıt alanlarını ve yeni enum değerlerini yok sayın; API geriye dönük uyumlu olarak yeni alanlar ekler. - Zaman damgalarını saat dilimi ofsetiyle işleyin (şu an `+03:00`); sabit `Z` varsaymayın. - Değişiklikleri [Değişiklik günlüğü](degisiklikler.html) üzerinden izleyin. --- # Mesaj gönderme POST /messages ile şablon, metin, medya, konum, kişi kartı, tepki, alıntılı yanıt ve form mesajı gönderin; 24 saat penceresini doğru yönetin. Tüm mesajlar tek bir uçtan gönderilir: `POST /messages`. Mesaj türü, gövdedeki alanlarla belirlenir. Bu sayfa her türün nasıl gönderileceğini ve hangi kuralların geçerli olduğunu anlatır. ## 24 saatlik müşteri hizmet penceresi WhatsApp'ın temel kuralı şudur: müşteri size yazdığı andan itibaren 24 saat boyunca serbest mesaj (metin, medya, konum, kişi kartı, tepki, form) gönderebilirsiniz. Pencere kapalıyken yalnız Meta onaylı bir **şablon** gönderebilirsiniz. | Durum | Gönderebilecekleriniz | |---|---| | Müşteri son 24 saatte yazdı | Şablon dahil tüm türler | | Müşteri son 24 saatte yazmadı | Yalnız şablon | Pencere kapalıyken serbest mesaj `422 meta_send_failed` ve `meta_code: 131047` döner. ## Ortak alanlar | Alan | Zorunlu | Açıklama | |---|---|---| | `phone_number_id` | Evet | Gönderen numaranızın Meta kimliği (`GET /numbers`). | | `to` | Evet (ya da `recipient`) | Alıcı, ülke koduyla ve `+` olmadan (ör. `905550000000`). | | `recipient` | `to` yoksa | Telefonu görünmeyen kullanıcının kimliği (BSUID). Eş anlamlıları: `recipient_id`, `bsuid`. Bkz. [Kullanıcı adı ve BSUID](kullanici-adi-bsuid.html). | | `type` | Türe göre | `template`, `text`, `image`, `video`, `audio`, `document`, `sticker`, `location`, `contacts`, `reaction`, `flow`, `interactive`. | | `context` | Hayır | Alıntılı yanıt: `{ "message_id": "wamid..." }`. | | `biz_opaque_callback_data` | Hayır | En fazla 512 karakter; durum olaylarında aynen döner. | Başarılı gönderim `201 Created` ve aşağıdaki yanıtı döner. Canlı anahtarda her başarılı gönderim 1 kredi düşer. ```json { "ok": true, "id": 48213, "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "to": "905550000000", "type": "template", "status": "sent", "mode": "live", "simulated": false, "credits_used": 1, "balance": 9875 } ``` > **Not:** `status: "sent"` mesajın Meta'ya kabul edildiğini gösterir. Teslim ve okunma `message.status.delivered` ve `message.status.read` olaylarıyla gelir. Bkz. [Webhooklar](webhooklar.html). ## Şablon mesajı Onaylı bir şablonla gönderin. Değişkenler `components` içinde verilir. ```bash cURL curl -X POST https://api.verimerkezi.app/wa/messages \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "type": "template", "template": { "name": "siparis_onayi", "language": { "code": "tr" }, "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "Ahmet" }, { "type": "text", "text": "VM-2026-1234" } ] } ] } }' ``` ```javascript Node.js const { VeriMerkeziClient } = require('./verimerkezi-client'); const vm = new VeriMerkeziClient(process.env.VM_API_KEY); const r = await vm.sendTemplate('1234567890', '905550000000', 'siparis_onayi', 'tr', [ { type: 'body', parameters: [{ type: 'text', text: 'Ahmet' }, { type: 'text', text: 'VM-2026-1234' }] }, ]); console.log(r.wamid); ``` ```python Python import os from verimerkezi import VeriMerkeziClient vm = VeriMerkeziClient(os.environ['VM_API_KEY']) r = vm.send_template('1234567890', '905550000000', 'siparis_onayi', 'tr', [ {'type': 'body', 'parameters': [{'type': 'text', 'text': 'Ahmet'}, {'type': 'text', 'text': 'VM-2026-1234'}]} ]) print(r['wamid']) ``` ```php PHP sendTemplate('1234567890', '905550000000', 'siparis_onayi', 'tr', [ ['type' => 'body', 'parameters' => [['type' => 'text', 'text' => 'Ahmet'], ['type' => 'text', 'text' => 'VM-2026-1234']]], ]); echo $r['wamid']; ``` - `language` alanı `"tr"` ya da `{ "code": "tr" }` olabilir. Şablonun onaylandığı dil kodunu birebir gönderin. - Şablon, gönderen numaranın bağlı olduğu işletme hesabında (WABA) `APPROVED` olmalıdır; aksi hâlde `422 template_not_in_waba` döner. ### Medya başlıklı şablon ```json { "phone_number_id": "1234567890", "to": "905550000000", "type": "template", "template": { "name": "fatura_bildirimi", "language": { "code": "tr" }, "components": [ { "type": "header", "parameters": [ { "type": "document", "document": { "link": "https://cdn.example.com/fatura.pdf", "filename": "fatura-ekim.pdf" } } ] }, { "type": "body", "parameters": [ { "type": "text", "text": "Ahmet" }, { "type": "text", "text": "₺1.250,00" } ] } ] } } ``` ### Dinamik URL butonu Şablondaki URL butonunun adresi `{{1}}` ile bitiyorsa, değişken kısmı her gönderimde verin: ```json { "type": "button", "sub_type": "url", "index": 0, "parameters": [{ "type": "text", "text": "d32eec6c,5cbd62f6" }] } ``` `index` butonun sıfır tabanlı sırasıdır. Meta kuralı gereği URL butonunda tek değişken olur ve adresin sonunda yer alır. ### Doğrulama kodu (OTP) `AUTHENTICATION` kategorisindeki şablonlar için yalnız kodu vermeniz yeterlidir; gövde ve kod butonu otomatik kurulur: ```json { "phone_number_id": "1234567890", "to": "905550000000", "type": "template", "template": { "name": "otp_dogrulama", "language": { "code": "tr" }, "otp": "482913" } } ``` Kodu siz üretir ve siz doğrularsınız; Veri Merkezi yalnız kodu WhatsApp ile iletir. ## Metin mesajı ```bash cURL curl -X POST https://api.verimerkezi.app/wa/messages \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "type": "text", "text": { "body": "Merhaba Ahmet Bey, siparişiniz kargoya verildi." } }' ``` ```javascript Node.js await vm.sendText('1234567890', '905550000000', 'Merhaba Ahmet Bey, siparişiniz kargoya verildi.'); ``` ```python Python vm.send_text('1234567890', '905550000000', 'Merhaba Ahmet Bey, siparişiniz kargoya verildi.') ``` ```php PHP sendText('1234567890', '905550000000', 'Merhaba Ahmet Bey, siparişiniz kargoya verildi.'); ``` `text` alanı `{ "body": "..." }` nesnesi ya da düz metin olabilir. Metindeki bağlantının önizlemesini göstermek için `{ "body": "...", "preview_url": true }` gönderin; `preview_url` gönderilmezse önizleme kapalıdır. ## Alıntılı yanıt Herhangi bir mesaja `context` ekleyerek belirli bir mesajı yanıtlayın. Alıcıda mesaj alıntı balonuyla görünür. ```json { "phone_number_id": "1234567890", "to": "905550000000", "type": "text", "text": { "body": "Tabii, hemen bakıyorum." }, "context": { "message_id": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhggQTNFQkQ2RjQ3MEIyQzk0RTE4AA==" } } ``` `message_id`, webhook'ta gelen `data.wamid` değeridir. Geçersizse `422 invalid_context` döner. ## Medya mesajı Görsel, video, ses, belge ve çıkartma gönderebilirsiniz. Kaynak olarak herkese açık bir `https://` adresi (`link`) ya da [`POST /media`](medya.html) ile aldığınız kimliği (`id`) verin. ```json { "phone_number_id": "1234567890", "to": "905550000000", "type": "document", "document": { "link": "https://cdn.example.com/fatura-2026-10.pdf", "filename": "fatura-2026-10.pdf", "caption": "Ekim ayı faturanız ektedir." } } ``` | Tür | `caption` | `filename` | Diğer | |---|---|---|---| | `image` | Evet | — | JPEG, PNG; en fazla 5 MB | | `video` | Evet | — | MP4, 3GPP; en fazla 16 MB | | `audio` | — | — | `voice: true` sesli not; en fazla 16 MB | | `document` | Evet | Evet | PDF, Office, TXT; en fazla 100 MB | | `sticker` | — | — | WebP; en fazla 500 KB | Ayrıntı ve sesli not: [Medya](medya.html). ## Tepki ```json { "phone_number_id": "1234567890", "to": "905550000000", "type": "reaction", "reaction": { "message_id": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhggQTNFQkQ2RjQ3MEIyQzk0RTE4AA==", "emoji": "👍" } } ``` - Boş `emoji` (`""`) önceki tepkiyi kaldırır. - Tepki mesajı `context` ile birlikte gönderilemez. ## Form mesajı Yayınlanmış bir WhatsApp formunu (Flow) gönderin. Yanıt `flow.completed` olayıyla gelir. ```json { "phone_number_id": "1234567890", "to": "905550000000", "type": "flow", "flow": { "flow_id": "1234567890123456", "flow_token": "crm-basvuru-42", "cta": "Formu Aç", "body": "Başvurunuzu 1 dakikada tamamlayın.", "screen": "WELCOME", "data": { "ad": "Ahmet" } } } ``` Ayrıntı: [Formlar](formlar.html). ## Okundu işareti ve yazıyor göstergesi Gelen bir mesajı okundu olarak işaretleyin ve isteğe bağlı olarak alıcıda yaklaşık 25 saniye "yazıyor…" gösterin. Kredi düşmez. ```bash curl -X POST https://api.verimerkezi.app/wa/messages/read \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "message_id": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhggQTNFQkQ2RjQ3MEIyQzk0RTE4AA==", "typing": true }' ``` Botunuz yanıtı göndermeden hemen önce çağırın. ## Kaçan mesajları geri alın Webhook teslimi kaçarsa `GET /messages` ile bir numaranın mesaj kayıtlarını eskiden yeniye okuyun (en fazla 90 gün geriye, kredi düşmez): ```bash curl "https://api.verimerkezi.app/wa/messages?phone_number_id=1234567890&since=2026-10-09T00:00:00Z&limit=100" \ -H "Authorization: Bearer $VM_API_KEY" ``` ## Konum mesajı Haritada açılan bir konum gönderin. `latitude` (−90…90) ve `longitude` (−180…180) zorunludur; sayı ya da sayısal metin olabilir. `name` ve `address` isteğe bağlıdır, en fazla 1000 karakter. ```bash curl -X POST https://api.verimerkezi.app/wa/messages \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "type": "location", "location": { "latitude": 41.0082, "longitude": 28.9784, "name": "Merkez Ofis", "address": "Örnek Mah. Örnek Cad. No:1, İstanbul" } }' ``` Hatalı değerde `422 invalid_location` döner; `error.field` hatalı alanı gösterir. ## Kişi kartı Bir ya da birden çok kişi kartı (en çok 20) gönderin. Her kartta `name.formatted_name` zorunludur. `phones[].wa_id` verirseniz alıcı kartta **Mesaj gönder** ve **Kişiyi kaydet** düğmelerini görür. ```bash curl -X POST https://api.verimerkezi.app/wa/messages \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "type": "contacts", "contacts": [{ "name": { "formatted_name": "Destek Hattı", "first_name": "Destek", "last_name": "Hattı" }, "phones": [{ "phone": "+905550000000", "type": "WORK", "wa_id": "905550000000" }], "emails": [{ "email": "destek@example.com", "type": "WORK" }] }] }' ``` Hatalı kartta `422 invalid_contacts` döner (ör. `field: contacts[0].name.formatted_name`). Konum ve kişi kartı serbest mesajdır: 24 saatlik pencere, 1 kredi ve test anahtarında simülasyon kuralları aynen geçerlidir. ## Etkileşimli mesajlar `type: "interactive"` ile yanıt düğmeleri (`button`, en çok 3), liste (`list`, en çok 10 satır), bağlantı düğmesi (`cta_url`) ve konum isteği (`location_request_message`) gönderin. Gövde Meta Cloud API ile aynıdır; Meta sınırları gönderimden önce doğrulanır. ```bash curl -X POST https://api.verimerkezi.app/wa/messages \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "type": "interactive", "interactive": { "type": "button", "body": { "text": "Yarın 14:00 randevunuzu onaylıyor musunuz?" }, "action": { "buttons": [ { "type": "reply", "reply": { "id": "onay", "title": "Onaylıyorum" } }, { "type": "reply", "reply": { "id": "degistir", "title": "Saati değiştir" } } ] } } }' ``` | Tür | Sınırlar | |---|---| | `button` | 1–3 düğme; `reply.title` ≤ 20, `reply.id` ≤ 256 (benzersiz). `body.text` ≤ 1024, `footer.text` ≤ 60. | | `list` | `action.button` ≤ 20; 1–10 bölüm, toplam 1–10 satır; satır `title` ≤ 24, `description` ≤ 72. `body.text` ≤ 4096. | | `cta_url` | `display_text` ≤ 20; `url` `http(s)://` ile başlar (≤ 2000). Dokunma olay üretmez. | | `location_request_message` | Yalnız `body.text` (≤ 1024). Yanıt `message.received` `type: location` olarak gelir. | Hatalı gövdede `422 invalid_interactive` döner; `error.field` hatalı alanı gösterir (ör. `interactive.action.buttons[1].reply.title`). Müşterinin seçimi `message.received` olayında `data.interactive.reply_id`, `reply_title` ve `reply_description` alanlarıyla gelir. ## İlgili - [Şablon mesajı gönder](referans/messages.html#sablon-mesaji-gonder) - [Hatalar](hatalar.html) - [Idempotency](idempotency.html) --- # Şablonlar WhatsApp mesaj şablonlarını API ile oluşturun, Meta'ya göndermeden doğrulayın, onay durumunu izleyin ve senkronlayın. Müşteriye 24 saat penceresi dışında ulaşmanın tek yolu Meta onaylı bir şablondur. Bu sayfa şablonları API ile nasıl oluşturacağınızı, doğrulayacağınızı ve yöneteceğinizi anlatır. ## Temel kavramlar - Şablonlar **işletme hesabı (WABA)** düzeyindedir. Aynı WABA'daki tüm numaralar aynı onaylı şablonları kullanır. - Yeni şablon Meta incelemesine gider ve `PENDING` durumunda başlar. Sonuç `template.approved` ya da `template.rejected` olayıyla bildirilir. - Kurallar Meta'ya gönderilmeden önce denetlenir; ihlaller `422 template_invalid` olarak hemen döner. | Kategori | Kullanım | |---|---| | `UTILITY` | Sipariş onayı, kargo, randevu, fatura bildirimi. | | `MARKETING` | Kampanya, indirim, duyuru. | | `AUTHENTICATION` | Doğrulama kodu (OTP). | Meta kategoriyi kendisi belirleyebilir; değiştirirse `template.category_changed` olayı gelir. | Durum | Anlamı | |---|---| | `DRAFT` | Taslak, Meta'ya gönderilmedi. | | `PENDING` | Meta inceliyor. | | `APPROVED` | Kullanıma hazır. | | `REJECTED` | Reddedildi; sebep `rejected_reason` alanında. | | `PAUSED` | Kalite nedeniyle geçici durduruldu. | | `DISABLED` | Devre dışı. | ## Şablon oluşturun ```bash cURL curl -X POST https://api.verimerkezi.app/wa/templates \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "police_yenileme_hatirlatma", "language": "tr", "category": "UTILITY", "header_media_url": "https://cdn.example.com/logo.jpg", "components": [ { "type": "HEADER", "format": "IMAGE" }, { "type": "BODY", "text": "Sayın {{1}}, {{2}} plakalı aracınızın poliçesi {{3}} tarihinde sona eriyor.", "example": { "body_text": [["Ahmet Yılmaz", "34 ABC 123", "12.10.2026"]] } }, { "type": "FOOTER", "text": "Firma Sigorta" }, { "type": "BUTTONS", "buttons": [ { "type": "QUICK_REPLY", "text": "Teklif istiyorum" } ] } ] }' ``` ```javascript Node.js const tpl = await vm.createTemplate({ name: 'police_yenileme_hatirlatma', language: 'tr', category: 'UTILITY', components: [ { type: 'BODY', text: 'Sayın {{1}}, poliçeniz {{2}} tarihinde sona eriyor.', example: { body_text: [['Ahmet', '12.10.2026']] } }, ], }); console.log(tpl.id, tpl.status); // PENDING ``` ```python Python tpl = vm.create_template({ 'name': 'police_yenileme_hatirlatma', 'language': 'tr', 'category': 'UTILITY', 'components': [ {'type': 'BODY', 'text': 'Sayın {{1}}, poliçeniz {{2}} tarihinde sona eriyor.', 'example': {'body_text': [['Ahmet', '12.10.2026']]}}, ], }) print(tpl['id'], tpl['status']) ``` ```php PHP createTemplate([ 'name' => 'police_yenileme_hatirlatma', 'language' => 'tr', 'category' => 'UTILITY', 'components' => [ ['type' => 'BODY', 'text' => 'Sayın {{1}}, poliçeniz {{2}} tarihinde sona eriyor.', 'example' => ['body_text' => [['Ahmet', '12.10.2026']]]], ], ]); echo $tpl['id']; ``` Yanıt `202 Accepted` döner ve şablon `PENDING` durumundadır. ### Hedef işletme hesabı - Hesabınızda tek WABA varsa hedef belirtmeniz gerekmez. - Birden çok WABA varsa `waba_id` gönderin; aksi hâlde `422 waba_required` döner. - `waba_id` yerine `phone_number_id` da verebilirsiniz; WABA numaradan çözülür. ### Medya başlığı Başlık `IMAGE`, `VIDEO` ya da `DOCUMENT` ise önce örnek dosyayı `POST /templates/media` ile yükleyin. Üç giriş biçimi vardır: `multipart/form-data` `file`, JSON `url` (herkese açık `https://`) ya da JSON `media_id` (`POST /media` ile yüklenmiş medya). ```bash curl -X POST https://api.verimerkezi.app/wa/templates/media \ -H "Authorization: Bearer $VM_API_KEY" \ -F file=@kampanya.jpg -F format=IMAGE ``` ```json { "ok": true, "header_handle": "4::aW1hZ2UvanBlZw==:ARb...", "header_media_ref": 8812, "format": "IMAGE", "mime_type": "image/jpeg", "file_size": 183244, "waba_id": "1029384756" } ``` Kabul edilen türler: `IMAGE` JPEG/PNG (en çok 5 MB), `VIDEO` MP4 (16 MB), `DOCUMENT` PDF (100 MB). Sonra şablonda `header_handle` değerini HEADER bileşeninin `example.header_handle[0]` alanına, `header_media_ref` değerini gövdenin üst düzeyine koyun: ```json { "header_media_ref": 8812, "components": [ { "type": "HEADER", "format": "IMAGE", "example": { "header_handle": ["4::aW1hZ2UvanBlZw==:ARb..."] } }, ... ] } ``` `header_handle` kısa ömürlüdür; hemen kullanın. `header_media_ref`, dosyanın Veri Merkezi'ndeki kopyasıdır: şablona bağlanınca panel ve kampanya gönderimlerinde başlık medyası otomatik bulunur. Eski `header_media_url` alanı çalışmaya devam eder. Hiçbiri yoksa `422 invalid_request` (`field: header_media_ref`) döner. ### Ad kuralları Şablon adında yalnız küçük harf, rakam ve alt çizgi kullanın (ör. `siparis_onayi`). Diğer karakterler `_` ile değiştirilir; büyük harfler de buna dahildir. ### Adlı değişkenler, başlık değişkeni ve yeni bileşenler - `parameter_format: "NAMED"` ile değişkenleri `{{ad_soyad}}` biçiminde adlandırın (küçük harf, rakam, `_`; harfle başlar; benzersiz). Örnekler `example.body_text_named_params` ve `example.header_text_named_params` içinde `[{ "param_name": "...", "example": "..." }]` olarak verilir. Varsayılan `POSITIONAL` biçimde örnekler `example.body_text` ve `example.header_text` içindedir. - Metin başlığı (`HEADER` `TEXT`) en çok 60 karakter ve en çok 1 değişken içerir; değişken varsa örnek zorunludur. - `LOCATION` başlığında oluştururken parametre yoktur (yalnız `UTILITY` / `MARKETING`); konum gönderimde verilir. - `COPY_CODE` düğmesi (en çok 1): `{ "type": "COPY_CODE", "example": "YENI2026" }`; düğme metnini Meta koyar. - Ek alanlar: `allow_category_change`, `message_send_ttl_seconds`, `cta_url_link_tracking_opted_out`. ```json { "name": "police_yenileme_hatirlatma", "language": "tr", "category": "UTILITY", "parameter_format": "NAMED", "components": [ { "type": "HEADER", "format": "TEXT", "text": "Poliçe {{police_no}}", "example": { "header_text_named_params": [ { "param_name": "police_no", "example": "TR-2026-001" } ] } }, { "type": "BODY", "text": "Sayın {{ad_soyad}}, {{bitis_tarihi}} tarihinde biten poliçeniz için teklifiniz hazır.", "example": { "body_text_named_params": [ { "param_name": "ad_soyad", "example": "Ayşe Yılmaz" }, { "param_name": "bitis_tarihi", "example": "30.10.2026" } ] } }, { "type": "BUTTONS", "buttons": [ { "type": "URL", "text": "Teklifi gör", "url": "https://ornek.com/teklif/{{1}}", "example": ["https://ornek.com/teklif/abc123"] }, { "type": "COPY_CODE", "example": "YENI2026" } ] } ] } ``` Adlı değişkenli, değişkenli metin başlıklı ya da `LOCATION` başlıklı şablonlar yalnız API ile (`POST /messages`, `components` ile) gönderilebilir; panel kampanya ve otomasyonları bunları "yalnız API" uyarısıyla engeller. ### Hatalar Doğrulama hataları (`422 invalid_request`, `422 template_invalid`) `error.field` alanında hatalı yolu verir (ör. `components[0].text`, `components[2].buttons[0].url`). Aynı WABA'da aynı ad ve dil varsa `409 template_exists`. Hata Meta'dan geldiyse `meta_code`, `meta_subcode`, `meta_message`, `meta_user_title`, `meta_user_message` ve `fbtrace_id` de döner; Meta'ya ulaşılamazsa `502 meta_unavailable`. ## Göndermeden doğrulayın Kullanıcı arayüzünde "gönder" düğmesinden önce kuralları denetlemek için `POST /templates/validate` kullanın. Meta'ya hiçbir şey gönderilmez. ```bash curl -X POST https://api.verimerkezi.app/wa/templates/validate \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "deneme", "language": "tr", "category": "UTILITY", "components": [ { "type": "BODY", "text": "Merhaba {{1}}", "example": { "body_text": [["Ahmet"]] } } ] }' ``` ```json { "ok": true, "valid": true, "message": "Şablon Meta kurallarına uygun görünüyor." } ``` ## Meta kuralları - Gövde en fazla 1024 karakter olabilir; değişkenler sıralı olmalıdır: `{{1}}`, `{{2}}`, `{{3}}`. - Her değişken için `example.body_text` içinde örnek değer verin. - Gövde yalnız değişkenden oluşamaz; sabit metin içermelidir. - Bileşen sırası `HEADER`, `BODY`, `FOOTER`, `BUTTONS` olmalıdır. Alt bilgi en fazla 60 karakterdir. - Genel ifadeler yerine markanızı ve bağlamı açıkça belirtin; yanıltıcı vaatler reddedilir. ## Dinamik URL butonu Her alıcıya kişiye özel bağlantı göndermek için URL butonunun adresini `{{1}}` ile bitirin ve tam bir örnek adres verin. Gönderimde değişken kısmı `button` bileşeniyle iletilir. Bkz. [Mesaj gönderme](mesaj-gonderme.html). ## Doğrulama kodu şablonu `AUTHENTICATION` kategorisinde gövde metnini Meta üretir; siz yalnız ayarları verirsiniz: | Alan | Açıklama | |---|---| | `add_security_recommendation` | "Bu kodu kimseyle paylaşmayın" satırı. | | `code_expiration_minutes` | Kodun geçerlilik süresi (dakika). | | `otp_type` | `COPY_CODE` (her cihazda kodu kopyalar) ya da `ONE_TAP` (Android uygulamanıza otomatik doldurur). | | `otp_button_text` | Buton metni. | | `autofill_text`, `package_name`, `signature_hash` | `ONE_TAP` için zorunlu. | ## Form butonu Şablona WhatsApp formu açan bir buton ekleyebilirsiniz: ```json { "type": "BUTTONS", "buttons": [ { "type": "FLOW", "text": "Formu doldur", "flow_id": "1234567890123456", "navigate_screen": "WELCOME", "flow_action": "navigate" } ] } ``` Ayrıntı: [Formlar](formlar.html). ## Listeleyin ve izleyin ```bash curl "https://api.verimerkezi.app/wa/templates?status=APPROVED&category=UTILITY&language=tr&limit=50" \ -H "Authorization: Bearer $VM_API_KEY" ``` - Her kayıtta `waba_id` ve şablonu kullanabilecek numaraları gösteren `phone_number_ids` döner. - Ret sebebini ve kalite puanını `GET /templates/{id}` ile görün. - Durum değişikliklerini `template.approved`, `template.rejected`, `template.flagged`, `template.paused`, `template.quality_changed` ve `template.category_changed` olaylarıyla izleyin. `template.disabled` ve `template.reinstated` olayları `*` aboneliğine dahil değildir; açıkça seçin. Durum olaylarında `previous_status`, `rejected_reason`, `rejected_reason_text`, `quality_score`, `occurred_at` ve `source` bulunur. - Liste satırında `quality_score`, `parameter_format`, `previous_category` ve `rejected_reason_text` döner; ayrıntıda ayrıca `header_media_ref` ve `edit_limit` vardır. Önizleme için `components` istemcide çizilir. ## Meta ile senkronlayın Şablonlar arka planda 15 dakikada bir Meta ile senkronlanır. Hemen güncellemek için: ```bash curl -X POST https://api.verimerkezi.app/wa/templates/sync \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890" }' ``` Senkron WABA başına dakikada 1 kez yapılabilir; sınırda `429 sync_rate_limited` döner. Listeyi okurken `GET /templates?refresh=1` aynı senkronu dener ve sınırdaysa hata vermeden mevcut listeyi `refreshed: false` ile döndürür. Senkron başarılıysa yanıtta `refreshed: true` ve `refreshed_at` (senkron zamanı) bulunur; başarısızsa `refreshed_at` `null` olur. ## Düzenleyin ve silin ### Düzenleme `PATCH /templates/{id}` (eşdeğeri `POST /templates/{id}`) yeni bileşenleri Meta onayına gönderir; durum yeniden `PENDING` olur. - Yalnız `APPROVED`, `REJECTED` ve `PAUSED` şablon düzenlenir; aksi `409 template_not_editable`. - Onaylı şablon 24 saatte 1, 30 günde en çok 10 kez düzenlenebilir ve kategorisi değişmez (`422 category_not_editable`). Reddedilen ve duraklatılan şablonlarda sınır yoktur, kategori değişebilir. Sınır dolunca `429 template_edit_limit` (`next_edit_at`, `retry_after`) döner. - Düzenleme TÜM bileşenleri değiştirir: gönderdiğiniz liste tam olmalıdır. - `name`, `language` ve `parameter_format` değişmez (`422 invalid_request`); yeni ad ya da dil için kopyalayın. - `GET /templates/{id}` yanıtındaki `edit_limit` (`editable`, `edits_last_30d`, `max_edits_30d`, `next_edit_at`) ile düğmeyi önceden kapatabilirsiniz. Yalnız Veri Merkezi üzerinden yapılan düzenlemeler sayılır; WhatsApp Manager'daki düzenlemeyi Meta `429` ile bildirir. ### Kopyalama `POST /templates/{id}/copy` yeni ad ve/veya dille kopya açar; `components` verirseniz çeviri için onlar kullanılır: ```bash curl -X POST https://api.verimerkezi.app/wa/templates/512/copy \ -H "Authorization: Bearer $VM_API_KEY" -H "Content-Type: application/json" \ -d '{ "name": "police_yenileme_hatirlatma_en", "language": "en_US" }' ``` Medya başlıklı kaynağın Veri Merkezi'nde kopyası yoksa (ör. başka bir sağlayıcıdan senkronlanmış) `422 invalid_request` (`field: header_media_ref`) döner; önce `POST /templates/media`, sonra `header_media_ref` ile tekrar deneyin. ### Silme - `DELETE /templates/{id}` yalnız o dili siler. - `DELETE /templates?name=police_yenileme_hatirlatma` adın tüm dillerini siler (`ids` ve `languages` döner). Birden çok WABA'nız varsa `waba_id` ekleyin. - Silinen onaylı şablonun adı 30 gün yeniden kullanılamaz; `DISABLED` şablon silinemez. Meta'da zaten yoksa yerel kayıt silinir (`meta_missing: true`). Geçmiş mesajlar etkilenmez. ### Önerilen akış 1. Liste: `GET /templates` (gerekirse `?refresh=1`). 2. Yeni: medya varsa `POST /templates/media` → `POST /templates/validate` (`field` ile ilgili kutuyu işaretleyin) → `POST /templates`. 3. Düzenle: `GET /templates/{id}` → `edit_limit.editable` kapalıysa `next_edit_at` gösterin → `PATCH /templates/{id}`. 4. Durumu olaylarla izleyin; olay kaçarsa `POST /templates/sync`. Meta kuralları: [Şablonlar](https://developers.facebook.com/docs/whatsapp/business-management-api/message-templates/) · [Bileşenler](https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/components) · [Yönetim (düzenle / sil)](https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/template-management) · [API başvurusu](https://developers.facebook.com/documentation/business-messaging/whatsapp/reference/whatsapp-business-account/message-template-api) · [Resumable Upload](https://developers.facebook.com/docs/graph-api/guides/upload) · [Medya türleri](https://developers.facebook.com/documentation/business-messaging/whatsapp/business-phone-numbers/media) Tüm alanlar: [Şablonlar referansı](referans/templates.html). --- # Medya Gönderim için dosyayı Meta'ya yükleyin, medya mesajı ve sesli not gönderin, müşterilerinizin gönderdiği medyayı güvenle indirin. Medya ile iki yönde çalışırsınız: göndereceğiniz dosyayı Meta'ya yüklersiniz ve müşterinizin gönderdiği dosyayı indirirsiniz. Bu iki işlem farklı kimlikler kullanır. | İşlem | Uç | Kimlik | |---|---|---| | Gönderim için yükleme | `POST /media` | Meta medya kimliği (metin, 30 gün geçerli) | | Gelen medyayı indirme | `GET /media/{media_id}` | Veri Merkezi depo kimliği (sayı, süresiz) | ## Dosya yükleyin Dosyayı herkese açık bir adreste yayınlamadan doğrudan Meta'ya yükleyin. İstek `multipart/form-data` olmalıdır. ```bash cURL curl -X POST https://api.verimerkezi.app/wa/media \ -H "Authorization: Bearer $VM_API_KEY" \ -F "phone_number_id=1234567890" \ -F "file=@police.pdf" ``` ```javascript Node.js const yuklenen = await vm.uploadMedia('1234567890', './police.pdf'); console.log(yuklenen.media_id); ``` ```python Python yuklenen = vm.upload_media('1234567890', './police.pdf') print(yuklenen['media_id']) ``` ```php PHP uploadMedia('1234567890', './police.pdf'); echo $yuklenen['media_id']; ``` ```json { "ok": true, "media_id": "1075907555339682", "type": "document", "mime_type": "application/pdf", "file_size": 183421, "phone_number_id": "1234567890", "expires_at": "2026-11-09T09:00:00Z", "mode": "live" } ``` Dönen kimliği 30 gün içinde gönderimde kullanın: ```json { "phone_number_id": "1234567890", "to": "905550000000", "type": "document", "document": { "id": "1075907555339682", "filename": "police.pdf" } } ``` ### Sınırlar | Tür | Biçimler | En fazla | |---|---|---| | Görsel | JPEG, PNG | 5 MB | | Çıkartma | WebP | 500 KB | | Video | MP4, 3GPP | 16 MB | | Ses | AAC, AMR, MP3, MP4, OGG | 16 MB | | Belge | PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT | 100 MB | - Dosyanın gerçek içeriği denetlenir; uzantısı yanıltıcıysa `415 media_invalid_format` döner. - Sınırı aşan dosya `413 media_too_large` döner. - Yükleme, medya hız sayacında (dakikada 60) sayılır. - Test anahtarıyla Meta'ya istek gitmez; `test.media.` önekli bir kimlik döner. ## Sesli not gönderin Sesi WhatsApp sesli notu (mikrofon simgeli) olarak göndermek için dosya OGG/Opus olmalıdır: ```json { "phone_number_id": "1234567890", "to": "905550000000", "type": "audio", "audio": { "id": "1138345975302799", "voice": true } } ``` `voice` alanını göndermezseniz `.ogg`/`.opus` ile biten bağlantılar ve Meta'da `audio/ogg` olarak kayıtlı medya otomatik sesli not olarak gider. MP3, M4A, AAC ve AMR normal ses dosyası olarak gider. ## Gelen medyayı indirin Müşteri görsel, video, ses, belge ya da çıkartma gönderdiğinde dosya sizin için hemen depolanır ve `message.received` olayında `data.media.media_id` olarak bildirilir: ```json "media": { "media_id": 13109, "path": "wa-media/inbound/2026/10/kgI9iKVlvNw3yFpXrzNVL8w2T023kEaznP3z5NHD.jpeg" } ``` `path` yalnız bilgi amaçlıdır; doğrudan istenemez. Dosyayı kimlikle indirin: ```bash cURL curl -L https://api.verimerkezi.app/wa/media/13109 \ -H "Authorization: Bearer $VM_API_KEY" \ -o gelen-gorsel.jpeg ``` ```javascript Node.js const fs = require('fs'); await vm.downloadMedia(13109, fs.createWriteStream('gelen-gorsel.jpeg')); ``` ```python Python vm.download_media(13109, 'gelen-gorsel.jpeg') ``` ```php PHP downloadMedia(13109, '/tmp/gelen-gorsel.jpeg'); ``` - Gerekli kapsam: `inbox:read` ya da `messages:read`. - Yalnız kendi hesabınızın medyasına erişirsiniz. Başka hesabın kimliği `404 media_not_found` döner; kaydın varlığı bile sızdırılmaz. - Dosya Meta'dan alınamadıysa `media` alanı `{ "error": "media_download_failed", "meta_media_id": "..." }` biçiminde gelir; bu durumda indirilecek dosya yoktur. - Medya dosyaları süresiz saklanır. ### Üstveri, büyük dosyalar ve önbellek - `GET /media/{media_id}?meta=1` dosya yerine boyut, tür ve SHA-256 bilgisini JSON olarak döndürür. - `HEAD /media/{media_id}` dosyayı aktarmadan `Content-Length` verir. - `Range: bytes=0-1048575` başlığıyla parça parça indirin (`206 Partial Content`); yarıda kalan indirmeyi sürdürün. - Yanıttaki `ETag` değerini `If-None-Match` ile gönderin; dosya değişmediyse `304 Not Modified` döner. ### Medya kayıtlarını listeleyin ```bash curl "https://api.verimerkezi.app/wa/media?source=inbound&kind=image&limit=50" \ -H "Authorization: Bearer $VM_API_KEY" ``` `source` için `inbound`, `outbound` ya da `echo`; `kind` için `image`, `video`, `document`, `audio`, `sticker` kullanın. İmleç `paging.next_cursor` alanındadır. > **Not:** Medya indirme ve yükleme, diğer uçlardan ayrı bir sayaçta dakikada 60 istekle sınırlıdır. Tüm alanlar: [Medya referansı](referans/media.html). --- # Kullanıcı adı ve BSUID Telefon numarası görünmeyen WhatsApp kullanıcılarını user_id (BSUID) ile tanıyın ve recipient alanıyla yanıtlayın. WhatsApp, kullanıcıların telefon numarası yerine kullanıcı adıyla iletişim kurabildiği bir modele geçiyor. Bu kullanıcılarda telefon numarası size iletilmeyebilir. Bu sayfa böyle kullanıcıları nasıl tanıyacağınızı ve onlara nasıl yanıt vereceğinizi anlatır. ## Kavramlar | Alan | Anlamı | |---|---| | `from` | Gönderenin telefon numarası. Telefonu görünmeyen kullanıcıda `null` gelir. | | `user_id` | WhatsApp kullanıcı kimliği (BSUID). Telefon `null` ise kullanıcıyı bu alanla tanırsınız. | | `username` | Kullanıcının WhatsApp kullanıcı adı (varsa). Meta özelliği kademeli açtığı için şu an çoğunlukla `null` gelir. | ## Gelen mesajda tanıma ```json { "event": "message.received", "data": { "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhggQTNFQkQ2RjQ3MEIyQzk0RTE4AA==", "from": null, "user_id": "TR.1234567890123456", "username": "ayse.yilmaz", "name": "Ayşe", "type": "text", "text": "Merhaba", "phone_number_id": "1234567890", "contact": { "wa_id": null, "user_id": "TR.1234567890123456", "username": "ayse.yilmaz", "profile_name": "Ayşe" } } } ``` > **İpucu:** Müşteri kaydınızı eşleştirirken önce telefonu, telefon yoksa `user_id` değerini anahtar olarak kullanın. `username` değişebilir; kalıcı kimlik olarak kullanmayın. `user_id` değerinin biçimi Meta tarafından belirlenir; yukarıdaki değer örnektir. Değeri olduğu gibi saklayın ve geri gönderin. ## Yanıt verme Telefonu olmayan kullanıcıya yanıt verirken `to` yerine `recipient` alanını kullanın. Diğer tüm alanlar aynıdır. ```bash cURL curl -X POST https://api.verimerkezi.app/wa/messages \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "recipient": "TR.1234567890123456", "type": "text", "text": { "body": "Merhaba Ayşe Hanım, nasıl yardımcı olabiliriz?" } }' ``` ```javascript Node.js await fetch('https://api.verimerkezi.app/wa/messages', { method: 'POST', headers: { Authorization: `Bearer ${process.env.VM_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ phone_number_id: '1234567890', recipient: 'TR.1234567890123456', type: 'text', text: { body: 'Merhaba Ayşe Hanım, nasıl yardımcı olabiliriz?' }, }), }); ``` ```python Python import os, requests requests.post( "https://api.verimerkezi.app/wa/messages", headers={"Authorization": f"Bearer {os.environ['VM_API_KEY']}"}, json={"phone_number_id": "1234567890", "recipient": "TR.1234567890123456", "type": "text", "text": {"body": "Merhaba Ayşe Hanım, nasıl yardımcı olabiliriz?"}}, ) ``` ```php PHP true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('VM_API_KEY'), 'Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode([ 'phone_number_id' => '1234567890', 'recipient' => 'TR.1234567890123456', 'type' => 'text', 'text' => ['body' => 'Merhaba Ayşe Hanım, nasıl yardımcı olabiliriz?'], ]), ]); curl_exec($ch); ``` - `recipient` yalnız `to` boşken kullanılır. Eş anlamlıları `recipient_id` ve `bsuid` alanlarıdır. - `recipient` ile gönderimde telefon biçimi denetimi yapılmaz. - Şablon, medya, tepki ve form mesajları da `recipient` ile gönderilebilir. - 24 saat penceresi ve diğer kurallar aynen geçerlidir. ## Diğer yerlerde BSUID - **Mesaj kayıtları:** `GET /messages` yanıtında her kayıtta `user_id` ve `username` döner. - **Durum olayları:** `message.status.*` olaylarında karşı tarafın BSUID değeri `user_id` alanındadır. - **Coexistence:** `message.echo` olayında `user_id`, müşterinin BSUID değeridir. - **KVKK silme:** `POST /privacy/erasure` isteğinde telefon yerine `user_id` gönderebilirsiniz. > **Uyarı:** Meta kullanıcı adı özelliğini kademeli olarak açmaktadır. `recipient` ile gönderimi üretime almadan önce kendi hesabınızda gerçek bir kullanıcıyla deneyin. --- # Formlar (WhatsApp Flows) WhatsApp içinde açılan çok ekranlı formları oluşturun, yayınlayın, müşteriye gönderin ve yanıtları webhook ile alın. Formlar (WhatsApp Flows), müşterinin sohbetten çıkmadan doldurduğu çok ekranlı arayüzlerdir: başvuru, randevu, anket, iletişim formu. Form tasarımı Meta'nın Flow JSON biçimindedir. Müşteri formu gönderdiğinde yanıt `flow.completed` olayıyla gelir. ## Akış 1. `POST /flows` ile formu `DRAFT` olarak oluşturun. 2. `validation_errors` boş değilse `PUT /flows/{flow_id}/json` ile düzeltin. 3. Taslağı kendinize `POST /messages` ve `flow.mode: "draft"` ile göndererek deneyin. 4. `POST /flows/{flow_id}/publish` ile yayınlayın. Yayınlanan form değiştirilemez. 5. Müşteriye form mesajı ya da form butonlu şablon gönderin. 6. Yanıtı `flow.completed` olayıyla alın. Formlar işletme hesabı (WABA) düzeyindedir; `phone_number_id` ile numaranızın WABA'sı seçilir. Form yönetimi kredi düşmez; form mesajı normal mesaj gibi 1 kredidir. Gerekli kapsamlar: `flows:read`, `flows:write`. ## Form oluşturun ```bash cURL curl -X POST https://api.verimerkezi.app/wa/flows \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "phone_number_id": "1234567890", "name": "Teklif formu", "categories": ["LEAD_GENERATION"], "flow_json": { "version": "7.2", "screens": [] } }' ``` ```javascript Node.js const form = await vm.createFlow('1234567890', 'Teklif formu', ['LEAD_GENERATION'], flowJson); console.log(form.flow_id, form.validation_errors); ``` ```python Python form = vm.create_flow('1234567890', 'Teklif formu', ['LEAD_GENERATION'], flow_json) print(form['flow_id'], form['validation_errors']) ``` ```php PHP createFlow('1234567890', 'Teklif formu', ['LEAD_GENERATION'], $flowJson); echo $form['flow_id']; ``` ```json { "ok": true, "flow_id": "1234567890123456", "status": "DRAFT", "validation_errors": [] } ``` - `categories` en az bir değer içermelidir: `SIGN_UP`, `SIGN_IN`, `APPOINTMENT_BOOKING`, `LEAD_GENERATION`, `CONTACT_US`, `CUSTOMER_SUPPORT`, `SURVEY`, `OTHER`. - `flow_json` nesne ya da JSON metni olabilir; en fazla 10 MB. - Form yazma uçları test anahtarıyla çalışmaz (`403 test_mode_unsupported`). > **İpucu:** Canlıda denenmiş tek ekranlı bir örnek form GitHub deposunda bulunur: `examples/flows/teklif-formu.flow.json`. ### Koşullu alanlar (If) — Meta kuralları Meta'nın If bileşeninde (`condition`) `==` ve `!=` için iki taraf aynı türde olmalıdır. Metin değişmezi tek tırnakla (`'2'`), sayı tırnaksız yazılır. `>`, `>=`, `<`, `<=` yalnız sayılarla çalışır. İç içe If en fazla 3 seviyedir ([Meta: If bileşeni](https://developers.facebook.com/docs/whatsapp/flows/reference/components#if)). Canlı Meta testlerinde (14.10.2026) ek kısıtlar gözlendi: - **Bir ifadede tek eşitlik.** `(${form.arac} == '2') || (${form.arac} == '3')` reddedilir. Meta bunu `((${form.arac} == '2') || ${form.arac}) == '3'` diye çözer ve `Type mismatch in an equality operation` hatası verir. - Çalışan biçimler: tek karşılaştırma (`${form.arac} == '2'`), mantıksal değerle birleşim (`${form.onay} || (${form.arac} == '2')`), sayısal aralık (`(${form.yas} > 2) || (${form.yas} < 1)`). - Birden çok değeri kontrol etmek için koşulu iç içe If'lere bölün (en fazla 3 seviye) ya da her değer için ayrı bir If yazın. - Switch bileşeni aynı testlerde `Cannot read property 'type'` hatası verdi. Şimdilik If kullanın. Meta bu hatayla reddettiğinde yanıta `hint` ve `hint_message` eklenir. Başarılı yanıtta (`validation_errors`) bu alanlar üst düzeyde, `422 meta_flow_failed` yanıtında ise `error` içinde yer alır. Flow JSON'unuz değiştirilmez. | `hint` | Anlamı | |---|---| | `flow_chained_equality` | Bir ifadede birden fazla eşitlik var. Koşulu bölün. | | `crm_update_required` | İstek bir Sigorta CRM hizmetine bağlı anahtardan geldi. CRM yazılımını güncel sürüme yükseltin. | ## Form mesajı gönderin ```json { "phone_number_id": "1234567890", "to": "905550000000", "type": "flow", "flow": { "flow_id": "1234567890123456", "flow_token": "crm-basvuru-42", "cta": "Formu Aç", "body": "Teklif için formu doldurun.", "screen": "WELCOME" } } ``` | Alan | Açıklama | |---|---| | `flow_id` ya da `flow_name` | Yalnız biri. | | `flow_token` | Kendi eşleştirme anahtarınız (en fazla 191 bayt). Verilmezse üretilir ve yanıtta döner. | | `cta` | Buton metni; en fazla 20 karakter, emoji yok. | | `body` | Gövde; en fazla 1024 karakter. `header` ve `footer` isteğe bağlı, en fazla 60 karakter. | | `mode` | `published` (varsayılan) ya da `draft`. | | `action` | `navigate` (varsayılan) ya da `data_exchange` (yalnız Endpoint'li formlar). | | `screen`, `data` | Açılış ekranı ve başlangıç verisi (`navigate` için). | Serbest mesaj kuralları geçerlidir: 24 saat penceresi kapalıyken form butonlu bir şablon kullanın. ## Şablonla gönderin Şablonu `POST /templates` ile `FLOW` butonuyla oluşturun. Gönderimde buton parametresi: ```json { "type": "button", "sub_type": "flow", "index": 0, "parameters": [ { "type": "action", "flow_token": "crm-basvuru-42", "flow_action_data": { "ad": "Ahmet" } } ] } ``` ## Yanıtı alın Müşteri formu gönderdiğinde `flow.completed` olayı gelir. Aynı mesaj ayrıca `message.received` (`type: interactive`) olarak da gelir. ```json { "event": "flow.completed", "data": { "event_id": "flow_7f3c2a91", "phone_number_id": "1234567890", "from": "905550000000", "flow_token": "crm-basvuru-42", "flow_id": "1234567890123456", "response": { "flow_token": "crm-basvuru-42", "ad": "Ayşe Yılmaz", "brans": "trafik" }, "media": [], "timestamp": "1791619800" } } ``` - `response`, form tasarımınızdaki alan adlarıyla ayrıştırılmış yanıttır. - Formdaki fotoğraf ve belgeler sunucumuzda indirilir; `media[].media_id` ile `GET /media/{media_id}` üzerinden alınır. - `data.event_id` aynı yanıt için sabittir; yinelenenleri bununla ayıklayın. - `flow.completed` ve `flow.status_changed` olayları `*` aboneliğine dahil değildir; açıkça seçin. ## Yönetim | İşlem | Uç | |---|---| | Listele (`limit` ≤ 200, `next_cursor` / `has_more`) | `GET /flows?phone_number_id=...` | | Ayrıntı | `GET /flows/{flow_id}` | | JSON güncelle (yalnız DRAFT) | `PUT /flows/{flow_id}/json` | | Yayınla | `POST /flows/{flow_id}/publish` | | Kullanımdan kaldır | `POST /flows/{flow_id}/deprecate` | | Sil (yalnız DRAFT) | `DELETE /flows/{flow_id}` | | Önizleme bağlantısı | `GET /flows/{flow_id}/preview` | | Metrikler (Endpoint'li formlar) | `GET /flows/{flow_id}/metrics` | Meta'nın yönetim arayüzünde oluşturduğunuz formlar için `{flow_id}` uçlarına `?phone_number_id=` ekleyin; form hesabınıza bağlanır. > **Not:** Meta'nın Endpoint (`data_exchange`) özelliğini kullanan formlarda şifreli Endpoint trafiğini Veri Merkezi karşılamaz; kendi sunucunuz karşılar. Tüm alanlar: [Formlar referansı](referans/flows.html). --- # Aramalar WhatsApp sesli aramalarını açın, gelen aramayı kabul edin, izin veren müşteriyi arayın ve arama olaylarını izleyin. WhatsApp Cloud API ile müşterilerinizden sesli arama alabilir ve izin veren müşterileri arayabilirsiniz. Ses, sizin WebRTC ucunuz ile Meta arasında doğrudan akar. Veri Merkezi yalnız sinyal verisini (SDP) iletir; sesi taşımaz ve kaydetmez. ## Ön koşullar - Numara yalnız API'ye bağlı olmalıdır. Telefondaki WhatsApp Business uygulamasıyla birlikte kullanılan (coexistence) numaralarda Meta aramayı desteklemez. - Günlük mesajlaşma limiti en az 2.000 kişi olmalıdır. - Müşteriyi aramak için WhatsApp hesabında geçerli bir ödeme yöntemi bulunmalıdır. Müşterinin sizi araması ücretsizdir. - İşletme başlatmalı arama bazı ülke kodlarında kullanılamaz (ABD/Kanada +1, Mısır +20, Vietnam +84, Nijerya +234). `GET /numbers/{phone_number_id}/calling` yanıtındaki `eligibility` alanı bu koşulları Türkçe açıklamalarla kontrol eder. ## Aramayı açın ```bash curl -X PATCH https://api.verimerkezi.app/wa/numbers/1234567890/calling \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "enabled": true, "callback_permission": true, "call_icon_visibility": "DEFAULT", "call_hours": { "status": "ENABLED", "timezone_id": "Europe/Istanbul", "weekly_operating_hours": [ { "day_of_week": "MONDAY", "open_time": "0900", "close_time": "1800" } ] } }' ``` Yalnız gönderdiğiniz alanlar değişir. Numara coexistence ise ya da limit 2.000'in altındaysa istek Meta'ya gitmeden `422` döner ve hata gövdesinde `eligibility` bulunur. ## Gelen arama 1. Müşteri aradığında `call.connect` olayı gelir: `direction: "user_initiated"` ve `sdp_offer` dolu. 2. WebRTC ucunuzda teklifi uygulayıp bir SDP yanıtı üretin. 3. İsteğe bağlı olarak bağlantıyı önceden kurmak için `pre_accept`, ardından `accept` çağırın. 4. Görüşme bitince `call.terminate` olayı gelir. Açmazsanız arama cevapsız olur. ```bash curl -X POST "https://api.verimerkezi.app/wa/calls/wacid.HBgLOTA1NTUxMjM0NTY3FQIAEhggQkE1/accept" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sdp_answer": "v=0\r\no=- 4611731400430051336 2 IN IP4 127.0.0.1\r\n..." }' ``` Reddetmek için `POST /calls/{call_id}/reject`, kapatmak için `POST /calls/{call_id}/terminate` kullanın. `call_id` değerini URL-kodlayın; içinde `/` varsa yol yerine `_` yazıp gövdede `call_id` gönderin. Her eylem arama kimliği ve eylem bazında idempotenttir: aynı eylemi tekrar gönderirseniz ilk sonuç `Idempotent-Replay: true` ile döner. ## Müşteriyi arayın Önce müşteriden arama izni alın: ```bash curl -X POST https://api.verimerkezi.app/wa/calls/permission-request \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "body": "Siparişiniz hakkında sizi arayabilir miyiz?" }' ``` - Yanıt `call.permission_reply` olayıyla gelir. - İzin durumunu `GET /calls/permission?phone_number_id=...&to=...` ile sorgulayın: `no_permission`, `temporary` (7 gün) ya da `permanent`. - Aynı kişiye 24 saatte 1, 7 günde 2 izin isteği gönderilebilir. İstek, 24 saat penceresi açıkken gider. - İzin isteği 1 mesaj kredisi düşer; başarısızsa iade edilir. İzin varsa aramayı başlatın: ```bash curl -X POST https://api.verimerkezi.app/wa/calls \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "sdp_offer": "v=0\r\n...", "biz_opaque": "crm-ticket-42" }' ``` Ardından `call.status` (`ringing`, `accepted`, `rejected`) ve Meta'nın yanıtıyla `call.connect` (`sdp_answer`) gelir. Başlattığınız aramada yalnız `terminate` eylemi kullanılabilir. ## Ücretlendirme - Arama eylemleri mesaj kredisi düşmez. - İşletme başlatmalı aramaları Meta 6 saniyelik dilimlerle doğrudan faturalar. `billable_pulses` alanı yalnız bilgi amaçlıdır. - Müşterinin sizi araması ücretsizdir. ## Gizlilik - SDP (IP ve ICE bilgisi) `call.connect` olayının ilk teslimatında tam gider; teslimden sonra, en geç 1 saat içinde kayıtlarda maskelenir. Yeniden teslim edilen `call.connect` olayında SDP yoktur. - `POST /privacy/erasure`, arama kayıtlarındaki kişi numarasını da siler. ## Geçmiş ```bash curl "https://api.verimerkezi.app/wa/calls?phone_number_id=1234567890&limit=20" \ -H "Authorization: Bearer $VM_API_KEY" ``` Süzgeçler: `phone_number_id`, `from` / `to` (ISO 8601 tarih aralığı), `peer`, `limit`, `cursor`. > **Not:** Test anahtarıyla tüm arama eylemleri Meta'ya gitmeden simüle edilir. Arama olayları (`call.*`) `*` aboneliğine dahil değildir; açıkça seçin. Tüm alanlar ve hata kodları: [Aramalar referansı](referans/calls.html). --- # Webhooklar Gelen mesajları, teslim durumlarını ve hesap olaylarını sunucunuzda alın; aboneliği kurun, imzayı doğrulayın, yeniden denemeleri yönetin. Webhooklar, WhatsApp tarafında olan olayları anında sunucunuza bildirir: müşteriden gelen mesaj, teslim ve okunma durumları, şablon onayı, numara ve kredi uyarıları. Bu sayfa aboneliği kurmayı, imzayı doğrulamayı ve teslimleri güvenilir biçimde işlemeyi anlatır. ## Nasıl çalışır 1. Bir olay gerçekleşir (ör. müşteri mesaj gönderir). 2. Veri Merkezi olayı abone olduğunuz adrese `POST` ile gönderir. Meta kaynaklı olaylarda (mesaj, durum, arama, form) gecikme genellikle 1 saniyenin altındadır; Meta dışından tetiklenen olaylar (`credit.*`, `number.detached`, `number.purged`, panelden `contact.opt_out_changed`, kampanya ya da otomasyon `message.sent`, `privacy.erasure_completed`) dakikalık kuyrukla en geç yaklaşık 1 dakikada gider. 3. Sunucunuz 10 saniye içinde `2xx` döndürür. 4. `2xx` dönmezse teslim artan aralıklarla yeniden denenir. ## 1. Alıcı uç noktanızı hazırlayın Uç noktanız: - Herkese açık bir `https://` adres olmalıdır. İç ağ, özel, loopback ve link-local adresler reddedilir. - Yönlendirme (`3xx`) yapmamalıdır; yönlendirmeler izlenmez. - Ham istek gövdesini imza doğrulaması için ayrıştırmadan okuyabilmelidir. ## 2. Abonelik oluşturun ```bash cURL curl -X POST https://api.verimerkezi.app/wa/webhooks \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://api.example.com/wa-webhook", "events": ["message.received", "message.status.delivered", "message.status.read", "message.status.failed", "template.approved", "template.rejected"], "description": "Üretim botu" }' ``` ```javascript Node.js const hook = await vm.createWebhook( 'https://api.example.com/wa-webhook', ['message.received', 'message.status.delivered', 'message.status.failed', 'credit.low'], 'Üretim botu' ); console.log(hook.secret); // whsec_... yalnız şimdi döner ``` ```python Python hook = vm.create_webhook( 'https://api.example.com/wa-webhook', ['message.received', 'message.status.delivered', 'message.status.failed', 'credit.low'], 'Üretim botu', ) print(hook['secret']) # yalnız şimdi döner ``` ```php PHP createWebhook( 'https://api.example.com/wa-webhook', ['message.received', 'message.status.delivered', 'message.status.failed', 'credit.low'], 'Üretim botu' ); echo $hook['secret']; // yalnız şimdi döner ``` > **Uyarı:** İmza anahtarı (`secret`, `whsec_` ile başlar) **yalnız oluşturma yanıtında** döner. Hemen güvenli bir yere kaydedin. Kaybederseniz `POST /webhooks/{id}/rotate-secret` ile yeni anahtar alın. - Aynı adrese ikinci bir aktif abonelik oluşturulamaz (`409 webhook_url_exists`); mevcut aboneliği `PATCH` ile güncelleyin. - Bilinmeyen olay adı ya da boş liste `422 invalid_webhook` döner. ### `*` (tüm olaylar) seçeneği `events` alanını göndermezseniz ya da `["*"]` gönderirseniz abonelik, o anki **12 klasik olaya** genişletilerek kaydedilir. Sonradan eklenen olaylar bu aboneliğe kendiliğinden gelmez. Aşağıdaki tabloda "Hayır" ile işaretli olayları açıkça seçmelisiniz. ## 3. İmzayı doğrulayın Her istek şu başlıkları taşır: ``` Content-Type: application/json X-VeriMerkezi-Signature-256: sha256=5d2a...e91f X-VeriMerkezi-Timestamp: 1791619200 X-VeriMerkezi-Event-Id: 01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb00 X-VeriMerkezi-Event-Type: message.received X-VeriMerkezi-Delivery-Attempt: 1 User-Agent: VeriMerkezi-Webhook/1.0 ``` İmza şu formülle üretilir: ``` sha256= + HMAC_SHA256(anahtar, zaman_damgasi + "." + ham_govde) ``` Doğrulama adımları: 1. Ham gövdeyi okuyun. 2. `X-VeriMerkezi-Timestamp` 5 dakikadan eskiyse isteği reddedin. 3. Beklenen imzayı hesaplayın ve başlıkla sabit zamanlı karşılaştırın. ```javascript Node.js const crypto = require('crypto'); const express = require('express'); const app = express(); app.post('/wa-webhook', express.raw({ type: 'application/json' }), (req, res) => { const secret = process.env.VM_WEBHOOK_SECRET; const ts = req.header('X-VeriMerkezi-Timestamp') || ''; const sig = req.header('X-VeriMerkezi-Signature-256') || ''; const body = req.body.toString('utf8'); if (Math.abs(Date.now() / 1000 - parseInt(ts, 10)) > 300) return res.status(401).end(); const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(ts + '.' + body).digest('hex'); const a = Buffer.from(expected), b = Buffer.from(sig); if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.status(401).end(); const event = JSON.parse(body); kuyrugaEkle(event); // işi arka plana alın res.status(200).json({ ok: true }); }); ``` ```python Python import hmac, hashlib, os, time, json from flask import Flask, request, abort app = Flask(__name__) SECRET = os.environ['VM_WEBHOOK_SECRET'].encode() @app.post('/wa-webhook') def webhook(): ts = request.headers.get('X-VeriMerkezi-Timestamp', '') sig = request.headers.get('X-VeriMerkezi-Signature-256', '') body = request.get_data(as_text=True) if not ts.isdigit() or abs(time.time() - int(ts)) > 300: abort(401) expected = 'sha256=' + hmac.new(SECRET, f'{ts}.{body}'.encode(), hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, sig): abort(401) kuyruga_ekle(json.loads(body)) return {'ok': True} ``` ```php PHP 300) { http_response_code(401); exit; } $expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, $secret); if (!hash_equals($expected, $sig)) { http_response_code(401); exit; } kuyrugaEkle(json_decode($body, true)); http_response_code(200); echo json_encode(['ok' => true]); ``` Resmî SDK'larda hazır doğrulama yardımcısı vardır: Node.js `VeriMerkeziClient.verifyWebhookSignature(secret, body, signature, timestamp)`, Python `VeriMerkeziClient.verify_webhook_signature(...)`. ## Olay yapısı Her olay aynı zarfı kullanır: ```json { "event": "message.received", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb00", "event_type": "message.received", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhggQTNFQkQ2RjQ3MEIyQzk0RTE4AA==", "from": "905550000000", "user_id": null, "username": null, "name": "Ayşe Yılmaz", "type": "text", "text": "Merhaba, siparişim ne durumda?", "media": null, "timestamp": "1791619200", "phone_number_id": "1234567890", "contact": { "wa_id": "905550000000", "user_id": null, "username": null, "profile_name": "Ayşe Yılmaz" } } } ``` | Alan | Açıklama | |---|---| | `event` | Olay adı. | | `event_id` | Olayın benzersiz kimliği (UUIDv7). Yeniden denemelerde aynı kalır. | | `event_type`, `created_at` | `event` ve `occurred_at` alanlarının eş anlamlıları (geriye dönük uyumluluk). | | `occurred_at` | Olay zamanı, saat dilimi ofsetli ISO 8601. | | `data` | Olaya özgü içerik. | `data.text` düz metindir; medyalı mesajda açıklamayı taşır. Dosya `data.media.media_id` ile indirilir. Buton yanıtlarında `data.button` alanı `{ payload, text }` olarak dolar. Etkileşimli mesajlarda `data.interactive` alanı `{ type, name }` taşır (ör. `button_reply`, `list_reply`; form yanıtında `{ "type": "nfm_reply", "name": "flow" }`); düğme ve liste seçiminde ayrıca `reply_id`, `reply_title` ve (yalnız listede) `reply_description` gelir. Diğer mesajlarda `null`dır. ## Olaylar | Olay | Ne zaman | `*` kapsar | |---|---|---| | `message.received` | Müşteri mesaj gönderdi. | Evet | | `message.echo` | Coexistence: işletme telefondaki uygulamadan yazdı. | Evet | | `message.status.sent` | Meta gönderimi kabul etti. | Evet | | `message.status.delivered` | Mesaj teslim edildi. | Evet | | `message.status.read` | Mesaj okundu. | Evet | | `message.status.failed` | Gönderim başarısız; `errors` alanına bakın. | Evet | | `template.approved` | Şablon onaylandı. | Evet | | `template.rejected` | Şablon reddedildi; `reason` / `rejected_reason` ve `rejected_reason_text` alanlarına bakın. | Evet | | `template.flagged` | Şablon işaretlendi. | Evet | | `template.paused` | Şablon durduruldu. | Evet | | `quality.changed` | Numara kalite puanı değişti. | Evet | | `account.alert` | Meta hesap uyarısı. | Evet | | `message.revoked` | Bir mesaj silindi. | Hayır | | `message.edited` | Bir mesaj düzenlendi. | Hayır | | `message.sent` | API dışından mesaj gönderildi (panel, otomasyon, kampanya). | Hayır | | `message.history` | Coexistence geçmiş aktarımından eski mesaj. | Hayır | | `number.status_changed` | Numara bağlandı, koptu, işaretlendi ya da kısıtlandı. | Hayır | | `number.name_changed` | Görünen ad için Meta kararı geldi. | Hayır | | `number.detached` | Numara hesabınızdan ayrıldı. | Hayır | | `number.purged` | Ayrılan numaranın kayıt silmesi tamamlandı. | Hayır | | `credit.low` | Kredi eşiğin altına düştü. | Hayır | | `credit.exhausted` | Kredi tükendi. | Hayır | | `privacy.erasure_completed` | KVKK silme talebi tamamlandı. | Hayır | | `contact.opt_out_changed` | Kişinin abonelik durumu değişti. | Hayır | | `template.category_changed` | Meta şablon kategorisini değiştirdi. | Hayır | | `template.quality_changed` | Şablon kalite puanı değişti. | Hayır | | `template.disabled` | Şablon Meta tarafından devre dışı bırakıldı. | Hayır | | `template.reinstated` | Duraklatma ya da işaret kalktı; şablon yeniden kullanılabilir. | Hayır | | `call.connect` | Gelen arama çalıyor ya da başlattığınız arama yanıtlandı. | Hayır | | `call.status` | Arama durumu değişti. | Hayır | | `call.terminate` | Arama bitti. | Hayır | | `call.permission_reply` | Müşteri arama izni isteğini yanıtladı. | Hayır | | `flow.completed` | Müşteri formu gönderdi. | Hayır | | `flow.status_changed` | Form durumu değişti. | Hayır | | `contact.synced` | Coexistence: telefondaki uygulama rehberinde kişi eklendi, düzenlendi ya da silindi (toplu). | Hayır | | `message.status.deleted` | Meta mesajın silindiğini bildirdi. | Hayır | | `template.pending`, `template.in_appeal`, `template.locked` | Şablon incelemede, itirazda ya da kilitli. | Hayır | | `template.archived`, `template.unarchived` | Şablon arşivlendi ya da arşivden çıktı. | Hayır | | `template.deleted`, `template.pending_deletion` | Şablon silindi ya da silinmek üzere. | Hayır | | `template.limit_exceeded` | Hesap şablon sınırına ulaştı. | Hayır | | `number.limit_changed` | Numaranın Meta mesaj limiti değişti. | Hayır | | `account.review_changed` | WhatsApp işletme hesabı incelemesi sonuçlandı. | Hayır | Şablon durum olaylarının (`template.approved`, `rejected`, `paused`, `flagged`, `disabled`, `reinstated`) yükünde `previous_status`, `rejected_reason`, `rejected_reason_text`, `quality_score`, `occurred_at` ve `source` (`webhook` ya da `sync`) bulunur; eski `reason` alanı korunur. Meta bildirimi gelmediyse geçiş şablon senkronunda yakalanır; aynı geçiş iki kez yayınlanmaz. Hata ve uyarı taşıyan olaylarda (`message.status.failed`, şablon olayları, `account.alert`, `number.name_changed`, `account.review_changed`) Türkçe `message_tr` / `reason_tr` ve `action_tr` alanları gelir; Meta'nın ham alanları aynen korunur. Sözlüğün tamamı: `GET /meta/error-codes`. Her olayın örnek yükü: [Webhook olayları](referans/webhook-olaylari.html). ## Teslim durumunu eşleştirin `message.status.*` olayları `wamid`, `recipient`, `timestamp`, `errors`, `phone_number_id`, `conversation`, `pricing`, `biz_opaque_callback_data`, `user_id` ve `direction` alanlarını taşır. Gönderimde `Idempotency-Key` kullandıysanız `biz_opaque_callback_data` bu anahtarı içerir; olayı kendi kaydınızla ek alan göndermeden eşleştirebilirsiniz. ## Yeniden deneme | Deneme | Bekleme | |---|---| | 1 | Olay anında | | 2 | +1 dakika | | 3 | +5 dakika | | 4 | +30 dakika | | 5 | +2 saat | | 6 | +12 saat | | 7 | +24 saat | 7. deneme de başarısız olursa teslim ölü (dead-letter) duruma geçer; toplam süre yaklaşık 38,6 saattir. Son 7 gündeki ölü teslimleri yeniden kuyruğa almak için: ```bash curl -X POST https://api.verimerkezi.app/wa/webhooks/redeliver \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "since": "2026-10-08T00:00:00Z" }' ``` > **İpucu:** Aynı olay birden fazla kez gelebilir. `event_id` değerini saklayın ve daha önce işlediğiniz olayları atlayın. ## Test edin - `POST /webhooks/{id}/test` aboneliğe anında imzalı bir `test.ping` olayı gönderir ve sonucu döndürür. - Test anahtarıyla `POST /test/inbound` sahte bir gelen mesaj üretir. Bkz. [Test modu](test-modu.html). ## İmza anahtarını döndürün Aboneliği silmeden imza anahtarını yenileyin: ```bash curl -X POST https://api.verimerkezi.app/wa/webhooks/17/rotate-secret \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "grace_seconds": 86400 }' ``` Yeni anahtar yalnız bu yanıtta döner. `grace_seconds` süresince her teslimde birincil `X-VeriMerkezi-Signature-256` başlığı yeni anahtarla, ek `X-VeriMerkezi-Signature-256-Previous` başlığı eski anahtarla imzalanır. Alıcınızı yeni anahtara geçirene kadar iki imzadan birini kabul edin; böylece hiçbir teslim düşmez. ## Aboneliği yönetin | İşlem | Uç | |---|---| | Listele | `GET /webhooks` | | Güncelle (adres, olaylar, etkinlik, ad) | `PATCH /webhooks/{id}` | | Pasifleştir | `PATCH /webhooks/{id}` ile `{ "active": false }` | | Sil | `DELETE /webhooks/{id}` | | Teslim sağlığı | `GET /health` | Kaçan olayları mesaj kayıtlarından da geri alabilirsiniz: `GET /messages`. Bkz. [Mesaj gönderme](mesaj-gonderme.html). --- # Kişiler Kişi rehberinize tekil ve toplu kişi ekleyin, listeleyin ve abonelikten çıkan (opt-out) kişileri izleyin. Kişi rehberi, müşterilerinizin telefon, ad, e-posta, firma ve etiket bilgilerini saklar. API ile kişi ekleyebilir, toplu içe aktarabilir, listeleyebilir ve mesaj almak istemeyenleri izleyebilirsiniz. > **Not:** Kişi güncelleme, silme, etiket düzenleme ve abonelik durumunu değiştirme için API ucu yoktur; bu işlemler panelden yapılır. ## Kişi ekleyin ```bash cURL curl -X POST https://api.verimerkezi.app/wa/contacts \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone": "5550000000", "country_code": "90", "name": "Ayşe Yılmaz", "email": "ayse@example.com", "company": "Firma A.Ş.", "tags": ["vip", "istanbul"] }' ``` ```javascript Node.js const kisi = await vm.createContact('5550000000', 'Ayşe Yılmaz', { email: 'ayse@example.com', tags: ['vip'] }); console.log(kisi.id, kisi.phone); // +905550000000 ``` ```python Python kisi = vm.create_contact('5550000000', 'Ayşe Yılmaz', email='ayse@example.com', tags=['vip']) print(kisi['id'], kisi['phone']) ``` ```php PHP createContact('5550000000', 'Ayşe Yılmaz', ['email' => 'ayse@example.com', 'tags' => ['vip']]); echo $kisi['phone']; ``` ```json { "ok": true, "id": 26481, "phone": "+905550000000", "name": "Ayşe Yılmaz" } ``` - Telefon en az 10 hane olmalıdır. Numara ülke koduyla başlamıyorsa `country_code` (varsayılan `90`) eklenir ve baştaki `0` atılır. - `name` en az 2 karakter olmalıdır. - Telefon zaten kayıtlıysa `409 contact_exists` döner; `error.id` mevcut kişiyi verir. ## Toplu ekleyin Tek istekte en fazla 1000 kişi ekleyin. Geçersiz satırlar `errors` dizisinde raporlanır, diğerleri eklenir. ```bash curl -X POST https://api.verimerkezi.app/wa/contacts/bulk \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "contacts": [ { "phone": "5550000003", "name": "Ali Demir" }, { "phone": "5550000002", "name": "Zeynep Şahin", "tags": ["yeni"] } ], "skip_duplicates": true, "country_code": "90" }' ``` ```json { "ok": true, "inserted": 2, "skipped": 0, "errors": [] } ``` - `skip_duplicates: true` ise kayıtlı telefonlar sessizce atlanır ve `skipped` sayısına eklenir. Göndermezseniz yinelenenler `errors` içinde `duplicate` koduyla döner. - Satır hatası biçimi: `{ "index": 47, "code": "invalid", "phone": "abc" }`. - 1000'den fazla kişi `422 too_many` döner; listeyi parçalara bölün. ## Listeleyin ```bash curl "https://api.verimerkezi.app/wa/contacts?limit=50&q=ayse" \ -H "Authorization: Bearer $VM_API_KEY" ``` | Parametre | Açıklama | |---|---| | `limit` | 1–200, varsayılan 50. | | `cursor` | Önceki yanıttaki `next_cursor`. | | `q` | Ad ya da telefon içinde arama. | | `opted_out` | `true` yalnız reddedenleri, `false` diğerlerini listeler. | Her kayıtta `opted_out` ve `opted_out_at` alanları döner. Sayfalama: [Sayfalama](sayfalama.html). ## Mesaj almak istemeyenler Müşteri WhatsApp'tan reddettiğinde ya da durum panelden değiştirildiğinde kişinin `opted_out` bayrağı güncellenir. Pazarlama ve otomatik mesaj göndermeden önce bu kişileri listenizden çıkarın. 1. İlk kurulumda reddedenleri bir kez çekin: ```bash curl "https://api.verimerkezi.app/wa/contacts/opted-out?limit=200" \ -H "Authorization: Bearer $VM_API_KEY" ``` 2. Değişiklikleri anında almak için webhook aboneliğinize `contact.opt_out_changed` olayını ekleyin. 3. Olay kaçırırsanız son eşitleme zamanınızla artımlı okuyun: `GET /contacts/opted-out?since=2026-10-01T00:00:00+03:00`. `contact.opt_out_changed` olayı `contact_id`, `phone`, `opted_out`, `previous_opted_out`, `source`, `phone_number_id` ve `changed_at` alanlarını taşır. `source` değeri değişikliğin kaynağını belirtir (`whatsapp_ret`, `whatsapp_onay`, `admin_override`, `automation`, `panel`, `panel_bulk`). > **Uyarı:** Abonelikten çıkış kanıtları yasal gereklilik olduğu için KVKK silme işleminde de korunur. ## KVKK - Kişiyi rehbere yalnız açık rızasıyla ekleyin. - Silme talebinde `POST /privacy/erasure` ile kişinin mesaj ve medya verilerini silin. Bkz. [Hesap referansı](referans/account.html#kvkk-silme). > **Not:** Coexistence numaralarında telefondaki WhatsApp Business uygulamasının rehberi ayrı bir kaynaktır: [Uygulama rehberi](uygulama-rehberi.html). Tüm alanlar: [Kişiler referansı](referans/contacts.html). --- # Uygulama rehberi WhatsApp Business uygulamasıyla birlikte kullanılan numaraların telefondaki kişi rehberini çekin, değişiklikleri contact.synced olayıyla alın. Numaranız WhatsApp Business uygulamasıyla birlikte (coexistence) bağlıysa, telefondaki uygulamanın kişi rehberi Veri Merkezi'ne aktarılır. Rehberde kişi eklediğinizde, adını değiştirdiğinizde ya da sildiğinizde değişiklik birkaç saniye içinde gelir. Bu veri, panel kişi rehberinden (`GET /contacts`) ayrıdır. | Uç | Ne yapar | |---|---| | `GET /app-contacts` | Rehber kayıtlarını listeler (süzgeç + imleçle sayfalama). | | `GET /numbers/{phone_number_id}/app-contacts/sync` | Numaranın senkron durumunu ve kayıt sayılarını verir. | | `contact.synced` olayı | Ekleme, düzenleme ve silmeleri toplu gönderir. | Kapsam: `contacts:read`. Kredi düşmez. ## Nasıl çalışır 1. Numara coexistence ile bağlanınca Veri Merkezi, Meta'dan rehberin ilk aktarımını otomatik ister. Kayıtlar `source: "app_initial_sync"` ile gelir. 2. Sonrasında telefondaki her ekleme, düzenleme ve silme Meta'dan kendiliğinden gelir (`source: "app_sync"`). 3. 2.23.0'dan önce bağlanmış numaralarda daha önce saklanmış rehber olayları geriye dönük işlendi (`source: "app_backfill"`). > **Not:** Meta ilk aktarımı yalnız numara bağlandıktan sonraki 24 saat içinde ve bir kez verir ([Meta belgesi](https://developers.facebook.com/docs/whatsapp/embedded-signup/custom-flows/onboarding-business-app-users)). Tekrar istemek için uç yoktur. Eski numarada durum ucu `initial_sync.status: "expired"` döner. Tam rehberi yeniden almak için numarayı panelden ayırıp yeniden bağlayın. ## Durumu kontrol edin ```bash curl "https://api.verimerkezi.app/wa/numbers/1234567890/app-contacts/sync" \ -H "Authorization: Bearer $VM_API_KEY" ``` `sync_supported: false` ise `reason` nedeni verir: `not_coexistence` (numara uygulamayla birlikte kullanılmıyor) ya da `number_detached` (numara ayrıldı). `contact_count` etkin, `removed_count` silinmiş kayıt sayısıdır. ## İlk tam çekim ```bash curl "https://api.verimerkezi.app/wa/app-contacts?phone_number_id=1234567890&include_removed=true&limit=200" \ -H "Authorization: Bearer $VM_API_KEY" ``` ```javascript Node.js let cursor = null; do { const sayfa = await vm.listAppContacts({ phoneNumberId: '1234567890', includeRemoved: true, limit: 200, cursor }); for (const k of sayfa.app_contacts) kaydet(k); cursor = sayfa.next_cursor; } while (cursor); ``` ```python Python cursor = None while True: sayfa = vm.list_app_contacts(phone_number_id='1234567890', include_removed=True, limit=200, cursor=cursor) for k in sayfa['app_contacts']: kaydet(k) cursor = sayfa.get('next_cursor') if not cursor: break ``` | Parametre | Açıklama | |---|---| | `phone_number_id` | Yalnız bu numaranın rehberi. Verilmezse bütün numaralarınız. | | `updated_since` | ISO 8601. Yalnız bu zamandan sonra değişen kayıtlar. | | `include_removed` | `true` ise rehberden silinmiş kayıtlar da gelir (`removed_at` dolu). | | `limit` | 1–200, varsayılan 50. | | `cursor` | Önceki yanıttaki `next_cursor`. | Sıralama `updated_at` azalan, sonra `id` azalandır. Kayıt alanları: `id`, `phone_number_id`, `phone` (E.164), `wa_id`, `user_id` (BSUID), `full_name`, `first_name`, `source`, `updated_at`, `removed_at`. ## Değişiklikleri olayla alın Webhook aboneliğinize `contact.synced` olayını ekleyin (`*` aboneliği bu olayı kapsamaz). ```json { "event": "contact.synced", "data": { "phone_number_id": "1234567890", "sync_type": "incremental", "batch": { "index": 1, "count": 1 }, "changes": [ { "action": "update", "id": 502, "phone": "+905552223344", "wa_id": "905552223344", "user_id": null, "full_name": "Mehmet Kaya (İş)", "first_name": "Mehmet", "changed_at": "2026-10-13T10:15:00+03:00" } ] } } ``` - `changes[].action`: `add`, `update` ya da `remove`. Meta düzenlemeyi de "ekleme" olarak gönderir; Veri Merkezi karşılaştırıp yalnız gerçek değişikliği yayınlar. - `remove`'da `full_name` ve `first_name` `null`dır. Eşleştirmeyi `id` (yoksa `phone`) ile yapın. - Bir olayda en fazla 100 değişiklik bulunur. Fazlası `batch.index` / `batch.count` ile bölünür. İlk aktarımda `sync_type: "initial"` gelir. - Olay kaçırırsanız günde bir kez artımlı okuyun: `GET /app-contacts?updated_since=&include_removed=true`. ## Test edin Test anahtarıyla (`vmk_test_`) `GET /app-contacts` 3 sabit örnek kayıt döner (`"simulated": true`). Webhook alıcınızı denemek için: ```bash curl -X POST https://api.verimerkezi.app/wa/test/inbound \ -H "Authorization: Bearer $VM_TEST_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "type": "app_contact_sync" }' ``` Bu istek `add`, `update` ve `remove` içeren örnek bir `contact.synced` olayı gönderir. Rehbere kayıt yazılmaz. ## Silme - `POST /numbers/{phone_number_id}/detach` + `purge_messages: true`: numaranın rehber kayıtları silinir (`number.purged` → `counts.app_contacts`). `false` ise kayıtlar kalır, yeni senkron durur. - `POST /privacy/erasure`: kişinin rehber kayıtları da silinir (`erased.app_contacts`). Tüm alanlar: [Uygulama rehberi referansı](referans/app-contacts.html). --- # Hesap API anahtarının sahibi olan hesabın bilgileri, hesap geneli ayarlar, KVKK işlemleri ve webhook teslimat sağlığı. ## Account nesnesi - `user` (object): Hesap sahibi: `id`, `email`, `name`, `role`. - `api_key` (object): İsteği yapan anahtar: `id`, `name`, `key_prefix`, `mode` (`live`/`test`), `scopes`, `expires_at`. - `account_settings` (object): Hesap ayarları: `revoke_edit_clean`, `default_automation_enabled`, `default_opt_out_autoreply_enabled`. - `credits` (object): Mesaj kredisi: `balance` ve etkin `packages` listesi. ## Hesap bilgisini getir `GET https://api.verimerkezi.app/wa/me` API anahtarının sahibini, anahtarın kendisini, hesap ayarlarını ve kredi bakiyesini döndürür. Entegrasyonunuzu doğrulamak için ilk çağrı olarak kullanın. ```bash curl -X GET "https://api.verimerkezi.app/wa/me" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json null ``` ## Hesap ayarlarını güncelle `PATCH https://api.verimerkezi.app/wa/account/settings` Hesabınızdaki tüm numaralara toplu uygulanan ayarları ve bundan sonra bağlanacak numaraların varsayılanlarını değiştirir. En az bir alan gönderin; değerler JSON boolean olmalıdır. Kapsam: `profile:write` ### İstek gövdesi - `revoke_edit_clean` (boolean): Açıkken silme/düzenleme olaylarında yalnız `message.revoked` / `message.edited` yayınlanır; `message.received` gönderilmez, 24 saatlik pencere uzamaz, otomasyon ve abonelikten çıkış yanıtı tetiklenmez. Tüm numaralara uygulanır. - `automation_enabled` (boolean): Veri Merkezi otomasyon motorunu tüm mevcut numaralarda açar/kapatır. - `opt_out_autoreply_enabled` (boolean): Her zaman `true`: 11.10.2026'dan beri RET onay mesajı zorunludur, kapatılamaz; `false` gönderilirse yok sayılır ve yanıtta `true` döner. Çıkış talebi her durumda kaydedilir. - `default_automation_enabled` (boolean): Bundan sonra bağlanacak numaraların otomasyon varsayılanı. - `default_opt_out_autoreply_enabled` (boolean): Bundan sonra bağlanacak numaraların çıkış yanıtı varsayılanı. Her zaman `true` uygulanır (RET onay mesajı zorunlu); `false` yok sayılır. ```bash curl -X PATCH "https://api.verimerkezi.app/wa/account/settings" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "revoke_edit_clean": true, "default_automation_enabled": false }' ``` Yanıt (200): ```json { "ok": true, "numbers_updated": 2, "numbers_total": 2, "settings": { "revoke_edit_clean": true, "automation_enabled": true, "opt_out_autoreply_enabled": true, "default_automation_enabled": false, "default_opt_out_autoreply_enabled": null } } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `profile:write` kapsamı yok. - 422 `invalid_request`: Değiştirilecek alan yok ya da değer boolean değil. ## Veri saklama sürelerini ayarla `PATCH https://api.verimerkezi.app/wa/account/retention` KVKK kapsamında hesap düzeyindeki saklama sürelerini gün olarak kaydeder. En az bir alan gönderin. Kapsam: `profile:write` ### İstek gövdesi - `messages_days` (integer): Mesaj kayıtları için gün (1–3650). - `media_days` (integer): Medya dosyaları için gün (1–3650). - `webhook_deliveries_days` (integer): Giden webhook teslim kayıtları için gün (1–365). ```bash curl -X PATCH "https://api.verimerkezi.app/wa/account/retention" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "messages_days": 365, "media_days": 180, "webhook_deliveries_days": 30 }' ``` Yanıt (200): ```json { "ok": true, "retention": { "messages_days": 365, "media_days": 180, "webhook_deliveries_days": 30 }, "note": "Saklama süreleri kaydedildi. Süre dolan veriler periyodik temizlik işiyle silinir; süre verilmeyen tür varsayılan olarak saklanır." } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `profile:write` kapsamı yok. - 422 `invalid_request`: Alan yok, tam sayı değil ya da aralık dışında. ## Kişiye ait verileri sil (KVKK) `POST https://api.verimerkezi.app/wa/privacy/erasure` Bir son kullanıcının silinme/unutulma talebini işler. Kişiye ait mesaj gövdeleri anonimleştirilir; medya, konuşma bilgileri, giden webhook teslim gövdeleri ve idempotency yanıtları silinir. Abonelikten çıkış (onay) kaydı korunur. İşlem idempotenttir. 2.29.0: isteğe bağlı `reference` yanıtta ve `privacy.erasure_completed` olayında aynen döner; `Idempotency-Key` ile yapılan yeniden deneme aynı `erasure_id` ve yanıtı döndürür, silme ikinci kez çalışmaz. Kapsam: `profile:write` ### Başlıklar - `Idempotency-Key` (): 8–128 karakterlik benzersiz anahtar (UUID önerilir). Aynı anahtar ve aynı gövdeyle yapılan tekrar, ilk yanıtı (aynı `erasure_id`) `Idempotent-Replay: true` başlığıyla döndürür; silme yeniden çalışmaz. 24 saat saklanır. (2.29.0) ### İstek gövdesi - `wa_id` (string): Kişinin telefon numarası (8–15 hane). `wa_id` ya da `user_id` zorunludur. - `user_id` (string): Kişinin BSUID değeri (telefonu görünmeyen kullanıcılar için). - `phone_number_id` (string): İsteğe bağlı; verilirse hesabınıza ait olmalıdır. - `reference` (string): İsteğe bağlı, 1–64 karakter: kendi talep kimliğiniz. Yanıtta ve `privacy.erasure_completed` olayında aynen döner (2.29.0). ```bash curl -X POST "https://api.verimerkezi.app/wa/privacy/erasure" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "wa_id": "905550000000", "phone_number_id": "1234567890", "reference": "crm-silme-4711" }' ``` Yanıt (202): ```json { "ok": true, "status": "completed", "erasure_id": "ers_0123456789abcdef01234567", "phone_number_id": "1234567890", "wa_id": "905550000000", "user_id": null, "reference": "crm-silme-4711", "completed_at": "2026-10-10T09:00:00Z", "erased": { "messages": 42, "media": 3 } } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `profile:write` kapsamı yok. - 404 `number_not_found`: Numara size ait değil ya da aktif değil. - 422 `invalid_request`: `wa_id` ve `user_id` ikisi de yok ya da biçim hatalı. - 500 `erasure_failed`: Silme tamamlanamadı; tekrar deneyin. ## Webhook teslimat sağlığını getir `GET https://api.verimerkezi.app/wa/health` Hesabınızın webhook teslimat durumunu döndürür: kuyruk derinliği, son bir saatteki teslim sayısı, ortalama ve p95 gecikme, son 24 saatteki ölü teslimler. İzleme panolarında kullanın. ```bash curl -X GET "https://api.verimerkezi.app/wa/health" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "status": "ok", "time": "2026-10-10T09:00:00Z", "webhook_delivery": { "active_subscriptions": 1, "queue_depth": 0, "delivered_last_hour": 240, "latency_seconds_last_hour": { "avg": 1.2, "p95": 3 }, "dead_last_24h": 0, "last_delivered_at": "2026-10-10T11:59:58+03:00" }, "meta_webhook": { "last_received_at": "2026-10-10T11:59:59+03:00", "failed_last_hour": 0 } } ``` --- # Numaralar Hesabınıza bağlı WhatsApp numaraları; numara ayarları, geçmiş aktarımı ve numarayı hesaptan ayırma. ## Number nesnesi - `id` (integer): Veri Merkezi'deki numara kaydının kimliği. - `waba_id` (string): Numaranın bağlı olduğu WhatsApp İşletme Hesabı (WABA) kimliği. - `phone_number_id` (string): Numaranın Meta kimliği. Gönderim ve diğer uçlarda bunu kullanırsınız. - `display_phone_number` (string): Görünen telefon numarası. - `verified_name` (string): Meta'da onaylı görünen ad. - `quality_rating` (string): Meta kalite puanı. - `status` (string): Numara durumu. - `status_reason` (string): Durum ayrıntısı (Meta inceleme durumu). - `disconnected_at` (string): Bağlantının koptuğu zaman. - `disconnect_reason` (string): Meta'nın bildirdiği kopma nedeni (ör. `PRIMARY_INACTIVITY`: coexistence'ta telefondaki WhatsApp Business uygulaması uzun süre kullanılmadı). Yalnız numara aktif değilken dolu. (2.30.0) - `disconnect_initiated_by` (string): Kopmayı başlatan taraf (ör. `SYSTEM`, `USER`). Yalnız numara aktif değilken dolu. (2.30.0) - `reauth_required` (boolean): Meta yetkisi düştü; numara panelden yeniden yetkilendirilmeli. Yeniden bağlanınca `false` olur. (2.30.0) - `reauth_required_at` (string): Yetki kaybının saptandığı zaman. (2.30.0) - `disconnect_message_tr` (string): Kopma, kısıt ya da yeniden yetki nedeninin kısa Türkçe açıklaması (kopma nedenleri sözlüğü). Neden bilinmiyorsa `null`. (2.31.0) - `disconnect_action_tr` (string): Müşterinin yapacağı adım (Türkçe). Adım yoksa `null`. (2.31.0) - `reconnect_url` (string): Numaranın yeniden bağlanacağı Veri Merkezi panel sayfası; yalnız yeniden bağlama gerekiyorsa dolu. (2.31.0) - `detached_at` (string): API ile hesaptan ayrıldığı zaman (ISO 8601). - `detached_reason` (string): Ayırma gerekçesi. - `purge` (object): Zamanlanmış kayıt silme durumu: `status` (`scheduled`/`running`/`done`), `scheduled_at`, `due_by`, `done_at`. - `connection_type` (string): Bağlantı türü. `coexistence`, WhatsApp Business uygulamasıyla birlikte kullanılan numaradır. - `connected_at` (string): Bağlanma zamanı. - `messaging_limit_tier` (string): Meta mesajlaşma limiti kademesi (ör. `TIER_250`, `TIER_2K`, `TIER_10K`, `TIER_100K`, `TIER_UNLIMITED`). - `payment_method_status` (string): Meta ödeme yöntemi durumu: `ok`, `missing` ya da `unknown`. Mesaj ücretini WhatsApp işletme hesabının sahibi Meta'ya öder; `missing` iken pazarlama ve hizmet şablonları 131042 ile reddedilir. (2.32.0) - `payment_method_action_tr` (string): Ödeme yöntemi eksikse müşterinin yapacağı adım (Türkçe); aksi halde `null`. (2.32.0) - `throughput` (object): Meta gönderim hızı bilgisi (yalnız aktif numarada, bir saat önbellekli). - `automation_enabled` (boolean): Veri Merkezi otomasyonu bu numarada açık mı. - `opt_out_autoreply_enabled` (boolean): Her zaman `true`: 11.10.2026'dan beri RET onay mesajı zorunludur, kapatılamaz; `false` gönderilirse yok sayılır ve yanıtta `true` döner. Çıkış talebi her durumda kaydedilir. ## Numaraları listele `GET https://api.verimerkezi.app/wa/numbers` Hesabınıza bağlı WhatsApp numaralarını döndürür. Parametresiz çağrı yalnız aktif numaraları listeler. 2.31.0: aktif olmayan ya da yeniden yetki bekleyen numarada `disconnect_message_tr` (kısa neden), `disconnect_action_tr` (müşterinin yapacağı adım) ve `reconnect_url` (yeniden bağlama sayfası) döner; neden bilinmiyorsa üçü de `null`. 2.32.0: `payment_method_status` (`ok` | `missing` | `unknown`) ve `payment_method_action_tr`. Kapsam: `profile:read` ### Sorgu parametreleri - `status` (string): `all` tüm durumları, tek bir durum adı yalnız o durumu döndürür. Verilmezse yalnız `active`. ```bash curl -X GET "https://api.verimerkezi.app/wa/numbers" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "numbers": [ { "id": 4, "waba_id": "1029384756", "phone_number_id": "1234567890", "display_phone_number": "+90 555 000 00 00", "verified_name": "Firma A.Ş.", "quality_rating": "GREEN", "status": "active", "status_reason": null, "disconnected_at": null, "disconnect_reason": null, "disconnect_initiated_by": null, "reauth_required": false, "reauth_required_at": null, "disconnect_message_tr": null, "disconnect_action_tr": null, "reconnect_url": null, "detached_at": null, "detached_reason": null, "purge": null, "connection_type": "standard", "connected_at": "2026-05-26 17:50:24", "messaging_limit_tier": "TIER_2K", "payment_method_status": "ok", "payment_method_action_tr": null, "throughput": { "level": "STANDARD" }, "automation_enabled": true, "opt_out_autoreply_enabled": true } ] } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `profile:read` kapsamı yok. ## Kopma nedenleri sözlüğü `GET https://api.verimerkezi.app/wa/meta/disconnect-reasons` Numara kopma, kısıt ve yeniden yetki nedenlerinin tamamını döndürür: kod, kaynak (`meta_disconnection_reason`, `meta_event`, `meta_ban_state`, `meta_restriction`, `vm_event`), kısa Türkçe açıklama ve müşterinin yapacağı adım. Kodlar Meta `account_update` webhook referansındaki değerlerle ve Veri Merkezi'nin kendi olaylarıyla sınırlıdır; Meta süre belgelemediği için süre yazılmaz. `GET /numbers` ve `number.status_changed` aynı sözlükle `disconnect_message_tr` / `disconnect_action_tr` döndürür; yerel kopya tutabilirsiniz. (2.31.0) Tam liste: - `PRIMARY_INACTIVITY` (meta_disconnection_reason): Telefondaki WhatsApp Business uygulaması uzun süre açılmadığı için Meta bağlantıyı kaldırdı. Adım: Telefonda WhatsApp Business uygulamasını açın; ardından Veri Merkezi panelinde WhatsApp › Ayarlar › Numaralar sayfasından numarayı yeniden bağlayın. - `COMPANION_INACTIVITY` (meta_disconnection_reason): Bağlı cihaz uzun süre kullanılmadığı için Meta bağlantıyı kaldırdı. Adım: Veri Merkezi panelinde WhatsApp › Ayarlar › Numaralar sayfasından numarayı yeniden bağlayın. - `BUSINESS_DOWNGRADE` (meta_disconnection_reason): Numara telefonda WhatsApp Business'tan normal WhatsApp'a geçirildi. Adım: (1) Numarayı telefonda tekrar WhatsApp Business uygulamasına taşıyın. (2) Veri Merkezi panelinde WhatsApp › Ayarlar › Numaralar sayfasından numarayı yeniden bağlayın. - `ACCOUNT_DISCONNECTED` (meta_disconnection_reason): WhatsApp hesabı silindi ya da Meta tarafından kapatıldı. Adım: Telefonda WhatsApp hesabının durumunu kontrol edin; hesap açıksa Veri Merkezi panelinde WhatsApp › Ayarlar › Numaralar sayfasından numarayı yeniden bağlayın. - `CHANGE_NUMBER` (meta_disconnection_reason): WhatsApp hesabının telefon numarası değiştirildi. Adım: Yeni numarayı Veri Merkezi panelinde WhatsApp › Ayarlar › Numaralar sayfasından bağlayın. - `USER_RE_REGISTERED` (meta_disconnection_reason): Numara başka bir cihazda yeniden kaydedildi. Adım: Veri Merkezi panelinde WhatsApp › Ayarlar › Numaralar sayfasından numarayı yeniden bağlayın. - `PARTNER_REMOVED` (meta_event): Numaranın Veri Merkezi bağlantısı kaldırıldı. Adım: Veri Merkezi panelinde WhatsApp › Ayarlar › Numaralar sayfasından numarayı yeniden bağlayın. - `PARTNER_APP_UNINSTALLED` (meta_event): Veri Merkezi uygulamasının WhatsApp hesabındaki izni kaldırıldı. Adım: Veri Merkezi panelinde WhatsApp › Ayarlar › Numaralar sayfasından numarayı yeniden bağlayıp izinleri onaylayın. - `ACCOUNT_DELETED` (meta_event): WhatsApp işletme hesabı silindi. Adım: Yeni ya da mevcut bir WhatsApp işletme hesabıyla Veri Merkezi panelinde WhatsApp › Ayarlar › Numaralar sayfasından numarayı bağlayın. - `ACCOUNT_OFFBOARDED` (meta_event): WhatsApp işletme hesabı platformdan çıkarıldı. Adım: Veri Merkezi destek ekibiyle görüşün. - `ACCOUNT_RECONNECTED` (meta_event): Numaranın bağlantısı yeniden kuruldu. - `ACCOUNT_VIOLATION` (meta_event): Meta bu hesap için politika ihlali bildirdi. Adım: Meta Business Suite'teki bildirimi inceleyin ve ilgili içeriği düzeltin. - `ACCOUNT_RESTRICTION` (meta_event): Meta bu hesapta bazı işlemleri geçici olarak kısıtladı. Adım: Kısıtın bitiş tarihini bekleyin; Meta bir düzeltme adımı bildirdiyse uygulayın. - `DISABLE` (meta_ban_state): Meta WhatsApp işletme hesabını kapattı (politika ihlali). Adım: Meta Business Suite üzerinden itiraz edin. - `SCHEDULE_FOR_DISABLE` (meta_ban_state): Meta WhatsApp işletme hesabını kapatmayı planladı. Adım: Bildirilen ihlali giderin ve Meta Business Suite üzerinden itiraz edin. - `REINSTATE` (meta_ban_state): WhatsApp işletme hesabı yeniden açıldı. - `RESTRICTED_BIZ_INITIATED_MESSAGING` (meta_restriction): İşletmenin başlattığı mesaj (şablon) gönderimi geçici olarak kısıtlandı. Adım: Kısıtın bitiş tarihini bekleyin; bu sürede yalnız müşteriden gelen mesajlara yanıt verebilirsiniz. - `RESTRICTED_CUSTOMER_INITIATED_MESSAGING` (meta_restriction): Müşterilerden gelen mesajlara yanıt gönderimi geçici olarak kısıtlandı. Adım: Kısıtın bitiş tarihini bekleyin. - `RESTRICTED_ADD_PHONE_NUMBER_ACTION` (meta_restriction): Bu hesaba yeni numara eklenmesi geçici olarak kısıtlandı. Adım: Kısıtın bitiş tarihini bekleyin. - `RESTRICTED_UTILITY_TEMPLATES` (meta_restriction): Hizmet (utility) şablonlarının gönderimi kısıtlandı. Adım: Kısıtın bitiş tarihini bekleyin. - `RESTRICTED_DIRECT_SEND_UTILITY_TEMPLATES` (meta_restriction): Hizmet (utility) şablonlarının doğrudan gönderimi kısıtlandı. Adım: Kısıtın bitiş tarihini bekleyin. - `RESTRICTED_BUSINESS_INITIATED_CALLING` (meta_restriction): İşletmenin başlattığı aramalar kısıtlandı. Adım: Kısıtın bitiş tarihini bekleyin. - `RESTRICTED_USER_INITIATED_CALLING` (meta_restriction): Müşterilerin işletmeyi araması kısıtlandı. Adım: Kısıtın bitiş tarihini bekleyin. - `RESTRICTED_BIZ_INITIATED_AND_USER_INITIATED_CALLING` (meta_restriction): İşletme ve müşteri aramaları kısıtlandı. Adım: Kısıtın bitiş tarihini bekleyin. - `RESTRICTED_USER_INITIATED_CALLING_CALL_BUTTON_HIDDEN` (meta_restriction): Müşteri arama düğmesi gizlendi; müşteriler işletmeyi arayamıyor. Adım: Kısıtın bitiş tarihini bekleyin. - `REAUTH_REQUIRED` (vm_event): Meta'daki WhatsApp izni kalktı; numara yeniden yetkilendirilmeli. Adım: Veri Merkezi panelinde WhatsApp › Ayarlar › Numaralar sayfasından numarayı yeniden bağlayıp izinleri onaylayın. - `CONNECTED_VIA_PANEL` (vm_event): Numara Veri Merkezi panelinden bağlandı. - `DISCONNECTED_VIA_PANEL` (vm_event): Numara Veri Merkezi panelinden ayrıldı. Adım: Yeniden kullanmak için Veri Merkezi panelinde WhatsApp › Ayarlar › Numaralar sayfasından numarayı yeniden bağlayın. - `TRANSFERRED` (vm_event): Numara başka bir Veri Merkezi hesabına taşındı. Kapsam: `—` ```bash curl -X GET "https://api.verimerkezi.app/wa/meta/disconnect-reasons" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "reconnect_url": "https://verimerkezi.app/panel/wa/ayarlar?bolum=numaralar", "reasons": [ { "code": "PRIMARY_INACTIVITY", "source": "meta_disconnection_reason", "message_tr": "Telefondaki WhatsApp Business uygulaması uzun süre açılmadığı için Meta bağlantıyı kaldırdı.", "action_tr": "Telefonda WhatsApp Business uygulamasını açın; ardından Veri Merkezi panelinde WhatsApp › Ayarlar › Numaralar sayfasından numarayı yeniden bağlayın." }, { "code": "RESTRICTED_BIZ_INITIATED_MESSAGING", "source": "meta_restriction", "message_tr": "İşletmenin başlattığı mesaj (şablon) gönderimi geçici olarak kısıtlandı.", "action_tr": "Kısıtın bitiş tarihini bekleyin; bu sürede yalnız müşteriden gelen mesajlara yanıt verebilirsiniz." }, { "code": "REAUTH_REQUIRED", "source": "vm_event", "message_tr": "Meta'daki WhatsApp izni kalktı; numara yeniden yetkilendirilmeli.", "action_tr": "Veri Merkezi panelinde WhatsApp › Ayarlar › Numaralar sayfasından numarayı yeniden bağlayıp izinleri onaylayın." } ] } ``` ## Hata kodları sözlüğü `GET https://api.verimerkezi.app/wa/meta/error-codes` Meta ve Veri Merkezi hata kodlarının Türkçe sözlüğü: kod (her zaman metin), kaynak (`meta_message`, `meta_template`, `meta_account`, `meta_call`, `vm`), kısa Türkçe açıklama (`message_tr`), müşterinin adımı (`action_tr`), önem (`info` | `warn` | `error` | `critical`), `retryable` ve kategori (`recipient` | `template` | `account` | `rate` | `payment` | `other`; sayısal Meta kodlarında). Yalnız Meta'nın belgelediği kodlar yer alır. Webhook olaylarındaki ve hata yanıtlarındaki `message_tr` / `action_tr` alanları bu sözlükten gelir. (2.32.0) Kapsam: `—` ### Sorgu parametreleri - `source` (string): Yalnız bir kaynağın kayıtları: `meta_message`, `meta_template`, `meta_account`, `meta_call`, `vm`. ```bash curl -X GET "https://api.verimerkezi.app/wa/meta/error-codes" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "version": "2.32.0", "count": 130, "codes": [ { "code": "131047", "source": "meta_message", "message_tr": "Kişinin son mesajının üzerinden 24 saatten fazla geçti; serbest mesaj gönderilemez.", "action_tr": "Onaylı bir şablon mesajı gönderin; kişi yanıt verince 24 saat yeniden açılır.", "severity": "info", "retryable": false, "category": "recipient" }, { "code": "131042", "source": "meta_message", "message_tr": "Meta faturalandırma (ödeme yöntemi) hatası.", "action_tr": "Meta Business Suite › Faturalandırma bölümünde WhatsApp işletme hesabının ödeme yöntemini ekleyin ya da güncelleyin.", "severity": "critical", "retryable": false, "category": "payment" }, { "code": "138006", "source": "meta_call", "message_tr": "Kişinin onayladığı bir arama izni yok.", "action_tr": "Önce kişiden arama izni isteyin.", "severity": "warn", "retryable": false, "category": "recipient" }, { "code": "FIRST_PAUSE", "source": "meta_template", "message_tr": "Şablon düşük kalite nedeniyle ilk kez duraklatıldı.", "action_tr": "İçeriği ve gönderim kitlesini gözden geçirin.", "severity": "warn", "retryable": false, "category": null }, { "code": "insufficient_credits", "source": "vm", "message_tr": "Mesaj kredisi yetersiz.", "action_tr": "Veri Merkezi panelinden kredi yükleyin.", "severity": "error", "retryable": false, "category": null } ] } ``` ## Geçmiş aktarımı durumunu getir `GET https://api.verimerkezi.app/wa/numbers/{phone_number_id}/history-import` Coexistence numaralarında son 180 güne kadar yazışmanın aktarım durumunu döndürür. Aktarım numara bağlanınca otomatik başlar; mesajlar `message.history` olayıyla gelir. Kapsam: `profile:read` ### Yol parametreleri - `phone_number_id` (string, zorunlu): Numaranızın Meta kimliği (yalnız rakam, 5–32 hane). ```bash curl -X GET "https://api.verimerkezi.app/wa/numbers/1234567890/history-import" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "history_import": { "phone_number_id": "1234567890", "eligible": true, "status": "in_progress", "window_open": false, "phase": 1, "progress": 45, "chunks_received": 4, "messages_received": 180, "media_downloaded": 12, "error": null } } ``` Hatalar: - 400 `invalid_phone_number_id`: `phone_number_id` yalnız rakam olmalı. - 403 `insufficient_scope`: Anahtarda `profile:read` kapsamı yok. - 404 `number_not_found`: Numara size ait değil ya da aktif değil. ## Geçmiş aktarımını başlat `POST https://api.verimerkezi.app/wa/numbers/{phone_number_id}/history-import` Otomatik başlatma kaçtıysa geçmiş aktarımını elle başlatır. Meta bu aktarıma yalnız bağlantıdan sonraki 24 saat içinde ve bir kez izin verir. Kapsam: `profile:write` ### Yol parametreleri - `phone_number_id` (string, zorunlu): Numaranızın Meta kimliği (yalnız rakam, 5–32 hane). ```bash curl -X POST "https://api.verimerkezi.app/wa/numbers/1234567890/history-import" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (202): ```json { "ok": true, "history_import": { "phone_number_id": "1234567890", "eligible": true, "status": "requested", "window_open": false, "phase": null, "progress": 0, "chunks_received": 0, "messages_received": 0, "media_downloaded": 0, "error": null }, "message": "Geçmiş aktarımı istendi." } ``` Hatalar: - 400 `invalid_phone_number_id`: `phone_number_id` yalnız rakam olmalı. - 403 `insufficient_scope`: Anahtarda `profile:write` kapsamı yok. - 404 `number_not_found`: Numara size ait değil ya da aktif değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 409 `history_window_expired`: 24 saatlik pencere kapandı; geçmiş için numarayı yeniden bağlamak gerekir. - 409 `history_in_progress`: Aktarım zaten sürüyor (benzer biçimde `history_requested`, `history_completed`, `history_declined`). - 422 `history_not_applicable`: Numara coexistence değil; geçmiş aktarımı yok. ## Numara ayarlarını güncelle `PATCH https://api.verimerkezi.app/wa/numbers/{phone_number_id}/settings` Kendi otomasyonunuzu çalıştırıyorsanız çift yanıtı önlemek için Veri Merkezi otomasyonunu ve abonelikten çıkış otomatik yanıtını numara bazında kapatın. Varsayılan: açık. Kapsam: `profile:write` ### Yol parametreleri - `phone_number_id` (string, zorunlu): Numaranızın Meta kimliği (yalnız rakam, 5–32 hane). ### İstek gövdesi - `automation_enabled` (boolean): Veri Merkezi otomasyon motoru. - `opt_out_autoreply_enabled` (boolean): Her zaman `true`: 11.10.2026'dan beri RET onay mesajı zorunludur, kapatılamaz; `false` gönderilirse yok sayılır ve yanıtta `true` döner. Çıkış talebi her durumda kaydedilir. ```bash curl -X PATCH "https://api.verimerkezi.app/wa/numbers/1234567890/settings" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "automation_enabled": false, "opt_out_autoreply_enabled": true }' ``` Yanıt (200): ```json { "ok": true, "phone_number_id": "1234567890", "settings": { "automation_enabled": false, "opt_out_autoreply_enabled": true } } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `profile:write` kapsamı yok. - 404 `number_not_found`: Numara size ait değil ya da aktif değil. - 422 `invalid_request`: Alan yok ya da değer boolean değil. ## Numarayı hesaptan ayır `POST https://api.verimerkezi.app/wa/numbers/{phone_number_id}/detach` Numarayı Veri Merkezi hesabınızdan ayırır. Numara Meta'da ve WABA'nızda kayıtlı kalır; yalnız Veri Merkezi bağı kopar. Kuyruktaki giden mesajlar iptal edilir ve kredi düşülmez. Kapsam: `profile:write` ### Yol parametreleri - `phone_number_id` (string, zorunlu): Numaranızın Meta kimliği (yalnız rakam, 5–32 hane). ### Başlıklar - `Idempotency-Key` (): 8–128 karakterlik benzersiz anahtar (UUID önerilir). Aynı anahtar ve aynı gövdeyle yapılan tekrar, ilk yanıtı `Idempotent-Replay: true` başlığıyla döndürür. 24 saat saklanır. ### İstek gövdesi - `purge_messages` (boolean, zorunlu): `false`: yalnız bağ kopar, kayıtlar kalır. `true`: mesaj içerikleri, medya, konuşmalar, form gönderimleri, ham webhook kayıtları, arama ayrıntıları, API günlüğü ve teslim gövdeleri en geç 24 saat içinde silinir. **Geri alınamaz.** - `reason` (string): Ayırma gerekçesi, en fazla 100 karakter. - `deregister_meta` (boolean): Bu sürümde desteklenmez; `true` gönderilirse `422 deregister_not_supported` döner. ```bash curl -X POST "https://api.verimerkezi.app/wa/numbers/1234567890/detach" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "purge_messages": false, "reason": "Numara başka sisteme taşındı" }' ``` Yanıt (200): ```json { "ok": true, "phone_number_id": "1234567890", "detached_at": "2026-10-08T14:05:00+03:00", "already": false, "cancelled_outbound": 3, "purge": null } ``` Hatalar: - 400 `invalid_phone_number_id`: `phone_number_id` yalnız rakam olmalı. - 403 `test_mode_unsupported`: Bu işlem yalnız canlı anahtarla yapılır. - 403 `insufficient_scope`: Anahtarda `profile:write` kapsamı yok. - 404 `number_not_found`: Numara size ait değil ya da aktif değil. - 409 `idempotency_in_progress`: Aynı anahtarla bir istek hâlâ işleniyor. - 422 `invalid_request`: `purge_messages` eksik/boolean değil ya da `reason` 100 karakteri aşıyor. - 422 `deregister_not_supported`: Meta'dan kaldırma bu sürümde yok. - 422 `idempotency_key_conflict`: Aynı anahtar farklı gövdeyle kullanılmış. - 503 `feature_unavailable`: Özellik geçici olarak kullanılamıyor. --- # Mesajlar Şablon, metin, medya, konum, kişi kartı, tepki ve form mesajı gönderin; gelen mesajları okundu işaretleyin ve kaçan olayları mesaj kayıtlarından geri alın. ## Message nesnesi - `id` (integer): Veri Merkezi'deki mesaj kaydının kimliği. - `wamid` (string): WhatsApp mesaj kimliği. Test anahtarıyla gönderilenlerde `test.` ile başlar. - `direction` (string): Yön. `echo`, işletmenin telefondaki WhatsApp Business uygulamasından yazdığı mesajdır. - `type` (string): Mesaj türü (ör. `text`, `template`, `image`, `video`, `audio`, `document`, `sticker`, `reaction`, `interactive`, `button`, `location`, `contacts`). - `status` (string): Mesaj durumu (ör. `received`, `sent`, `delivered`, `read`, `failed`). - `from` (string): Gönderen telefon numarası. - `to` (string): Alıcı telefon numarası. - `user_id` (string): Karşı tarafın BSUID değeri (telefonu görünmeyen kullanıcılar). - `username` (string): Gönderenin WhatsApp kullanıcı adı. - `text` (string): Mesaj metni; medyada açıklama. - `media` (object): Medya bilgisi; gelen medyada `media_id` ile indirilir. - `template_name` (string): Şablon mesajlarında şablon adı. - `campaign_id` (integer): Kampanyadan gönderildiyse kampanya kimliği. - `is_test` (boolean): Test anahtarıyla oluşturulan kayıt mı. - `error` (object): Başarısız mesajda `code`, `title`, `message`. - `created_at` (string): Oluşturulma zamanı (ISO 8601). - `sent_at` (string): Gönderim zamanı. - `delivered_at` (string): Teslim zamanı. - `read_at` (string): Okunma zamanı. - `failed_at` (string): Başarısızlık zamanı. - `context` (object): Alıntılı mesajda `{ message_id, from, forwarded, frequently_forwarded }`. - `reaction` (object): Tepki mesajında `{ message_id, emoji }`. - `location` (object): Konum mesajında `{ latitude, longitude, name, address, url }`. - `contacts` (array): Kişi kartı paylaşıldıysa Meta'nın kişi dizisi. - `original_message_id` (string): Silme/düzenlemede hedef mesajın `wamid` değeri. - `button` (object): Buton yanıtında `{ payload, text }`. ## Şablon mesajı gönder `POST https://api.verimerkezi.app/wa/messages` Meta onaylı bir şablonla mesaj gönderir. Müşteri size son 24 saat içinde yazmadıysa yalnız şablon mesajı gönderebilirsiniz. Kapsam: `messages:send` ### Başlıklar - `Idempotency-Key` (): Önerilir. 8–128 karakter. Aynı anahtar + aynı gövde ilk yanıtı döndürür (mesaj ikinci kez gitmez); aynı anahtar + farklı gövde `422 idempotency_key_conflict` döner. 24 saat saklanır. ### İstek gövdesi - `phone_number_id` (string, zorunlu): Gönderimi yapacak numaranızın Meta kimliği. Numara size ait ve aktif olmalıdır. - `to` (string, zorunlu): Alıcının telefon numarası, uluslararası biçimde ve `+` olmadan (ör. `905550000000`). Rakam dışı karakterler atılır; en az 10 hane gerekir. Telefonu olmayan alıcıda `to` yerine `recipient` gönderin. - `recipient` (string): Telefonu görünmeyen kullanıcının WhatsApp kullanıcı kimliği (BSUID, webhook'taki `user_id`). Yalnız `to` boşken kullanılır. Eş anlamlıları: `recipient_id`, `bsuid`. - `context` (object): Alıntılı yanıt. Alıcıda yanıtladığınız mesaj alıntı balonuyla görünür. Tepki mesajıyla birlikte kullanılamaz. - `biz_opaque_callback_data` (string): En fazla 512 karakterlik kendi eşleştirme değeriniz. `message.status.*` olaylarında `data.biz_opaque_callback_data` olarak aynen döner. Göndermezseniz ve `Idempotency-Key` verdiyseniz sunucu bu alanı anahtarın değeriyle kendisi doldurur. - `type` (string): `template` (önerilir; şablon mesajında isteğe bağlıdır). - `template` (object, zorunlu): Onaylı şablon. Şablon, gönderen numaranın bağlı olduğu WABA'da `APPROVED` olmalıdır. ```bash curl -X POST "https://api.verimerkezi.app/wa/messages" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "type": "template", "template": { "name": "siparis_onayi", "language": { "code": "tr" }, "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "Ahmet" }, { "type": "text", "text": "VM-2026-1234" } ] } ] } }' ``` Yanıt (201): ```json { "ok": true, "id": 48213, "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "to": "905550000000", "type": "template", "status": "sent", "mode": "live", "simulated": false, "credits_used": 1, "balance": 9875 } ``` Hatalar: - 402 `insufficient_credits`: Mesaj krediniz yetersiz. Yanıtta `error.balance` döner. - 403 `insufficient_scope`: Anahtarda `messages:send` kapsamı yok. - 404 `number_not_found`: Numara size ait değil ya da aktif değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 409 `meta_outcome_unknown`: İstek Meta'ya iletildi ama sonucu doğrulanamadı. Aynı anahtarla tekrar göndermeyin; durum olayını bekleyin (`retryable: false`). - 409 `idempotency_in_progress`: Aynı `Idempotency-Key` ile bir istek hâlâ işleniyor. - 422 `invalid_request`: Zorunlu alan eksik ya da biçim hatalı (`error.field` alanı belirtir). - 422 `invalid_phone`: Alıcı numarası geçersiz (en az 10 hane). - 422 `idempotency_key_conflict`: Aynı anahtar farklı bir gövdeyle kullanılmış. - 422 `meta_send_failed`: Meta gönderimi reddetti. `meta_code` ve `meta_subcode` alanlarına bakın (`retryable: true`). - 500 `send_failed`: Sistem hatası; kredi iade edildi. Tekrar deneyebilirsiniz. - 422 `template_not_in_waba`: Şablon gönderen numaranın WABA'sında yok ya da onaylı değil (Meta 132001; `retryable: false`). - 422 `invalid_template_flow_button`: Şablondaki form butonu parametresi hatalı. - 422 `invalid_template_parameters`: Düğme bileşeni kayıtlı şablon tanımına uymuyor: sabit adresli URL butonuna parametre verilmiş (`template.components[i].parameters`), `index`'teki düğme URL değil ya da `index` eksik (`template.components[i].index`), değişkenli URL butonu için parametre yok (`template.components`). Meta'ya gönderilmez. ## Metin mesajı gönder `POST https://api.verimerkezi.app/wa/messages` Serbest metin mesajı gönderir. Yalnız müşteri size son 24 saat içinde yazdıysa teslim edilir; aksi hâlde Meta reddeder. Kapsam: `messages:send` ### Başlıklar - `Idempotency-Key` (): Önerilir. 8–128 karakter. Aynı anahtar + aynı gövde ilk yanıtı döndürür (mesaj ikinci kez gitmez); aynı anahtar + farklı gövde `422 idempotency_key_conflict` döner. 24 saat saklanır. ### İstek gövdesi - `phone_number_id` (string, zorunlu): Gönderimi yapacak numaranızın Meta kimliği. Numara size ait ve aktif olmalıdır. - `to` (string, zorunlu): Alıcının telefon numarası, uluslararası biçimde ve `+` olmadan (ör. `905550000000`). Rakam dışı karakterler atılır; en az 10 hane gerekir. Telefonu olmayan alıcıda `to` yerine `recipient` gönderin. - `recipient` (string): Telefonu görünmeyen kullanıcının WhatsApp kullanıcı kimliği (BSUID, webhook'taki `user_id`). Yalnız `to` boşken kullanılır. Eş anlamlıları: `recipient_id`, `bsuid`. - `context` (object): Alıntılı yanıt. Alıcıda yanıtladığınız mesaj alıntı balonuyla görünür. Tepki mesajıyla birlikte kullanılamaz. - `biz_opaque_callback_data` (string): En fazla 512 karakterlik kendi eşleştirme değeriniz. `message.status.*` olaylarında `data.biz_opaque_callback_data` olarak aynen döner. Göndermezseniz ve `Idempotency-Key` verdiyseniz sunucu bu alanı anahtarın değeriyle kendisi doldurur. - `type` (string): `text` (önerilir; metin mesajında isteğe bağlıdır). - `text` (object | string, zorunlu): Mesaj metni: `{ "body": "..." }` nesnesi ya da düz metin. Nesnede `body` metin değilse `422 invalid_request` (`field: text`). Yalnız 24 saatlik müşteri hizmet penceresi açıkken teslim edilir. ```bash curl -X POST "https://api.verimerkezi.app/wa/messages" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "type": "text", "text": { "body": "Merhaba Ahmet Bey, siparişiniz kargoya verildi." }, "context": { "message_id": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhggQTNFQkQ2RjQ3MEIyQzk0RTE4AA==" } }' ``` Yanıt (201): ```json { "ok": true, "id": 48213, "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "to": "905550000000", "type": "text", "status": "sent", "mode": "live", "simulated": false, "credits_used": 1, "balance": 9875 } ``` Hatalar: - 402 `insufficient_credits`: Mesaj krediniz yetersiz. Yanıtta `error.balance` döner. - 403 `insufficient_scope`: Anahtarda `messages:send` kapsamı yok. - 404 `number_not_found`: Numara size ait değil ya da aktif değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 409 `meta_outcome_unknown`: İstek Meta'ya iletildi ama sonucu doğrulanamadı. Aynı anahtarla tekrar göndermeyin; durum olayını bekleyin (`retryable: false`). - 409 `idempotency_in_progress`: Aynı `Idempotency-Key` ile bir istek hâlâ işleniyor. - 422 `invalid_request`: Zorunlu alan eksik ya da biçim hatalı (`error.field` alanı belirtir). - 422 `invalid_phone`: Alıcı numarası geçersiz (en az 10 hane). - 422 `idempotency_key_conflict`: Aynı anahtar farklı bir gövdeyle kullanılmış. - 422 `meta_send_failed`: Meta gönderimi reddetti. `meta_code` ve `meta_subcode` alanlarına bakın (`retryable: true`). - 500 `send_failed`: Sistem hatası; kredi iade edildi. Tekrar deneyebilirsiniz. - 422 `invalid_context`: `context.message_id` geçerli bir `wamid` değil. ## Medya mesajı gönder `POST https://api.verimerkezi.app/wa/messages` Görsel, video, ses, belge ya da çıkartma gönderir. Medyayı herkese açık bir `https` adresiyle (`link`) ya da `POST /media` ile aldığınız kimlikle (`id`) verin. Serbest mesaj olduğu için 24 saatlik pencere kuralı geçerlidir. Kapsam: `messages:send` ### Başlıklar - `Idempotency-Key` (): Önerilir. 8–128 karakter. Aynı anahtar + aynı gövde ilk yanıtı döndürür (mesaj ikinci kez gitmez); aynı anahtar + farklı gövde `422 idempotency_key_conflict` döner. 24 saat saklanır. ### İstek gövdesi - `phone_number_id` (string, zorunlu): Gönderimi yapacak numaranızın Meta kimliği. Numara size ait ve aktif olmalıdır. - `to` (string, zorunlu): Alıcının telefon numarası, uluslararası biçimde ve `+` olmadan (ör. `905550000000`). Rakam dışı karakterler atılır; en az 10 hane gerekir. Telefonu olmayan alıcıda `to` yerine `recipient` gönderin. - `recipient` (string): Telefonu görünmeyen kullanıcının WhatsApp kullanıcı kimliği (BSUID, webhook'taki `user_id`). Yalnız `to` boşken kullanılır. Eş anlamlıları: `recipient_id`, `bsuid`. - `context` (object): Alıntılı yanıt. Alıcıda yanıtladığınız mesaj alıntı balonuyla görünür. Tepki mesajıyla birlikte kullanılamaz. - `biz_opaque_callback_data` (string): En fazla 512 karakterlik kendi eşleştirme değeriniz. `message.status.*` olaylarında `data.biz_opaque_callback_data` olarak aynen döner. Göndermezseniz ve `Idempotency-Key` verdiyseniz sunucu bu alanı anahtarın değeriyle kendisi doldurur. - `type` (string, zorunlu): Medya türü. - `image | video | audio | document | sticker` (object, zorunlu): `type` ile aynı adlı medya nesnesi. ```bash curl -X POST "https://api.verimerkezi.app/wa/messages" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "type": "document", "document": { "link": "https://cdn.example.com/fatura-2026-10.pdf", "filename": "fatura-2026-10.pdf", "caption": "Ekim ayı faturanız ektedir." } }' ``` Yanıt (201): ```json { "ok": true, "id": 48213, "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "to": "905550000000", "type": "document", "status": "sent", "mode": "live", "simulated": false, "credits_used": 1, "balance": 9875 } ``` Hatalar: - 402 `insufficient_credits`: Mesaj krediniz yetersiz. Yanıtta `error.balance` döner. - 403 `insufficient_scope`: Anahtarda `messages:send` kapsamı yok. - 404 `number_not_found`: Numara size ait değil ya da aktif değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 409 `meta_outcome_unknown`: İstek Meta'ya iletildi ama sonucu doğrulanamadı. Aynı anahtarla tekrar göndermeyin; durum olayını bekleyin (`retryable: false`). - 409 `idempotency_in_progress`: Aynı `Idempotency-Key` ile bir istek hâlâ işleniyor. - 422 `invalid_request`: Zorunlu alan eksik ya da biçim hatalı (`error.field` alanı belirtir). - 422 `invalid_phone`: Alıcı numarası geçersiz (en az 10 hane). - 422 `idempotency_key_conflict`: Aynı anahtar farklı bir gövdeyle kullanılmış. - 422 `meta_send_failed`: Meta gönderimi reddetti. `meta_code` ve `meta_subcode` alanlarına bakın (`retryable: true`). - 500 `send_failed`: Sistem hatası; kredi iade edildi. Tekrar deneyebilirsiniz. - 422 `invalid_media`: `link`/`id` yok, bağlantı `https://` ile başlamıyor ya da `audio.voice` boolean değil. ## Konum mesajı gönder `POST https://api.verimerkezi.app/wa/messages` Haritada açılan bir konum gönderir. Serbest mesajdır: yalnız 24 saatlik müşteri hizmet penceresi açıkken teslim edilir. Kapsam: `messages:send` ### Başlıklar - `Idempotency-Key` (): Önerilir. 8–128 karakter. Aynı anahtar + aynı gövde ilk yanıtı döndürür (mesaj ikinci kez gitmez); aynı anahtar + farklı gövde `422 idempotency_key_conflict` döner. 24 saat saklanır. ### İstek gövdesi - `phone_number_id` (string, zorunlu): Gönderimi yapacak numaranızın Meta kimliği. Numara size ait ve aktif olmalıdır. - `to` (string, zorunlu): Alıcının telefon numarası, uluslararası biçimde ve `+` olmadan (ör. `905550000000`). Rakam dışı karakterler atılır; en az 10 hane gerekir. Telefonu olmayan alıcıda `to` yerine `recipient` gönderin. - `recipient` (string): Telefonu görünmeyen kullanıcının WhatsApp kullanıcı kimliği (BSUID, webhook'taki `user_id`). Yalnız `to` boşken kullanılır. Eş anlamlıları: `recipient_id`, `bsuid`. - `context` (object): Alıntılı yanıt. Alıcıda yanıtladığınız mesaj alıntı balonuyla görünür. Tepki mesajıyla birlikte kullanılamaz. - `biz_opaque_callback_data` (string): En fazla 512 karakterlik kendi eşleştirme değeriniz. `message.status.*` olaylarında `data.biz_opaque_callback_data` olarak aynen döner. Göndermezseniz ve `Idempotency-Key` verdiyseniz sunucu bu alanı anahtarın değeriyle kendisi doldurur. - `type` (string, zorunlu): `location`. - `location` (object, zorunlu): Gönderilecek konum. ```bash curl -X POST "https://api.verimerkezi.app/wa/messages" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "type": "location", "location": { "latitude": 41.0082, "longitude": 28.9784, "name": "Merkez Ofis", "address": "Örnek Mah. Örnek Cad. No:1, İstanbul" } }' ``` Yanıt (201): ```json { "ok": true, "id": 48213, "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "to": "905550000000", "type": "location", "status": "sent", "mode": "live", "simulated": false, "credits_used": 1, "balance": 9875 } ``` Hatalar: - 402 `insufficient_credits`: Mesaj krediniz yetersiz. Yanıtta `error.balance` döner. - 403 `insufficient_scope`: Anahtarda `messages:send` kapsamı yok. - 404 `number_not_found`: Numara size ait değil ya da aktif değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 409 `meta_outcome_unknown`: İstek Meta'ya iletildi ama sonucu doğrulanamadı. Aynı anahtarla tekrar göndermeyin; durum olayını bekleyin (`retryable: false`). - 409 `idempotency_in_progress`: Aynı `Idempotency-Key` ile bir istek hâlâ işleniyor. - 422 `invalid_request`: Zorunlu alan eksik ya da biçim hatalı (`error.field` alanı belirtir). - 422 `invalid_phone`: Alıcı numarası geçersiz (en az 10 hane). - 422 `idempotency_key_conflict`: Aynı anahtar farklı bir gövdeyle kullanılmış. - 422 `meta_send_failed`: Meta gönderimi reddetti. `meta_code` ve `meta_subcode` alanlarına bakın (`retryable: true`). - 500 `send_failed`: Sistem hatası; kredi iade edildi. Tekrar deneyebilirsiniz. - 422 `invalid_location`: Enlem/boylam eksik ya da aralık dışı, ad/adres çok uzun (`error.field` hatalı alanı gösterir). - 422 `invalid_context`: `context.message_id` geçerli bir `wamid` değil. ## Kişi kartı gönder `POST https://api.verimerkezi.app/wa/messages` Bir ya da birden çok kişi kartı (vCard) gönderir. Serbest mesajdır: yalnız 24 saatlik müşteri hizmet penceresi açıkken teslim edilir. Kapsam: `messages:send` ### Başlıklar - `Idempotency-Key` (): Önerilir. 8–128 karakter. Aynı anahtar + aynı gövde ilk yanıtı döndürür (mesaj ikinci kez gitmez); aynı anahtar + farklı gövde `422 idempotency_key_conflict` döner. 24 saat saklanır. ### İstek gövdesi - `phone_number_id` (string, zorunlu): Gönderimi yapacak numaranızın Meta kimliği. Numara size ait ve aktif olmalıdır. - `to` (string, zorunlu): Alıcının telefon numarası, uluslararası biçimde ve `+` olmadan (ör. `905550000000`). Rakam dışı karakterler atılır; en az 10 hane gerekir. Telefonu olmayan alıcıda `to` yerine `recipient` gönderin. - `recipient` (string): Telefonu görünmeyen kullanıcının WhatsApp kullanıcı kimliği (BSUID, webhook'taki `user_id`). Yalnız `to` boşken kullanılır. Eş anlamlıları: `recipient_id`, `bsuid`. - `context` (object): Alıntılı yanıt. Alıcıda yanıtladığınız mesaj alıntı balonuyla görünür. Tepki mesajıyla birlikte kullanılamaz. - `biz_opaque_callback_data` (string): En fazla 512 karakterlik kendi eşleştirme değeriniz. `message.status.*` olaylarında `data.biz_opaque_callback_data` olarak aynen döner. Göndermezseniz ve `Idempotency-Key` verdiyseniz sunucu bu alanı anahtarın değeriyle kendisi doldurur. - `type` (string, zorunlu): `contacts`. - `contacts` (array, zorunlu): 1–20 kişi kartı (Meta biçimi). Bilinmeyen alanlar Meta'ya iletilmez. ```bash curl -X POST "https://api.verimerkezi.app/wa/messages" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "type": "contacts", "contacts": [ { "name": { "formatted_name": "Destek Hattı", "first_name": "Destek", "last_name": "Hattı" }, "phones": [ { "phone": "+905550000000", "type": "WORK", "wa_id": "905550000000" } ], "emails": [ { "email": "destek@example.com", "type": "WORK" } ], "org": { "company": "Örnek A.Ş.", "title": "Müşteri Hizmetleri" }, "urls": [ { "url": "https://example.com", "type": "WORK" } ] } ] }' ``` Yanıt (201): ```json { "ok": true, "id": 48213, "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "to": "905550000000", "type": "contacts", "status": "sent", "mode": "live", "simulated": false, "credits_used": 1, "balance": 9875 } ``` Hatalar: - 402 `insufficient_credits`: Mesaj krediniz yetersiz. Yanıtta `error.balance` döner. - 403 `insufficient_scope`: Anahtarda `messages:send` kapsamı yok. - 404 `number_not_found`: Numara size ait değil ya da aktif değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 409 `meta_outcome_unknown`: İstek Meta'ya iletildi ama sonucu doğrulanamadı. Aynı anahtarla tekrar göndermeyin; durum olayını bekleyin (`retryable: false`). - 409 `idempotency_in_progress`: Aynı `Idempotency-Key` ile bir istek hâlâ işleniyor. - 422 `invalid_request`: Zorunlu alan eksik ya da biçim hatalı (`error.field` alanı belirtir). - 422 `invalid_phone`: Alıcı numarası geçersiz (en az 10 hane). - 422 `idempotency_key_conflict`: Aynı anahtar farklı bir gövdeyle kullanılmış. - 422 `meta_send_failed`: Meta gönderimi reddetti. `meta_code` ve `meta_subcode` alanlarına bakın (`retryable: true`). - 500 `send_failed`: Sistem hatası; kredi iade edildi. Tekrar deneyebilirsiniz. - 422 `invalid_contacts`: Kart sayısı 1–20 dışında ya da zorunlu alan eksik (ör. `field: contacts[0].name.formatted_name`). - 422 `invalid_context`: `context.message_id` geçerli bir `wamid` değil. ## Etkileşimli mesaj gönder `POST https://api.verimerkezi.app/wa/messages` Yanıt düğmeleri, liste, bağlantı düğmesi ya da konum isteği gönderir. Gövde Meta Cloud API ile aynıdır; Meta sınırları gönderimden önce doğrulanır. Serbest mesajdır: yalnız 24 saatlik pencere açıkken teslim edilir. Kapsam: `messages:send` ### Başlıklar - `Idempotency-Key` (): Önerilir. 8–128 karakter. Aynı anahtar + aynı gövde ilk yanıtı döndürür (mesaj ikinci kez gitmez); aynı anahtar + farklı gövde `422 idempotency_key_conflict` döner. 24 saat saklanır. ### İstek gövdesi - `phone_number_id` (string, zorunlu): Gönderimi yapacak numaranızın Meta kimliği. Numara size ait ve aktif olmalıdır. - `to` (string, zorunlu): Alıcının telefon numarası, uluslararası biçimde ve `+` olmadan (ör. `905550000000`). Rakam dışı karakterler atılır; en az 10 hane gerekir. Telefonu olmayan alıcıda `to` yerine `recipient` gönderin. - `recipient` (string): Telefonu görünmeyen kullanıcının WhatsApp kullanıcı kimliği (BSUID, webhook'taki `user_id`). Yalnız `to` boşken kullanılır. Eş anlamlıları: `recipient_id`, `bsuid`. - `context` (object): Alıntılı yanıt. Alıcıda yanıtladığınız mesaj alıntı balonuyla görünür. Tepki mesajıyla birlikte kullanılamaz. - `biz_opaque_callback_data` (string): En fazla 512 karakterlik kendi eşleştirme değeriniz. `message.status.*` olaylarında `data.biz_opaque_callback_data` olarak aynen döner. Göndermezseniz ve `Idempotency-Key` verdiyseniz sunucu bu alanı anahtarın değeriyle kendisi doldurur. - `type` (string, zorunlu): `interactive`. - `interactive` (object, zorunlu): Meta `interactive` nesnesi. ```bash curl -X POST "https://api.verimerkezi.app/wa/messages" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "type": "interactive", "interactive": { "type": "button", "header": { "type": "text", "text": "Randevu" }, "body": { "text": "Yarın 14:00 randevunuzu onaylıyor musunuz?" }, "footer": { "text": "Örnek Klinik" }, "action": { "buttons": [ { "type": "reply", "reply": { "id": "onay", "title": "Onaylıyorum" } }, { "type": "reply", "reply": { "id": "degistir", "title": "Saati değiştir" } } ] } } }' ``` Yanıt (201): ```json { "ok": true, "id": 48213, "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "to": "905550000000", "type": "interactive", "status": "sent", "mode": "live", "simulated": false, "credits_used": 1, "balance": 9875 } ``` Hatalar: - 402 `insufficient_credits`: Mesaj krediniz yetersiz. Yanıtta `error.balance` döner. - 403 `insufficient_scope`: Anahtarda `messages:send` kapsamı yok. - 404 `number_not_found`: Numara size ait değil ya da aktif değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 409 `meta_outcome_unknown`: İstek Meta'ya iletildi ama sonucu doğrulanamadı. Aynı anahtarla tekrar göndermeyin; durum olayını bekleyin (`retryable: false`). - 409 `idempotency_in_progress`: Aynı `Idempotency-Key` ile bir istek hâlâ işleniyor. - 422 `invalid_request`: Zorunlu alan eksik ya da biçim hatalı (`error.field` alanı belirtir). - 422 `invalid_phone`: Alıcı numarası geçersiz (en az 10 hane). - 422 `idempotency_key_conflict`: Aynı anahtar farklı bir gövdeyle kullanılmış. - 422 `meta_send_failed`: Meta gönderimi reddetti. `meta_code` ve `meta_subcode` alanlarına bakın (`retryable: true`). - 500 `send_failed`: Sistem hatası; kredi iade edildi. Tekrar deneyebilirsiniz. - 422 `invalid_interactive`: `interactive` nesnesi hatalı ya da Meta sınırını aşıyor (`error.field` hatalı alanı gösterir, ör. `interactive.action.buttons[1].reply.title`). - 422 `invalid_context`: `context.message_id` geçerli bir `wamid` değil. ## Tepki gönder `POST https://api.verimerkezi.app/wa/messages` Bir mesaja emoji tepkisi verir ya da mevcut tepkiyi kaldırır. Kapsam: `messages:send` ### Başlıklar - `Idempotency-Key` (): Önerilir. 8–128 karakter. Aynı anahtar + aynı gövde ilk yanıtı döndürür (mesaj ikinci kez gitmez); aynı anahtar + farklı gövde `422 idempotency_key_conflict` döner. 24 saat saklanır. ### İstek gövdesi - `phone_number_id` (string, zorunlu): Gönderimi yapacak numaranızın Meta kimliği. Numara size ait ve aktif olmalıdır. - `to` (string, zorunlu): Alıcının telefon numarası, uluslararası biçimde ve `+` olmadan (ör. `905550000000`). Rakam dışı karakterler atılır; en az 10 hane gerekir. Telefonu olmayan alıcıda `to` yerine `recipient` gönderin. - `recipient` (string): Telefonu görünmeyen kullanıcının WhatsApp kullanıcı kimliği (BSUID, webhook'taki `user_id`). Yalnız `to` boşken kullanılır. Eş anlamlıları: `recipient_id`, `bsuid`. - `biz_opaque_callback_data` (string): En fazla 512 karakterlik kendi eşleştirme değeriniz. `message.status.*` olaylarında `data.biz_opaque_callback_data` olarak aynen döner. Göndermezseniz ve `Idempotency-Key` verdiyseniz sunucu bu alanı anahtarın değeriyle kendisi doldurur. - `type` (string, zorunlu): `reaction` olmalıdır. - `reaction` (object, zorunlu): Tepki. ```bash curl -X POST "https://api.verimerkezi.app/wa/messages" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "type": "reaction", "reaction": { "message_id": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhggQTNFQkQ2RjQ3MEIyQzk0RTE4AA==", "emoji": "👍" } }' ``` Yanıt (201): ```json { "ok": true, "id": 48213, "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "to": "905550000000", "type": "reaction", "status": "sent", "mode": "live", "simulated": false, "credits_used": 1, "balance": 9875 } ``` Hatalar: - 402 `insufficient_credits`: Mesaj krediniz yetersiz. Yanıtta `error.balance` döner. - 403 `insufficient_scope`: Anahtarda `messages:send` kapsamı yok. - 404 `number_not_found`: Numara size ait değil ya da aktif değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 409 `meta_outcome_unknown`: İstek Meta'ya iletildi ama sonucu doğrulanamadı. Aynı anahtarla tekrar göndermeyin; durum olayını bekleyin (`retryable: false`). - 409 `idempotency_in_progress`: Aynı `Idempotency-Key` ile bir istek hâlâ işleniyor. - 422 `invalid_request`: Zorunlu alan eksik ya da biçim hatalı (`error.field` alanı belirtir). - 422 `invalid_phone`: Alıcı numarası geçersiz (en az 10 hane). - 422 `idempotency_key_conflict`: Aynı anahtar farklı bir gövdeyle kullanılmış. - 422 `meta_send_failed`: Meta gönderimi reddetti. `meta_code` ve `meta_subcode` alanlarına bakın (`retryable: true`). - 500 `send_failed`: Sistem hatası; kredi iade edildi. Tekrar deneyebilirsiniz. - 422 `invalid_reaction`: `reaction.message_id` geçerli bir `wamid` değil ya da `emoji` tek emoji değil. ## Form mesajı gönder `POST https://api.verimerkezi.app/wa/messages` Müşteriye WhatsApp içinde açılan bir form (WhatsApp Flow) gönderir. Form yanıtı `flow.completed` olayıyla gelir. Serbest mesaj kuralları geçerlidir: 24 saat penceresi ve 1 kredi. Kapsam: `messages:send` ### Başlıklar - `Idempotency-Key` (): Önerilir. 8–128 karakter. Aynı anahtar + aynı gövde ilk yanıtı döndürür (mesaj ikinci kez gitmez); aynı anahtar + farklı gövde `422 idempotency_key_conflict` döner. 24 saat saklanır. ### İstek gövdesi - `phone_number_id` (string, zorunlu): Gönderimi yapacak numaranızın Meta kimliği. Numara size ait ve aktif olmalıdır. - `to` (string, zorunlu): Alıcının telefon numarası, uluslararası biçimde ve `+` olmadan (ör. `905550000000`). Rakam dışı karakterler atılır; en az 10 hane gerekir. Telefonu olmayan alıcıda `to` yerine `recipient` gönderin. - `recipient` (string): Telefonu görünmeyen kullanıcının WhatsApp kullanıcı kimliği (BSUID, webhook'taki `user_id`). Yalnız `to` boşken kullanılır. Eş anlamlıları: `recipient_id`, `bsuid`. - `context` (object): Alıntılı yanıt. Alıcıda yanıtladığınız mesaj alıntı balonuyla görünür. Tepki mesajıyla birlikte kullanılamaz. - `biz_opaque_callback_data` (string): En fazla 512 karakterlik kendi eşleştirme değeriniz. `message.status.*` olaylarında `data.biz_opaque_callback_data` olarak aynen döner. Göndermezseniz ve `Idempotency-Key` verdiyseniz sunucu bu alanı anahtarın değeriyle kendisi doldurur. - `type` (string, zorunlu): `flow` olmalıdır. - `flow` (object, zorunlu): WhatsApp Flow (form) mesajı. ```bash curl -X POST "https://api.verimerkezi.app/wa/messages" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "type": "flow", "flow": { "flow_id": "1234567890123456", "flow_token": "crm-basvuru-42", "cta": "Formu Aç", "body": "Başvurunuzu 1 dakikada tamamlayın.", "screen": "WELCOME", "data": { "ad": "Ahmet" } } }' ``` Yanıt (201): ```json { "ok": true, "id": 48213, "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "to": "905550000000", "type": "flow", "status": "sent", "mode": "live", "simulated": false, "credits_used": 1, "balance": 9875, "flow": { "flow_token": "crm-basvuru-42", "flow_id": "1234567890123456", "flow_name": null, "mode": "published" } } ``` Hatalar: - 402 `insufficient_credits`: Mesaj krediniz yetersiz. Yanıtta `error.balance` döner. - 403 `insufficient_scope`: Anahtarda `messages:send` kapsamı yok. - 404 `number_not_found`: Numara size ait değil ya da aktif değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 409 `meta_outcome_unknown`: İstek Meta'ya iletildi ama sonucu doğrulanamadı. Aynı anahtarla tekrar göndermeyin; durum olayını bekleyin (`retryable: false`). - 409 `idempotency_in_progress`: Aynı `Idempotency-Key` ile bir istek hâlâ işleniyor. - 422 `invalid_request`: Zorunlu alan eksik ya da biçim hatalı (`error.field` alanı belirtir). - 422 `invalid_phone`: Alıcı numarası geçersiz (en az 10 hane). - 422 `idempotency_key_conflict`: Aynı anahtar farklı bir gövdeyle kullanılmış. - 422 `meta_send_failed`: Meta gönderimi reddetti. `meta_code` ve `meta_subcode` alanlarına bakın (`retryable: true`). - 500 `send_failed`: Sistem hatası; kredi iade edildi. Tekrar deneyebilirsiniz. - 422 `invalid_flow`: `flow` nesnesi hatalı (`error.field` alanı belirtir). ## Okundu işaretle ve yazıyor göster `POST https://api.verimerkezi.app/wa/messages/read` Gelen bir mesajı okundu olarak işaretler (mavi tik). `typing: true` ile alıcıda yaklaşık 25 saniye "yazıyor…" göstergesi çıkar. Kredi düşmez. Kapsam: `messages:send` ### İstek gövdesi - `phone_number_id` (string, zorunlu): Mesajın geldiği numaranızın Meta kimliği. - `message_id` (string, zorunlu): Okundu işaretlenecek gelen mesajın `wamid` değeri. - `typing` (boolean): "Yazıyor…" göstergesi. ```bash curl -X POST "https://api.verimerkezi.app/wa/messages/read" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "message_id": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhggQTNFQkQ2RjQ3MEIyQzk0RTE4AA==", "typing": true }' ``` Yanıt (200): ```json { "ok": true, "marked_read": true, "typing": true } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `messages:send` kapsamı yok. - 404 `number_not_found`: Numara size ait değil ya da aktif değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 422 `invalid_request`: `phone_number_id` ya da `message_id` eksik. - 422 `meta_error`: Meta isteği reddetti (`meta_code`). ## Mesaj kayıtlarını listele `GET https://api.verimerkezi.app/wa/messages` Bir numaranın gelen ve giden mesaj kayıtlarını zamana göre artan sırada döndürür. Webhook teslimi kaçtığında olayları geri almak (uzlaştırma) için kullanın. Kredi düşmez. Kapsam: `messages:read` ya da `inbox:read` ### Sorgu parametreleri - `phone_number_id` (string, zorunlu): Numaranızın Meta kimliği. - `since` (string): ISO 8601 başlangıç zamanı. En fazla 90 gün geriye gider; verilmezse son 24 saat. - `cursor` (integer): Önceki yanıttaki `next_cursor`. Bu kimlikten büyük kayıtlar döner. - `limit` (integer): Sayfa boyutu (1–100). ```bash curl -X GET "https://api.verimerkezi.app/wa/messages?phone_number_id=1234567890&since=2026-10-09T00%3A00%3A00Z&limit=100" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "phone_number_id": "1234567890", "data": [ { "id": 248117, "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhggQTNFQkQ2RjQ3MEIyQzk0RTE4AA==", "direction": "inbound", "type": "text", "status": "received", "from": "905550000000", "to": null, "user_id": null, "username": null, "text": "Merhaba, siparişim ne durumda?", "media": null, "template_name": null, "campaign_id": null, "is_test": false, "error": null, "created_at": "2026-10-10T10:00:00+03:00", "sent_at": null, "delivered_at": null, "read_at": null, "failed_at": null, "context": null, "reaction": null, "location": null, "contacts": null, "original_message_id": null, "button": null } ], "has_more": true, "next_cursor": 248117 } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `messages:read` ya da `inbox:read` kapsamı yok. - 404 `number_not_found`: Numara size ait değil. - 422 `invalid_request`: `phone_number_id` eksik ya da rakam değil. - 422 `invalid_since`: `since` ISO 8601 değil. ## Test gelen mesajı oluştur `POST https://api.verimerkezi.app/wa/test/inbound` Yalnız test anahtarıyla çalışır. Sahte bir gelen mesajı gerçek gelen mesaj hattından geçirir ve imzalı `message.received`, `message.revoked` ya da `message.edited` olayını (`app_contact_sync` ile `contact.synced` olayını) aktif webhook aboneliklerinize gönderir. Meta'ya istek gitmez. ### İstek gövdesi - `phone_number_id` (string, zorunlu): Numaranızın Meta kimliği. - `type` (string): Sahte olay türü. `app_contact_sync` örnek bir `contact.synced` olayı gönderir (`add`, `update`, `remove`; rehbere kayıt yazılmaz). - `from` (string): Gönderen telefon (8–15 hane). - `text` (string): Mesaj metni (en fazla 4096). `text` ve `edit` için zorunlu. - `original_message_id` (string): `revoke` ve `edit` için hedef mesajın `wamid` değeri. ```bash curl -X POST "https://api.verimerkezi.app/wa/test/inbound" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "type": "text", "from": "905550000000", "text": "Merhaba, bu bir test mesajıdır." }' ``` Yanıt (202): ```json { "ok": true, "mode": "test", "simulated": true, "phone_number_id": "1234567890", "injected": { "wamid": "test.in.8f2c1a9b" }, "note": "İmzalı olay(lar) hesabın aktif webhook aboneliklerine gönderildi. Meta'ya istek gitmedi." } ``` Hatalar: - 403 `test_only`: Bu uç yalnız `vmk_test_` anahtarıyla kullanılır. - 404 `number_not_found`: Numara size ait değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 422 `invalid_request`: Parametre hatalı. - 500 `test_inbound_failed`: Sahte mesaj işlenemedi. --- # Medya Gönderim için dosyayı Meta'ya yükleyin; müşterilerinizin gönderdiği medyayı listeleyin ve indirin. ## Media nesnesi - `media_id` (integer): Veri Merkezi depo kimliği. Webhook'taki `data.media.media_id` ile aynıdır. - `client_id` (integer): Medyanın ait olduğu numara kaydı. - `kind` (string): Medya türü. - `source` (string): Kaynak. - `mime` (string): Dosyanın MIME türü. - `size` (integer): Bayt cinsinden boyut. - `sha256` (string): Dosyanın SHA-256 özeti (yalnız `?meta=1` yanıtında). - `original_name` (string): Özgün dosya adı. - `created_at` (string): Kayıt zamanı. - `download_url` (string): İndirme adresi. ## Medya yükle `POST https://api.verimerkezi.app/wa/media` Dosyayı herkese açık bir adreste yayınlamadan doğrudan Meta'ya yükler ve bir Meta medya kimliği döndürür. Kimlik 30 gün geçerlidir; `POST /messages` içinde `{type}.id` olarak kullanılır. İstek `multipart/form-data` olmalıdır. Kapsam: `messages:send` ### İstek gövdesi - `file` (file, zorunlu): Yüklenecek tek dosya (multipart alanı). - `phone_number_id` (string, zorunlu): Dosyanın kullanılacağı numara (aynı WABA'da geçerlidir). ```bash curl -X POST "https://api.verimerkezi.app/wa/media" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (201): ```json { "ok": true, "media_id": "1075907555339682", "type": "document", "mime_type": "application/pdf", "file_size": 183421, "phone_number_id": "1234567890", "expires_at": "2026-11-09T09:00:00Z", "mode": "live" } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `messages:send` kapsamı yok. - 404 `number_not_found`: Numara size ait değil ya da aktif değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 413 `media_too_large`: Dosya türün sınırını aşıyor (`max_bytes` ve `type` alanları döner). - 415 `unsupported_media_type`: İstek `multipart/form-data` değil. - 415 `media_invalid_format`: Desteklenmeyen dosya türü; içerik gerçekten doğrulanır. - 422 `invalid_request`: `phone_number_id` ya da `file` eksik. - 422 `invalid_media`: Dosya boş. - 422 `upload_failed`: Dosya alınamadı. - 422 `meta_upload_failed`: Meta dosyayı kabul etmedi. ## Medya kayıtlarını listele `GET https://api.verimerkezi.app/wa/media` Hesabınıza ait gelen ve giden medya kayıtlarını yeniden eskiye döndürür. Kapsam: `inbox:read` ya da `messages:read` ### Sorgu parametreleri - `limit` (integer): Sayfa boyutu (1–100). - `cursor` (integer): Bu `media_id` değerinden küçük kayıtlar döner (önceki yanıttaki `next_cursor`). - `source` (string): Yön süzgeci. - `kind` (string): Tür süzgeci. ```bash curl -X GET "https://api.verimerkezi.app/wa/media?limit=50&source=inbound" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "media": [ { "media_id": 13115, "client_id": 27, "kind": "image", "source": "inbound", "mime": "image/jpeg", "size": 108919, "original_name": null, "created_at": "2026-10-09 20:14:58", "download_url": "https://api.verimerkezi.app/wa/media/13115" } ], "paging": { "limit": 50, "has_more": true, "next_cursor": 13115 } } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `inbox:read` ya da `messages:read` kapsamı yok. ## Medya dosyasını indir `GET https://api.verimerkezi.app/wa/media/{media_id}` Dosyanın kendisini ikili akış olarak döndürür. Webhook'ta gelen `data.media.media_id` değerini kullanın. `?meta=1` ile dosya yerine üstveri döner. Kapsam: `inbox:read` ya da `messages:read` ### Yol parametreleri - `media_id` (integer, zorunlu): Medya depo kimliği (yalnız rakam). ### Sorgu parametreleri - `meta` (integer): `1` dosya yerine JSON üstveri döndürür. ### Başlıklar - `Range` (): Parça indirme, ör. `bytes=0-1048575`. Yanıt `206 Partial Content` olur. - `If-None-Match` (): Önceki yanıttaki `ETag`. Dosya değişmediyse `304 Not Modified` döner. ```bash curl -X GET "https://api.verimerkezi.app/wa/media/13115?meta=1" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "media": { "media_id": 13115, "client_id": 27, "kind": "image", "source": "inbound", "mime": "image/jpeg", "size": 108919, "sha256": "5d174a5bd79a7c91cc0ce4be0be40fdf61f6ae03c2b1e8d9f0a7b6c5d4e3f2a1", "original_name": null, "created_at": "2026-10-09 20:14:58", "download_url": "https://api.verimerkezi.app/wa/media/13115" } } ``` Hatalar: - 400 `invalid_media_id`: `media_id` yalnız rakamlardan oluşmalı. - 403 `insufficient_scope`: Anahtarda `inbox:read` ya da `messages:read` kapsamı yok. - 404 `media_not_found`: Kayıt yok ya da sizin hesabınıza ait değil (varlığı sızdırılmaz). - 404 `media_file_missing`: Kayıt var, dosya depoda bulunamadı. - 416 `range_not_satisfiable`: İstenen bayt aralığı geçersiz. ## Medya başlıklarını getir `HEAD https://api.verimerkezi.app/wa/media/{media_id}` Dosyayı aktarmadan `Content-Length`, `Content-Type` ve `ETag` başlıklarını döndürür. Kapsam: `inbox:read` ya da `messages:read` ### Yol parametreleri - `media_id` (integer, zorunlu): Medya depo kimliği. ```bash curl -X HEAD "https://api.verimerkezi.app/wa/media/13115" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json {} ``` Hatalar: - 400 `invalid_media_id`: `media_id` yalnız rakamlardan oluşmalı. - 403 `insufficient_scope`: Anahtarda `inbox:read` ya da `messages:read` kapsamı yok. - 404 `media_not_found`: Kayıt yok ya da size ait değil. --- # Şablonlar Meta onaylı mesaj şablonlarını oluşturun, doğrulayın, listeleyin, düzenleyin, kopyalayın ve silin; başlık medyasını yükleyin. Şablonlar işletme hesabı (WABA) düzeyindedir. ## Template nesnesi - `id` (integer): Veri Merkezi şablon kimliği. - `meta_template_id` (string): Meta şablon kimliği. - `name` (string): Şablon adı. - `language` (string): Dil kodu. - `category` (string): Kategori. Meta kategoriyi değiştirdiyse güncel kategori. - `previous_category` (string): Meta kategoriyi değiştirdiyse önceki kategori; yoksa `null`. - `parameter_format` (string): Değişken biçimi: `POSITIONAL` (`{{1}}`) ya da `NAMED` (`{{ad_soyad}}`). Oluşturulduktan sonra değişmez. - `status` (string): Durum. - `rejected_reason` (string): Meta'nın ham ret nedeni (`INVALID_FORMAT`, `ABUSIVE_CONTENT`, `INCORRECT_CATEGORY`, `SCAM`, `TAG_CONTENT_MISMATCH` …). Yalnız `REJECTED` iken anlamlıdır. - `rejected_reason_text` (string): Ret nedeninin Türkçe açıklaması. Yalnız `REJECTED` iken dolu. - `quality_score` (string): Şablon kalite puanı (`GREEN`, `YELLOW`, `RED`, `UNKNOWN`). 2.25.0'dan beri listede de döner. - `components` (array): Bileşenler. - `header_media` (object): Medya başlığı (`IMAGE` / `VIDEO` / `DOCUMENT`) olan şablonlarda başlık medyası; liste ve ayrıntıda döner, medya başlığı yoksa `null`. Veri Merkezi'nde dosya kopyası varsa `{ url, mime_type, file_size, sha256, filename, format, temporary: false }` (`url` kalıcı, herkese açık bağlantıdır). Kopya yoksa Meta onay örneği `{ url, format, temporary: true }` (süreli bağlantı, gönderimde güvenmeyin). - `header_media_ref` (integer): Yalnız ayrıntıda: medya başlığının Veri Merkezi'ndeki dosya kopyası (`POST /templates/media` yanıtındaki değer); yoksa `null`. - `edit_limit` (object): Yalnız ayrıntıda: düzenleme durumu `{ editable, edits_last_30d, max_edits_30d, next_edit_at }`. `editable: false` ise durum düzenlemeye uygun değildir ya da onaylı şablonun sınırı dolmuştur (`next_edit_at`). Yalnız Veri Merkezi üzerinden yapılan düzenlemeleri sayar. - `waba_id` (string): Şablonun ait olduğu WABA. - `phone_number_ids` (array): Bu şablonu kullanabilen, aynı WABA'daki aktif numaralar (listede). - `phone_number_id` (string): Eski alan: WABA'nın ilk aktif numarası. Yerine `phone_number_ids` kullanın. - `display_phone_number` (string): Eski alan. - `created_at` (string): Oluşturulma zamanı. - `submitted_at` (string): Meta'ya gönderilme zamanı (ayrıntıda). - `status_updated_at` (string): Son durum değişikliği (ayrıntıda). ## Şablonları listele `GET https://api.verimerkezi.app/wa/templates` Hesabınızın WABA'larındaki şablonları yeniden eskiye döndürür. Süzgeçler ve imleçle sayfalama desteklenir. Kapsam: `templates:read` ### Sorgu parametreleri - `status` (string): Durum süzgeci. - `category` (string): Kategori süzgeci. - `language` (string): Dil kodu (ör. `tr`). - `q` (string): Ad içinde arama. - `phone_number_id` (string): Yalnız bu numaranın WABA'sındaki şablonlar. - `cursor` (integer): Önceki yanıttaki `next_cursor` (bu kimlikten küçük kayıtlar). - `limit` (integer): Sayfa boyutu (1–100). - `refresh` (boolean): `1` okumadan önce Meta'dan senkron dener (WABA başına dakikada 1). Sınırdaysa hata vermez; `refreshed: false` döner. ```bash curl -X GET "https://api.verimerkezi.app/wa/templates?status=APPROVED" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "templates": [ { "id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "language": "tr", "category": "UTILITY", "previous_category": null, "parameter_format": "POSITIONAL", "status": "APPROVED", "rejected_reason": null, "rejected_reason_text": null, "quality_score": "GREEN", "components": [ { "type": "BODY", "text": "Merhaba {{1}}, {{2}} numaralı siparişiniz onaylandı." } ], "header_media": null, "created_at": "2026-09-20 10:12:00", "waba_id": "1029384756", "phone_number_ids": [ "1234567890" ], "phone_number_id": "1234567890", "display_phone_number": "+90 555 000 00 00", "client_id": 4 } ], "has_more": false, "next_cursor": null } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `templates:read` kapsamı yok. ## Şablon oluştur `POST https://api.verimerkezi.app/wa/templates` Şablonu oluşturup Meta onayına gönderir ve `PENDING` döndürür (Meta anında `APPROVED` ya da `REJECTED` de verebilir). Kurallar Meta'ya gitmeden önce denetlenir; ihlaller `error.field` alan yoluyla birlikte `422 template_invalid` ya da `422 invalid_request` olarak döner. Aynı WABA'da aynı ad ve dilde şablon varsa `409 template_exists`. Meta reddederse yerelde taslak artığı kalmaz. Sonuç `template.approved` ya da `template.rejected` olayıyla bildirilir. Kapsam: `templates:write` ### İstek gövdesi - `name` (string, zorunlu): Şablon adı (1–512). Yalnız küçük harf, rakam ve alt çizgi; büyük harf ve diğer karakterler `_` olur. Yanıttaki `name` esastır. Silinen onaylı şablonun adı 30 gün yeniden kullanılamaz. - `language` (string, zorunlu): Dil kodu. - `category` (string, zorunlu): Kategori. - `components` (array, zorunlu): Meta bileşenleri: `HEADER` (`TEXT` en çok 1 değişkenli, `IMAGE`, `VIDEO`, `DOCUMENT`, `LOCATION`), `BODY`, `FOOTER`, `BUTTONS` (`QUICK_REPLY`, `URL`, `PHONE_NUMBER`, `COPY_CODE`, `FLOW`, `OTP`). Değişken için örnek zorunludur: `POSITIONAL` biçimde `example.body_text` / `example.header_text`, `NAMED` biçimde `example.body_text_named_params` / `example.header_text_named_params` (`[{ param_name, example }]`). Ayrıntı: Şablonlar rehberi. - `parameter_format` (string): `POSITIONAL` (varsayılan, `{{1}}`, `{{2}}` …) ya da `NAMED` (`{{ad_soyad}}`: küçük harf, rakam, `_`; harfle başlar; benzersiz). Oluşturduktan sonra değişmez. - `allow_category_change` (boolean): Meta'ya aynen iletilir: `true` ise Meta kategoriyi kendisi değiştirebilir. - `message_send_ttl_seconds` (integer): Meta'ya aynen iletilir: mesajın geçerlilik süresi (saniye; en çok 2592000, `-1` varsayılan süre). - `cta_url_link_tracking_opted_out` (boolean): Meta'ya aynen iletilir: URL düğmelerinde bağlantı izlemesinden çıkış. - `header_media_ref` (integer): `POST /templates/media` yanıtındaki dosya kopyası. Medya başlığında `example.header_handle`, `header_media_ref` ya da `header_media_url` değerlerinden en az biri gerekir; hiçbiri yoksa `422 invalid_request` (`field: header_media_ref`). `example.header_handle` olarak `https://` adres kabul edilmez. - `waba_id` (string): Hedef WABA. Hesabınızda tek WABA varsa gerekmez; birden çok varsa zorunludur. - `phone_number_id` (string): `waba_id` yerine; WABA numaradan çözülür. - `header_media_url` (string): Eski yol (2.22): medya başlığı için herkese açık `https://` adres; dosya indirilip Meta'ya örnek olarak yüklenir. Yerine `POST /templates/media` + `header_media_ref` önerilir. - `optout_footer` (boolean): Yalnız `MARKETING`: standart abonelikten çıkış alt bilgisini ekler (varsayılan `true`). Kendi alt bilginizi kullanmak için `false` verin. - `add_security_recommendation` (boolean): `AUTHENTICATION`: "Bu kodu kimseyle paylaşmayın" satırı. - `code_expiration_minutes` (integer): `AUTHENTICATION`: kod geçerlilik süresi (dakika). - `otp_type` (string): `AUTHENTICATION`: buton türü. - `otp_button_text` (string): `AUTHENTICATION`: buton metni. - `autofill_text` (string): `ONE_TAP`: otomatik doldurma metni. - `package_name` (string): `ONE_TAP`: Android uygulama paket adı. - `signature_hash` (string): `ONE_TAP`: uygulama imza özeti (11 karakter). ```bash curl -X POST "https://api.verimerkezi.app/wa/templates" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "waba_id": "1029384756", "name": "police_yenileme_hatirlatma", "language": "tr", "category": "UTILITY", "header_media_url": "https://cdn.example.com/logo.jpg", "components": [ { "type": "HEADER", "format": "IMAGE" }, { "type": "BODY", "text": "Sayın {{1}}, {{2}} plakalı aracınızın poliçesi {{3}} tarihinde sona eriyor.", "example": { "body_text": [ [ "Ahmet Yılmaz", "34 ABC 123", "12.10.2026" ] ] } }, { "type": "FOOTER", "text": "Firma Sigorta" }, { "type": "BUTTONS", "buttons": [ { "type": "QUICK_REPLY", "text": "Teklif istiyorum" } ] } ] }' ``` Yanıt (202): ```json { "ok": true, "id": 513, "meta_template_id": "1249033812345679", "name": "police_yenileme_hatirlatma", "language": "tr", "category": "UTILITY", "previous_category": null, "parameter_format": "POSITIONAL", "status": "PENDING", "rejected_reason": null, "rejected_reason_text": null, "quality_score": null, "waba_id": "1029384756", "phone_number_id": "1234567890", "created_at": "2026-10-10T09:00:00+03:00", "submitted_at": "2026-10-10T09:00:02+03:00", "status_updated_at": null } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `templates:write` kapsamı yok. - 404 `number_not_found`: Numara/WABA size ait değil ya da aktif WABA yok. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 409 `template_exists`: Aynı WABA'da aynı ad ve dilde şablon zaten var (`field: name`, `template_id`). Meta'ya gidilmez. - 413 `media_too_large`: Başlık medyası sınırı aşıyor (`max_bytes` döner). - 422 `invalid_request`: `name`, `components` eksik ya da biçim yanlış; medya başlığında `header_media_ref` yok (`error.field` yolu söyler). - 422 `waba_required`: Birden çok WABA'nız var; `waba_id` verin. - 422 `header_media_fetch_failed`: `header_media_url` indirilemedi. - 422 `template_invalid`: Meta kurallarına aykırı (Meta'ya gönderilmedi); `error.field` yolu söyler (ör. `components[0].text`). - 422 `template_rejected`: Meta şablonu reddetti. Hata Meta'dan geldiyse `meta_code`, `meta_subcode`, `meta_message`, `meta_user_title`, `meta_user_message` ve `fbtrace_id` alanları da döner. - 502 `meta_unavailable`: Meta'ya ulaşılamadı ya da Meta 5xx döndü; biraz sonra tekrar deneyin. ## Şablonu doğrula `POST https://api.verimerkezi.app/wa/templates/validate` Şablon gövdesini Meta'ya göndermeden `POST /templates` ile aynı kurallarla denetler. Kullanıcı "gönder"e basmadan hatayı görmesini sağlar; 2.25.0'dan beri ihlal `422 template_invalid` yanıtında `error.field` (ör. `components[0].text`) ile hangi alanın hatalı olduğunu söyler. Kapsam: `templates:read` ### İstek gövdesi - `components` (array, zorunlu): Denetlenecek bileşenler. - `waba_id` (string): Hedef WABA. - `phone_number_id` (string): WABA'yı çözmek için numara. - `name` (string): Şablon adı. - `language` (string): Dil kodu. - `category` (string): Kategori. - `parameter_format` (string): `POSITIONAL` ya da `NAMED`. - `header_media_ref` (integer): Medya başlığı için `POST /templates/media` yanıtındaki değer. Verilmezse doğrulamada yer tutucu sayılır. ```bash curl -X POST "https://api.verimerkezi.app/wa/templates/validate" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "name": "deneme", "language": "tr", "category": "UTILITY", "components": [ { "type": "BODY", "text": "Merhaba {{1}}", "example": { "body_text": [ [ "Ahmet" ] ] } } ] }' ``` Yanıt (200): ```json { "ok": true, "valid": true, "message": "Şablon Meta kurallarına uygun görünüyor." } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `templates:read` kapsamı yok. - 404 `number_not_found`: Numara/WABA size ait değil. - 422 `waba_required`: Birden çok WABA'nız var; `waba_id` verin. - 422 `invalid_request`: `components` eksik. - 422 `template_invalid`: Kural ihlali; `message` sebebi, `error.field` alan yolunu söyler. ## Şablonları Meta'dan senkronla `POST https://api.verimerkezi.app/wa/templates/sync` Şablon listesini Meta'dan hemen çekip durum, kategori, kalite puanı, ret sebebi ve bileşenleri günceller. Meta'da hiçbir şeyi değiştirmez. Gövde boşsa tüm aktif WABA'larınız senkronlanır. Kapsam: `templates:read` ya da `templates:write` ### İstek gövdesi - `phone_number_id` (string): Yalnız bu numaranın WABA'sını senkronlar. ```bash curl -X POST "https://api.verimerkezi.app/wa/templates/sync" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890" }' ``` Yanıt (200): ```json { "ok": true, "synced": { "1029384756": 42 }, "templates_changed": 3, "at": "2026-10-10T12:00:00+03:00" } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `templates:read` ya da `templates:write` yok. - 404 `number_not_found`: Numara size ait değil ya da aktif değil. - 422 `invalid_request`: `phone_number_id` rakam değil. - 429 `sync_rate_limited`: WABA başına dakikada 1 senkron. `Retry-After` ve `error.retry_after` döner. - 502 `sync_failed`: Meta şablon listesi alınamadı. ## Şablon başlık medyası yükle `POST https://api.verimerkezi.app/wa/templates/media` Medya başlıklı (`IMAGE`, `VIDEO`, `DOCUMENT`) şablon için örnek dosyayı Meta'nın Resumable Upload'ına yükler ve `header_handle` döndürür. Dönen `header_handle` değerini HEADER bileşeninde `example.header_handle[0]`'a, `header_media_ref` değerini `POST /templates` gövdesinin üst düzeyine koyun. Üç giriş biçimi vardır (biri): `multipart/form-data` ile `file`, JSON `url` ya da JSON `media_id`. Kapsam: `templates:write` ### İstek gövdesi - `file` (file): `multipart/form-data` alanı: yüklenecek tek dosya. `format` verilmezse dosya türünden çıkarılır. - `url` (string): JSON girişi: herkese açık `https://` adres; sunucu indirir. - `media_id` (string): JSON girişi: `POST /media` ile Meta'ya yüklenmiş (30 gün geçerli) medya kimliği; sunucu Meta'dan indirip yeniden yükler. `phone_number_id` ile birlikte verin. - `format` (string): Beklenen biçim; dosya türüyle uyuşmazsa `415`. - `waba_id` (string): Hedef WABA. Hesabınızda tek WABA varsa gerekmez; birden çok varsa zorunludur. - `phone_number_id` (string): `waba_id` yerine; WABA numaradan çözülür. ```bash curl -X POST "https://api.verimerkezi.app/wa/templates/media" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://cdn.example.com/kampanya.jpg", "format": "IMAGE", "waba_id": "1029384756" }' ``` Yanıt (201): ```json { "ok": true, "header_handle": "4::aW1hZ2UvanBlZw==:ARb...", "header_media_ref": 8812, "format": "IMAGE", "mime_type": "image/jpeg", "file_size": 183244, "waba_id": "1029384756" } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `templates:write` kapsamı yok. - 404 `number_not_found`: Numara/WABA size ait değil. - 413 `media_too_large`: Sınır aşıldı (`max_bytes` döner): `IMAGE` 5 MB, `VIDEO` 16 MB, `DOCUMENT` 100 MB. - 415 `media_invalid_format`: Tür başlık için desteklenmiyor ya da istenen `format` ile uyuşmuyor. Kabul: JPEG/PNG, MP4, PDF. - 422 `invalid_request`: `file`, `url` ya da `media_id` yok; `format` geçersiz; `media_id` rakam değil (`error.field`). - 422 `waba_required`: Birden çok WABA'nız var; `waba_id` verin. - 422 `header_media_fetch_failed`: `url` indirilemedi ya da `media_id` Meta'dan alınamadı. - 422 `meta_upload_failed`: Meta'ya yükleme başarısız. Hata Meta'dan geldiyse `meta_code`, `meta_subcode`, `meta_message`, `meta_user_title`, `meta_user_message` ve `fbtrace_id` alanları da döner. - 502 `meta_unavailable`: Meta'ya ulaşılamadı ya da Meta 5xx döndü; biraz sonra tekrar deneyin. ## Şablon ayrıntısını getir `GET https://api.verimerkezi.app/wa/templates/{id}` Tek bir şablonu tam bileşenleri, kalite puanı, ret nedeni (ham ve Türkçe), değişken biçimi, başlık medyası ve düzenleme durumuyla (`edit_limit`) döndürür. Kapsam: `templates:read` ### Yol parametreleri - `id` (integer, zorunlu): Veri Merkezi şablon kimliği. ```bash curl -X GET "https://api.verimerkezi.app/wa/templates/512" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "id": 512, "meta_template_id": "1249033812345678", "name": "police_yenileme_hatirlatma", "language": "tr", "category": "UTILITY", "previous_category": null, "parameter_format": "POSITIONAL", "status": "REJECTED", "rejected_reason": "INVALID_FORMAT", "rejected_reason_text": "Şablon biçimi geçersiz (değişken/örnek eksik olabilir).", "quality_score": null, "waba_id": "1029384756", "phone_number_id": "1234567890", "created_at": "2026-09-22T09:00:00+03:00", "submitted_at": "2026-09-22T09:00:03+03:00", "status_updated_at": "2026-09-22T09:05:41+03:00", "components": [ { "type": "BODY", "text": "Sayın {{1}}, poliçeniz {{2}} tarihinde sona eriyor.", "example": { "body_text": [ [ "Ahmet", "12.10.2026" ] ] } } ], "header_media": { "url": "https://api.verimerkezi.app/storage/template-media/8812.jpg", "mime_type": "image/jpeg", "file_size": 183244, "sha256": "9f2c...e41a", "filename": "kampanya.jpg", "format": "IMAGE", "temporary": false }, "display_phone_number": "+90 555 000 00 00", "ok": true, "header_media_ref": null, "edit_limit": { "editable": true, "edits_last_30d": 0, "max_edits_30d": 10, "next_edit_at": null } } ``` Hatalar: - 400 `invalid_template_id`: Geçersiz şablon kimliği. - 403 `insufficient_scope`: Anahtarda `templates:read` kapsamı yok. - 404 `template_not_found`: Şablon bulunamadı. ## Şablonu düzenle `PATCH https://api.verimerkezi.app/wa/templates/{id}` `APPROVED`, `REJECTED` ya da `PAUSED` şablonun bileşenlerini değiştirir ve yeniden Meta incelemesine gönderir; durum `PENDING` olur. Düzenleme TÜM bileşenleri değiştirir: gönderdiğiniz liste tam olmalıdır. Onaylı şablon 24 saatte 1, 30 günde en çok 10 kez düzenlenebilir ve kategorisi değişmez; reddedilen ve duraklatılan şablonlarda sınır yoktur ve kategori değişebilir. `POST /templates/{id}` ile eşdeğerdir. Kapsam: `templates:write` ### Yol parametreleri - `id` (integer, zorunlu): Veri Merkezi şablon kimliği. ### İstek gövdesi - `components` (array, zorunlu): TAM bileşen listesi (yalnız kategori değişikliğinde verilmeyebilir). Biçim `POST /templates` ile aynıdır. - `category` (string): Yeni kategori; yalnız `REJECTED` / `PAUSED` şablonda. Onaylıda farklı kategori `422 category_not_editable` verir. - `message_send_ttl_seconds` (integer): Mesaj geçerlilik süresi (saniye). - `header_media_ref` (integer): Medya başlığını değiştirirken `POST /templates/media` değeri. Verilmezse mevcut Veri Merkezi kopyasından yeni tanıtıcı üretilir. - `header_media_url` (string): Eski yol: herkese açık `https://` adres. - `allow_category_change` (boolean): Meta'ya aynen iletilir. - `cta_url_link_tracking_opted_out` (boolean): Meta'ya aynen iletilir. - `optout_footer` (boolean): Yalnız `MARKETING`; oluşturmadaki gibi. ```bash curl -X PATCH "https://api.verimerkezi.app/wa/templates/512" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "components": [ { "type": "BODY", "text": "Sayın {{1}}, poliçeniz {{2}} tarihinde bitiyor.", "example": { "body_text": [ [ "Ahmet", "12.10.2026" ] ] } } ] }' ``` Yanıt (200): ```json { "id": 512, "meta_template_id": "1249033812345678", "name": "police_yenileme_hatirlatma", "language": "tr", "category": "UTILITY", "previous_category": null, "parameter_format": "POSITIONAL", "status": "PENDING", "rejected_reason": null, "rejected_reason_text": null, "quality_score": null, "waba_id": "1029384756", "phone_number_id": "1234567890", "created_at": "2026-09-22T09:00:00+03:00", "submitted_at": "2026-09-22T09:00:03+03:00", "status_updated_at": "2026-09-22T09:05:41+03:00", "components": [ { "type": "BODY", "text": "Sayın {{1}}, poliçeniz {{2}} tarihinde sona eriyor.", "example": { "body_text": [ [ "Ahmet", "12.10.2026" ] ] } } ], "display_phone_number": "+90 555 000 00 00", "ok": true, "header_media_ref": null, "edit_limit": { "editable": false, "edits_last_30d": 1, "max_edits_30d": 10, "next_edit_at": "2026-10-15T09:05:41+03:00" } } ``` Hatalar: - 400 `invalid_template_id`: Geçersiz şablon kimliği. - 403 `insufficient_scope`: Anahtarda `templates:write` kapsamı yok. - 404 `template_not_found`: Şablon bulunamadı. - 409 `template_not_editable`: Durum `APPROVED` / `REJECTED` / `PAUSED` dışında (ör. `PENDING`, `DISABLED`) ya da şablon Meta'ya hiç gönderilmemiş (`status` döner). - 422 `invalid_request`: `name`, `language` ya da `parameter_format` gönderildi (düzenlenemez; yeni ad / dil için kopyalayın) ya da `components` biçimi yanlış (`error.field`). - 422 `category_not_editable`: Onaylı şablonda kategori değişikliği istendi (`field: category`). - 422 `template_invalid`: Bileşenler Meta kurallarına aykırı; `error.field` yolu söyler. - 422 `template_edit_failed`: Meta düzenlemeyi kabul etmedi. Hata Meta'dan geldiyse `meta_code`, `meta_subcode`, `meta_message`, `meta_user_title`, `meta_user_message` ve `fbtrace_id` alanları da döner. - 429 `template_edit_limit`: Düzenleme sınırı: `next_edit_at` (en erken zaman) ve `retry_after` (saniye, `Retry-After` başlığı) döner. Meta kaynaklıysa `meta_*` alanları da gelir. - 502 `meta_unavailable`: Meta'ya ulaşılamadı ya da Meta 5xx döndü; biraz sonra tekrar deneyin. ## Şablonu düzenle (POST) `POST https://api.verimerkezi.app/wa/templates/{id}` `PATCH /templates/{id}` ile birebir aynı davranır; `PATCH` gönderemeyen istemciler için `POST` eşdeğeridir (2.25.0). Kurallar, gövde, hatalar ve yanıt aynıdır. Kapsam: `templates:write` ### Yol parametreleri - `id` (integer, zorunlu): Veri Merkezi şablon kimliği. ### İstek gövdesi - `components` (array, zorunlu): TAM bileşen listesi (yalnız kategori değişikliğinde verilmeyebilir). Biçim `POST /templates` ile aynıdır. - `category` (string): Yeni kategori; yalnız `REJECTED` / `PAUSED` şablonda. Onaylıda farklı kategori `422 category_not_editable` verir. - `message_send_ttl_seconds` (integer): Mesaj geçerlilik süresi (saniye). - `header_media_ref` (integer): Medya başlığını değiştirirken `POST /templates/media` değeri. Verilmezse mevcut Veri Merkezi kopyasından yeni tanıtıcı üretilir. - `header_media_url` (string): Eski yol: herkese açık `https://` adres. - `allow_category_change` (boolean): Meta'ya aynen iletilir. - `cta_url_link_tracking_opted_out` (boolean): Meta'ya aynen iletilir. - `optout_footer` (boolean): Yalnız `MARKETING`; oluşturmadaki gibi. ```bash curl -X POST "https://api.verimerkezi.app/wa/templates/512" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "components": [ { "type": "BODY", "text": "Sayın {{1}}, poliçeniz {{2}} tarihinde bitiyor.", "example": { "body_text": [ [ "Ahmet", "12.10.2026" ] ] } } ] }' ``` Yanıt (200): ```json { "id": 512, "meta_template_id": "1249033812345678", "name": "police_yenileme_hatirlatma", "language": "tr", "category": "UTILITY", "previous_category": null, "parameter_format": "POSITIONAL", "status": "PENDING", "rejected_reason": null, "rejected_reason_text": null, "quality_score": null, "waba_id": "1029384756", "phone_number_id": "1234567890", "created_at": "2026-09-22T09:00:00+03:00", "submitted_at": "2026-09-22T09:00:03+03:00", "status_updated_at": "2026-09-22T09:05:41+03:00", "components": [ { "type": "BODY", "text": "Sayın {{1}}, poliçeniz {{2}} tarihinde sona eriyor.", "example": { "body_text": [ [ "Ahmet", "12.10.2026" ] ] } } ], "display_phone_number": "+90 555 000 00 00", "ok": true, "header_media_ref": null, "edit_limit": { "editable": false, "edits_last_30d": 1, "max_edits_30d": 10, "next_edit_at": "2026-10-15T09:05:41+03:00" } } ``` Hatalar: - 400 `invalid_template_id`: Geçersiz şablon kimliği. - 403 `insufficient_scope`: Anahtarda `templates:write` kapsamı yok. - 404 `template_not_found`: Şablon bulunamadı. - 409 `template_not_editable`: Durum `APPROVED` / `REJECTED` / `PAUSED` dışında (ör. `PENDING`, `DISABLED`) ya da şablon Meta'ya hiç gönderilmemiş (`status` döner). - 422 `invalid_request`: `name`, `language` ya da `parameter_format` gönderildi (düzenlenemez; yeni ad / dil için kopyalayın) ya da `components` biçimi yanlış (`error.field`). - 422 `category_not_editable`: Onaylı şablonda kategori değişikliği istendi (`field: category`). - 422 `template_invalid`: Bileşenler Meta kurallarına aykırı; `error.field` yolu söyler. - 422 `template_edit_failed`: Meta düzenlemeyi kabul etmedi. Hata Meta'dan geldiyse `meta_code`, `meta_subcode`, `meta_message`, `meta_user_title`, `meta_user_message` ve `fbtrace_id` alanları da döner. - 429 `template_edit_limit`: Düzenleme sınırı: `next_edit_at` (en erken zaman) ve `retry_after` (saniye, `Retry-After` başlığı) döner. Meta kaynaklıysa `meta_*` alanları da gelir. - 502 `meta_unavailable`: Meta'ya ulaşılamadı ya da Meta 5xx döndü; biraz sonra tekrar deneyin. ## Şablonu kopyala `POST https://api.verimerkezi.app/wa/templates/{id}/copy` Mevcut şablonu yeni ad ve/veya dille kopyalayıp Meta onayına gönderir. Kaynağın bileşenleri, `parameter_format` değeri ve (Veri Merkezi'nde kopyası varsa) başlık medyası kullanılır; `components` verilirse onlar esas alınır (çeviri için). Yanıt ve hatalar `POST /templates` ile aynıdır. Kapsam: `templates:write` ### Yol parametreleri - `id` (integer, zorunlu): Veri Merkezi şablon kimliği. ### İstek gövdesi - `name` (string, zorunlu): Yeni şablon adı. - `language` (string): Yeni dil; verilmezse kaynağın dili. - `category` (string): Yeni kategori; verilmezse kaynağın kategorisi. - `components` (array): Kaynağın yerine kullanılacak bileşenler (ör. çeviri). - `waba_id` (string): Hedef WABA; verilmezse kaynağın WABA'sı. - `optout_footer` (boolean): Kopyada varsayılan `false`: kaynağın alt bilgisi aynen alınır. - `header_media_ref` (integer): Başlık medyası için `POST /templates/media` değeri. - `allow_category_change` (boolean): Meta'ya aynen iletilir. - `message_send_ttl_seconds` (integer): Mesaj geçerlilik süresi (saniye). - `cta_url_link_tracking_opted_out` (boolean): Meta'ya aynen iletilir. ```bash curl -X POST "https://api.verimerkezi.app/wa/templates/512/copy" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "police_yenileme_hatirlatma_en", "language": "en_US", "components": [ { "type": "BODY", "text": "Dear {{1}}, your policy for {{2}} expires on {{3}}.", "example": { "body_text": [ [ "Ahmet Yilmaz", "34 ABC 123", "12.10.2026" ] ] } } ] }' ``` Yanıt (202): ```json { "ok": true, "id": 514, "meta_template_id": "1249033812345680", "name": "police_yenileme_hatirlatma_en", "language": "en_US", "category": "UTILITY", "previous_category": null, "parameter_format": "POSITIONAL", "status": "PENDING", "rejected_reason": null, "rejected_reason_text": null, "quality_score": null, "waba_id": "1029384756", "phone_number_id": "1234567890", "created_at": "2026-10-14T09:00:00+03:00", "submitted_at": "2026-10-14T09:00:02+03:00", "status_updated_at": null } ``` Hatalar: - 400 `invalid_template_id`: Geçersiz şablon kimliği. - 403 `insufficient_scope`: Anahtarda `templates:write` kapsamı yok. - 404 `template_not_found`: Kaynak şablon bulunamadı. - 409 `template_exists`: Aynı WABA'da aynı ad ve dil zaten var (`field: name`, `template_id`). - 422 `invalid_request`: `name` eksik; medya başlıklı kaynağın Veri Merkezi kopyası yoksa `field: header_media_ref` (önce `POST /templates/media`, sonra `header_media_ref` ile tekrar deneyin). - 422 `template_invalid`: Bileşenler Meta kurallarına aykırı; `error.field` yolu söyler. - 422 `template_rejected`: Meta şablonu reddetti. Hata Meta'dan geldiyse `meta_code`, `meta_subcode`, `meta_message`, `meta_user_title`, `meta_user_message` ve `fbtrace_id` alanları da döner. - 502 `meta_unavailable`: Meta'ya ulaşılamadı ya da Meta 5xx döndü; biraz sonra tekrar deneyin. ## Şablonu sil `DELETE https://api.verimerkezi.app/wa/templates/{id}` Yalnız bu dili (bu kimlikteki satırı) siler: Meta'ya ad ve `hsm_id` ile gider. Adın diğer dilleri etkilenmez; tüm dilleri silmek için `DELETE /templates?name=…` kullanın. Geçmiş mesajlar etkilenmez; Meta silinen onaylı şablonun adını 30 gün yeniden kullandırmaz. Meta'ya hiç gönderilmemiş taslak yalnız yerelden silinir. Kapsam: `templates:write` ### Yol parametreleri - `id` (integer, zorunlu): Veri Merkezi şablon kimliği. ```bash curl -X DELETE "https://api.verimerkezi.app/wa/templates/512" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "deleted": true, "id": 512, "name": "police_yenileme_hatirlatma", "language": "tr" } ``` Hatalar: - 400 `invalid_template_id`: Geçersiz şablon kimliği. - 403 `insufficient_scope`: Anahtarda `templates:write` kapsamı yok. - 404 `template_not_found`: Şablon bulunamadı. - 422 `template_delete_failed`: Meta silmeyi kabul etmedi (ör. `DISABLED` şablon silinemez). Hata Meta'dan geldiyse `meta_code`, `meta_subcode`, `meta_message`, `meta_user_title`, `meta_user_message` ve `fbtrace_id` alanları da döner. - 502 `meta_unavailable`: Meta'ya ulaşılamadı ya da Meta 5xx döndü; biraz sonra tekrar deneyin. ## Şablonu ada göre sil (tüm diller) `DELETE https://api.verimerkezi.app/wa/templates/{id}` Adın TÜM dillerini aynı anda siler (Meta'ya yalnız `name` ile gider). Tek dil için `DELETE /templates/{id}` kullanın. Silinen onaylı şablonun adı 30 gün yeniden kullanılamaz; `DISABLED` şablon silinemez. Kapsam: `templates:write` ### Sorgu parametreleri - `name` (string, zorunlu): Silinecek şablon adı (küçük harf, rakam, alt çizgi). - `waba_id` (string): Hedef WABA. Hesabınızda tek WABA varsa gerekmez; birden çok varsa zorunludur. - `phone_number_id` (string): `waba_id` yerine; WABA numaradan çözülür. ```bash curl -X DELETE "https://api.verimerkezi.app/wa/templates/{id}" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "deleted": true, "name": "police_yenileme_hatirlatma", "waba_id": "1029384756", "ids": [ 512, 513 ], "languages": [ "tr", "en_US" ] } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `templates:write` kapsamı yok. - 404 `template_not_found`: Bu adla şablon bulunamadı. - 404 `number_not_found`: Numara/WABA size ait değil. - 422 `invalid_request`: `name` eksik ya da geçersiz (`field: name`). - 422 `waba_required`: Birden çok WABA'nız var; `waba_id` verin. - 422 `template_delete_failed`: Meta silmeyi kabul etmedi. Hata Meta'dan geldiyse `meta_code`, `meta_subcode`, `meta_message`, `meta_user_title`, `meta_user_message` ve `fbtrace_id` alanları da döner. - 502 `meta_unavailable`: Meta'ya ulaşılamadı ya da Meta 5xx döndü; biraz sonra tekrar deneyin. --- # Kişiler Kişi rehberinize kişi ekleyin, toplu içe aktarın, listeleyin ve abonelikten çıkanları izleyin. ## Contact nesnesi - `id` (integer): Kişi kimliği. - `phone` (string): Uluslararası biçimde telefon, `+` önekiyle. - `name` (string): Ad soyad. - `email` (string): E-posta. - `company` (string): Firma. - `tags` (array): Etiketler. - `source` (string): Kaynak (API ile eklenenlerde `api`). - `opted_out` (boolean): Kişi mesaj almayı reddetti mi. - `opted_out_at` (string): Reddetme zamanı (ISO 8601). - `created_at` (string): Oluşturulma zamanı. ## Kişileri listele `GET https://api.verimerkezi.app/wa/contacts` Kişi rehberinizi yeniden eskiye, imleçle sayfalı döndürür. Kapsam: `contacts:read` ### Sorgu parametreleri - `limit` (integer): Sayfa boyutu (1–200). - `cursor` (string): Önceki yanıttaki opak `next_cursor`. - `q` (string): Ad ya da telefon içinde arama. - `opted_out` (boolean): Abonelik durumuna göre süzer. ```bash curl -X GET "https://api.verimerkezi.app/wa/contacts?limit=50&q=ayse" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "contacts": [ { "id": 26481, "phone": "+905550000000", "name": "Ayşe Yılmaz", "email": "ayse@example.com", "company": "Firma A.Ş.", "tags": [ "vip", "istanbul" ], "source": "api", "opted_out": false, "opted_out_at": null, "created_at": "2026-10-01 14:22:10" } ], "next_cursor": "eyJpZCI6MjY0ODEsImsiOiJpZCIsImQiOiJkZXNjIn0", "has_more": true } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `contacts:read` kapsamı yok. ## Abonelikten çıkanları listele `GET https://api.verimerkezi.app/wa/contacts/opted-out` Mesaj almayı reddeden kişileri döndürür. `contact.opt_out_changed` olayıyla birlikte artımlı eşitleme için `since` kullanın. Kapsam: `contacts:read` ### Sorgu parametreleri - `limit` (integer): Sayfa boyutu (1–200). - `cursor` (string): Opak imleç. - `since` (string): ISO 8601; yalnız bu zamandan sonra reddedenler. ```bash curl -X GET "https://api.verimerkezi.app/wa/contacts/opted-out?since=2026-10-01T00%3A00%3A00%2B03%3A00" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "contacts": [ { "id": 26470, "phone": "+905550000001", "name": "Mehmet Kaya", "opted_out": true, "opted_out_at": "2026-10-05T16:40:00+03:00", "reason": "whatsapp_ret" } ], "next_cursor": null, "has_more": false } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `contacts:read` kapsamı yok. - 422 `invalid_request`: `since` ISO 8601 değil (`field: since`). ## Kişi ekle `POST https://api.verimerkezi.app/wa/contacts` Rehbere tek kişi ekler. Telefon, ülke koduyla birleştirilerek uluslararası biçime çevrilir. Kapsam: `contacts:write` ### İstek gövdesi - `phone` (string, zorunlu): Telefon (en az 10 hane). Ülke koduyla başlamıyorsa `country_code` eklenir. - `name` (string, zorunlu): Ad soyad (en az 2 karakter). - `country_code` (string): Ülke kodu. - `email` (string): E-posta. - `company` (string): Firma. - `tags` (array): Etiketler. ```bash curl -X POST "https://api.verimerkezi.app/wa/contacts" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone": "5550000000", "country_code": "90", "name": "Ayşe Yılmaz", "email": "ayse@example.com", "company": "Firma A.Ş.", "tags": [ "vip", "istanbul" ] }' ``` Yanıt (201): ```json { "ok": true, "id": 26481, "phone": "+905550000000", "name": "Ayşe Yılmaz" } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `contacts:write` kapsamı yok. - 409 `contact_exists`: Bu telefon zaten kayıtlı (`error.id` mevcut kişiyi verir). - 422 `invalid_phone`: Geçerli telefon zorunlu. - 422 `invalid_name`: `name` en az 2 karakter olmalı. ## Kişileri toplu ekle `POST https://api.verimerkezi.app/wa/contacts/bulk` Tek istekte en fazla 1000 kişi ekler. Geçersiz satırlar `errors` dizisinde döner; diğerleri eklenir. Kapsam: `contacts:write` ### İstek gövdesi - `contacts` (array, zorunlu): Kişi listesi (en fazla 1000). - `skip_duplicates` (boolean): `true` ise kayıtlı telefonlar sessizce atlanır; aksi hâlde `errors` içinde `duplicate` döner. - `country_code` (string): Ülke kodu. ```bash curl -X POST "https://api.verimerkezi.app/wa/contacts/bulk" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "contacts": [ { "phone": "5550000003", "name": "Ali Demir" }, { "phone": "5550000002", "name": "Zeynep Şahin", "tags": [ "yeni" ] } ], "skip_duplicates": true, "country_code": "90" }' ``` Yanıt (201): ```json { "ok": true, "inserted": 2, "skipped": 0, "errors": [] } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `contacts:write` kapsamı yok. - 422 `invalid_request`: `contacts` dizisi zorunlu. - 422 `too_many`: Tek istekte en fazla 1000 kişi. --- # Uygulama rehberi WhatsApp Business uygulamasıyla birlikte kullanılan (coexistence) numaraların telefondaki kişi rehberini okuyun ve senkron durumunu izleyin. ## AppContact nesnesi - `id` (integer): Kaydın kimliği (numara + telefon başına tekil). `contact.synced` olayındaki `changes[].id` ile aynıdır. - `phone_number_id` (string): Rehberin ait olduğu işletme numarası. - `phone` (string): Kişinin telefonu, `+` önekiyle (E.164). Meta vermezse `null`. - `wa_id` (string): WhatsApp kimliği (telefonun rakamları). Meta bu alanı göndermez; telefondan türetilir. - `user_id` (string): Kişinin işletmeye özel kullanıcı kimliği (BSUID). Meta göndermediyse `null`. - `full_name` (string): Telefondaki rehberde kayıtlı ad soyad. Silinen kayıtta `null`. - `first_name` (string): Rehberdeki ad. Silinen kayıtta `null`. - `source` (string): Son değişikliğin kaynağı. - `updated_at` (string): Son ekleme, güncelleme ya da silme zamanı (ISO 8601). `updated_since` buna göre süzer. - `removed_at` (string): Kişi telefondaki rehberden silindiyse silinme zamanı (ISO 8601). ## Uygulama rehberini listele `GET https://api.verimerkezi.app/wa/app-contacts` Coexistence numaralarınızın telefondaki WhatsApp Business uygulaması rehberini döndürür. Panel kişi rehberinden (`GET /contacts`) ayrı bir kaynaktır. Sıralama `updated_at` azalan, sonra `id` azalan. İlk tam çekimden sonra `contact.synced` olayını dinleyin; kaçan olayları günlük `updated_since` + `include_removed=true` ile telafi edin. Kapsam: `contacts:read` ### Sorgu parametreleri - `phone_number_id` (string): Yalnız bu numaranın rehberi. Verilmezse hesabınızdaki bütün numaralar. - `updated_since` (string): ISO 8601; yalnız `updated_at` bu zamana eşit ya da sonra olanlar (artımlı eşitleme). - `include_removed` (boolean): `true` ise telefondaki rehberden silinmiş kayıtlar da döner (`removed_at` dolu). - `limit` (integer): Sayfa boyutu (1–200). - `cursor` (string): Opak imleç; önceki yanıttaki `next_cursor`. ```bash curl -X GET "https://api.verimerkezi.app/wa/app-contacts?phone_number_id=1234567890&updated_since=2026-10-13T00%3A00%3A00%2B03%3A00" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "app_contacts": [ { "id": 501, "phone_number_id": "1234567890", "phone": "+905551112233", "wa_id": "905551112233", "user_id": "TR.1A2B3C4D5E6F7G8H", "full_name": "Ayşe Yılmaz", "first_name": "Ayşe", "source": "app_sync", "updated_at": "2026-10-13T10:15:00+03:00", "removed_at": null } ], "next_cursor": "eyJpZCI6NTAxLCJrIjoidXBkYXRlZF9hdCIsImQiOiJkZXNjIiwidSI6IjIwMjYtMTAtMTMgMTA6MTU6MDAifQ", "has_more": true } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `contacts:read` kapsamı yok. - 404 `number_not_found`: `phone_number_id` hesabınıza ait değil. - 422 `invalid_request`: `updated_since`, `limit`, `cursor` ya da `phone_number_id` geçersiz (`field` alanına bakın). ## Rehber senkron durumunu getir `GET https://api.verimerkezi.app/wa/numbers/{phone_number_id}/app-contacts/sync` Numaranın uygulama rehberi senkronunu destekleyip desteklemediğini, ilk aktarımın durumunu ve kayıt sayılarını döndürür. Kapsam: `contacts:read` ### Yol parametreleri - `phone_number_id` (string, zorunlu): Numaranızın Meta kimliği (yalnız rakam, 5–32 hane). ```bash curl -X GET "https://api.verimerkezi.app/wa/numbers/1234567890/app-contacts/sync" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "phone_number_id": "1234567890", "coexistence": true, "sync_supported": true, "reason": null, "initial_sync": { "status": "completed", "requested_at": "2026-10-13T09:00:00+03:00", "completed_at": "2026-10-13T09:02:10+03:00" }, "last_event_at": "2026-10-13T10:15:00+03:00", "contact_count": 842, "removed_count": 12 } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `contacts:read` kapsamı yok. - 404 `number_not_found`: Numara size ait değil. - 422 `invalid_request`: `phone_number_id` yalnız rakam olmalı. --- # Webhook abonelikleri Olayların sunucunuza gönderileceği webhook aboneliklerini yönetin; imza anahtarını döndürün ve ölü teslimleri yeniden kuyruğa alın. ## WebhookSubscription nesnesi - `id` (integer): Abonelik kimliği. - `name` (string): Abonelik adı (`description`). - `url` (string): Teslim adresi (`https`). - `events` (array): Abone olunan olaylar. `*` kaydedilirken o anki 12 klasik olaya genişletilir. - `active` (boolean): Abonelik etkin mi. - `secret_prefix` (string): İmza anahtarının ilk 16 karakteri. - `last_success_at` (string): Son başarılı teslim. - `last_failure_at` (string): Son başarısız teslim. - `failure_count` (integer): Ardışık başarısız teslim sayısı. - `created_at` (string): Oluşturulma zamanı. ## Abonelikleri listele `GET https://api.verimerkezi.app/wa/webhooks` Hesabınızın webhook aboneliklerini döndürür. İmza anahtarı dönmez; yalnız `secret_prefix` görünür. Kapsam: `webhooks:read` ```bash curl -X GET "https://api.verimerkezi.app/wa/webhooks" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "webhooks": [ { "id": 17, "name": "Üretim botu", "url": "https://api.example.com/wa-webhook", "events": [ "message.received", "message.status.delivered", "template.approved" ], "active": true, "secret_prefix": "whsec_9f3akq2m7", "last_success_at": "2026-10-10T11:00:00+03:00", "last_failure_at": null, "failure_count": 0, "created_at": "2026-10-01T12:00:00+03:00" } ] } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `webhooks:read` kapsamı yok. ## Abonelik oluştur `POST https://api.verimerkezi.app/wa/webhooks` Yeni bir webhook aboneliği oluşturur. İmza anahtarı (`secret`) yalnız bu yanıtta bir kez döner. Kapsam: `webhooks:write` ### İstek gövdesi - `url` (string, zorunlu): Herkese açık `https://` adres. İç ağ, özel, loopback ve link-local adresler reddedilir. - `events` (array): Olay adları ya da `*`. Hiç gönderilmezse `*`; boş dizi ya da bilinmeyen olay `422` döner. - `description` (string): Abonelik adı (1–80 karakter). ```bash curl -X POST "https://api.verimerkezi.app/wa/webhooks" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://api.example.com/wa-webhook", "events": [ "message.received", "message.status.delivered", "template.approved" ], "description": "Üretim botu" }' ``` Yanıt (201): ```json { "ok": true, "id": 17, "url": "https://api.example.com/wa-webhook", "events": [ "message.received", "message.status.delivered", "template.approved" ], "active": true, "secret": "whsec_9f3akq2m7x1p8s0d4v6b2n5c3z7l9h1j", "secret_prefix": "whsec_9f3akq2m7" } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `webhooks:write` kapsamı yok. - 409 `webhook_url_exists`: Bu adrese zaten aktif bir abonelik var (`existing_id` döner). - 422 `invalid_request`: `url` zorunlu. - 422 `invalid_webhook`: Adres `https` değil/genel değil ya da olay listesi geçersiz. - 500 `webhook_create_failed`: Abonelik oluşturulamadı. ## Aboneliği güncelle `PATCH https://api.verimerkezi.app/wa/webhooks/{id}` Adresi, olay listesini, etkinliği ya da adı değiştirir. En az bir alan gönderin. Kapsam: `webhooks:write` ### Yol parametreleri - `id` (integer, zorunlu): Abonelik kimliği. ### İstek gövdesi - `url` (string): Yeni `https` adres. - `events` (array): Yeni olay listesi. - `active` (boolean): `false` aboneliği pasifleştirir. - `description` (string): Yeni ad (1–80 karakter). ```bash curl -X PATCH "https://api.verimerkezi.app/wa/webhooks/17" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "events": [ "message.received", "message.status.failed", "flow.completed" ], "active": true }' ``` Yanıt (200): ```json { "id": 17, "name": "Üretim botu", "url": "https://api.example.com/wa-webhook", "events": [ "message.received", "message.status.failed", "flow.completed" ], "active": true, "secret_prefix": "whsec_9f3akq2m7", "last_success_at": "2026-10-10T11:00:00+03:00", "last_failure_at": null, "failure_count": 0, "created_at": "2026-10-01T12:00:00+03:00", "ok": true } ``` Hatalar: - 400 `invalid_request`: Geçersiz abonelik kimliği. - 403 `insufficient_scope`: Anahtarda `webhooks:write` kapsamı yok. - 404 `webhook_not_found`: Abonelik bulunamadı. - 422 `invalid_webhook`: Alan yok, adres geçersiz ya da olay listesi hatalı. ## Aboneliği sil `DELETE https://api.verimerkezi.app/wa/webhooks/{id}` Aboneliği kalıcı olarak siler. Kapsam: `webhooks:write` ### Yol parametreleri - `id` (integer, zorunlu): Abonelik kimliği. ```bash curl -X DELETE "https://api.verimerkezi.app/wa/webhooks/17" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "deleted": true, "id": 17 } ``` Hatalar: - 400 `invalid_request`: Geçersiz abonelik kimliği. - 403 `insufficient_scope`: Anahtarda `webhooks:write` kapsamı yok. - 404 `webhook_not_found`: Abonelik bulunamadı. ## Test bildirimi gönder `POST https://api.verimerkezi.app/wa/webhooks/{id}/test` Aboneliğe hemen imzalı bir `test.ping` olayı gönderir ve teslimin sonucunu döndürür. Kapsam: `webhooks:write` ### Yol parametreleri - `id` (integer, zorunlu): Abonelik kimliği. ```bash curl -X POST "https://api.verimerkezi.app/wa/webhooks/17/test" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "result": "delivered" } ``` Hatalar: - 400 `invalid_request`: Geçersiz abonelik kimliği. - 403 `insufficient_scope`: Anahtarda `webhooks:write` kapsamı yok. - 404 `webhook_not_found`: Abonelik bulunamadı. ## İmza anahtarını döndür `POST https://api.verimerkezi.app/wa/webhooks/{id}/rotate-secret` Aboneliği silmeden imza anahtarını yeniler; kimlik, istatistik ve geçmiş korunur. Yeni anahtar yalnız bu yanıtta döner. Geçiş süresince eski anahtarla ikinci bir imza da gönderilir. Kapsam: `webhooks:write` ### Yol parametreleri - `id` (integer, zorunlu): Abonelik kimliği. ### İstek gövdesi - `grace_seconds` (integer): Eski anahtarın `X-VeriMerkezi-Signature-256-Previous` başlığıyla gönderileceği süre (0–604800). `0` anında geçiş. ```bash curl -X POST "https://api.verimerkezi.app/wa/webhooks/17/rotate-secret" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "grace_seconds": 86400 }' ``` Yanıt (200): ```json { "ok": true, "id": 17, "secret": "whsec_2m8c4x9v1b7n3k5q0p6z8l2j4h7g1f3d", "secret_prefix": "whsec_2m8c4x9v1", "previous_valid_until": "2026-10-11T12:00:00+03:00" } ``` Hatalar: - 400 `invalid_request`: Geçersiz abonelik kimliği. - 403 `insufficient_scope`: Anahtarda `webhooks:write` kapsamı yok. - 404 `webhook_not_found`: Abonelik bulunamadı. - 422 `invalid_request`: `grace_seconds` aralık dışında (`field: grace_seconds`). ## Ölü teslimleri yeniden kuyruğa al `POST https://api.verimerkezi.app/wa/webhooks/redeliver` Tüm denemeleri tükenip ölü (dead-letter) duruma düşen teslimlerinizi yeniden kuyruğa alır. Olaylar dakikalık teslim turunda yeniden gönderilir. Kapsam: `webhooks:write` ya da `messages:read` ya da `inbox:read` ### İstek gövdesi - `since` (string, zorunlu): ISO 8601; en fazla son 7 gün. ```bash curl -X POST "https://api.verimerkezi.app/wa/webhooks/redeliver" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "since": "2026-10-08T00:00:00Z" }' ``` Yanıt (202): ```json { "ok": true, "requeued": 12, "since": "2026-10-08T03:00:00+03:00", "note": "Olaylar dakikalık teslimat turunda yeniden gönderilir." } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `webhooks:write`, `messages:read` ya da `inbox:read` yok. - 422 `invalid_since`: `since` eksik, ISO 8601 değil ya da 7 günden eski. --- # İşletme profili Numaranızın WhatsApp işletme profilini (hakkında, adres, açıklama, e-posta, sektör, web siteleri) okuyun ve güncelleyin. ## BusinessProfile nesnesi - `about` (string): Hakkında metni (en fazla 139 karakter). - `address` (string): Adres (en fazla 256 karakter). - `description` (string): Açıklama (en fazla 512 karakter). - `email` (string): E-posta. - `vertical` (string): Meta sektör kategorisi. - `websites` (array): En fazla 2 web sitesi. ## Profili getir `GET https://api.verimerkezi.app/wa/profile/{phone_number_id}` İşletme profilini Meta'dan canlı okur ve Meta'nın döndürdüğü alanları aynen iletir. Kapsam: `profile:read` ### Yol parametreleri - `phone_number_id` (string, zorunlu): Numaranızın Meta kimliği (yalnız rakam, 5–32 hane). ```bash curl -X GET "https://api.verimerkezi.app/wa/profile/1234567890" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "profile": { "about": "7/24 müşteri desteği", "address": "Çankaya / Ankara", "description": "Kurumsal yazılım ve WhatsApp çözümleri.", "email": "destek@example.com", "vertical": "PROF_SERVICES", "websites": [ "https://example.com" ] } } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `profile:read` kapsamı yok. - 404 `number_not_found`: Numara bulunamadı. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 422 `meta_error`: Meta isteği reddetti (`meta_code`). ## Profili güncelle `PATCH https://api.verimerkezi.app/wa/profile/{phone_number_id}` İşletme profilini günceller. Yalnız gönderdiğiniz alanlar değişir; uzun metinler sınırda kesilir. Kapsam: `profile:write` ### Yol parametreleri - `phone_number_id` (string, zorunlu): Numaranızın Meta kimliği (yalnız rakam, 5–32 hane). ### İstek gövdesi - `about` (string): En fazla 139 karakter. - `description` (string): En fazla 512 karakter. - `address` (string): En fazla 256 karakter. - `email` (string): E-posta. - `vertical` (string): Sektör kodu (bkz. nesne). - `websites` (array): En fazla 2 adres; fazlası atılır. ```bash curl -X PATCH "https://api.verimerkezi.app/wa/profile/1234567890" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "about": "7/24 müşteri desteği", "email": "destek@example.com", "websites": [ "https://example.com" ] }' ``` Yanıt (200): ```json { "ok": true, "updated": true, "fields": [ "about", "email", "websites" ] } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `profile:write` kapsamı yok. - 404 `number_not_found`: Numara bulunamadı. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 422 `coexistence_readonly_profile`: Coexistence numarasında profil WhatsApp Business uygulamasından güncellenir. - 422 `invalid_request`: En az bir alan gerekli. - 422 `meta_error`: Meta isteği reddetti. --- # Raporlar Seçilen dönem için gönderim, teslim, okunma ve başarısızlık özetini numara kırılımıyla alın. ## ReportSummary nesnesi - `period` (string): Dönem. - `phone_number_id` (string): Süzülen numara; `null` tüm numaraların toplamıdır. - `sent` (integer): Gönderilen mesaj (test hariç). - `delivered` (integer): Son durumu teslim olan mesaj. - `read` (integer): Okunan mesaj. - `effective_delivered` (integer): `delivered + read` (okunan her mesaj teslim edilmiştir). - `failed` (integer): Başarısız mesaj. - `delivery_rate` (number): `effective_delivered / sent` yüzdesi. - `read_rate` (number): `read / sent` yüzdesi. - `failure_rate` (number): `failed / sent` yüzdesi. - `balance` (integer): Güncel kredi bakiyesi. - `by_number` (array): Süzgeç verilmediğinde numara başına aynı alanlar. ## Rapor özetini getir `GET https://api.verimerkezi.app/wa/reports/summary` Giden mesajların dönemsel özetini döndürür. Test anahtarıyla gönderilen mesajlar dahil edilmez. Kapsam: `reports:read` ### Sorgu parametreleri - `period` (string): Dönem. - `phone_number_id` (string): Yalnız bu numaranın özeti (`by_number` boş döner). ```bash curl -X GET "https://api.verimerkezi.app/wa/reports/summary?period=30d" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "period": "30d", "phone_number_id": null, "sent": 8742, "delivered": 2267, "read": 6234, "effective_delivered": 8501, "failed": 241, "delivery_rate": 97.2, "read_rate": 71.3, "failure_rate": 2.8, "balance": 1258, "by_number": [ { "phone_number_id": "1234567890", "display_phone_number": "+90 555 000 00 00", "sent": 5120, "delivered": 1170, "read": 3810, "effective_delivered": 4980, "failed": 140, "delivery_rate": 97.3, "read_rate": 74.4, "failure_rate": 2.7 } ] } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `reports:read` kapsamı yok. - 404 `number_not_found`: Numara size ait değil. - 422 `invalid_period`: `period` yalnız `7d`, `30d`, `90d` olabilir. --- # Kredi Mesaj kredisi bakiyenizi, etkin paketlerinizi, kullanım istatistiğinizi ve kredi hareketlerinizi görün. ## CreditTransaction nesnesi - `id` (integer): Hareket kimliği. - `type` (string): Hareket türü (ör. `consume`). - `amount` (integer): Kredi miktarı. - `balance_before` (integer): Önceki bakiye. - `balance_after` (integer): Sonraki bakiye. - `source` (string): Kaynak (ör. `api`). - `reference_type` (string): Referans türü. - `reference_id` (string): Referans kimliği. - `note` (string): Açıklama. - `created_at` (string): Zaman. ## Bakiyeyi getir `GET https://api.verimerkezi.app/wa/credit/balance` Güncel mesaj kredisi bakiyesini ve son 30 günün özetini döndürür. ```bash curl -X GET "https://api.verimerkezi.app/wa/credit/balance" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "balance": 9875, "total_purchased": 10000, "total_used": 125, "total_refunded": 0, "active_packages": 1, "last_consume_at": "2026-10-10 10:15:03", "low_threshold": 5, "status": "active" } ``` ## Etkin paketleri listele `GET https://api.verimerkezi.app/wa/credit/packages` Hesabınızdaki etkin kredi paketlerini döndürür. Paketler yakın bitiş tarihinden başlayarak tüketilir. ```bash curl -X GET "https://api.verimerkezi.app/wa/credit/packages" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "packages": [ { "id": 42, "package_slug": "profesyonel", "package_name": "Profesyonel", "credits_purchased": 10000, "credits_used": 125, "credits_remaining": 9875, "activated_at": "2026-05-27 14:33:21", "expires_at": "2027-05-27 14:33:21", "source": "purchase" } ], "count": 1 } ``` ## Kullanım istatistiğini getir `GET https://api.verimerkezi.app/wa/credit/usage` Seçilen gün sayısı için kredi kullanım özetini, kaynak kırılımını ve günlük seriyi döndürür. Kapsam: `reports:read` ### Sorgu parametreleri - `days` (integer): Gün sayısı (1–365). ```bash curl -X GET "https://api.verimerkezi.app/wa/credit/usage?days=30" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "period_days": 30, "balance": 9875, "total_purchased": 10000, "total_used": 125, "total_refunded": 0, "by_source": { "api": 120, "panel": 5 }, "daily_usage": { "2026-10-09": 14, "2026-10-10": 6 }, "last_consume_at": "2026-10-10 10:15:03" } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `reports:read` kapsamı yok. ## Kredi hareketlerini listele `GET https://api.verimerkezi.app/wa/credit/transactions` Kredi hareketlerini yeniden eskiye, imleçle sayfalı döndürür. Kapsam: `reports:read` ### Sorgu parametreleri - `limit` (integer): Sayfa boyutu (1–200). - `cursor` (string): Opak imleç. - `type` (string): Hareket türü süzgeci (ör. `consume`). - `source` (string): Kaynak süzgeci (ör. `api`). ```bash curl -X GET "https://api.verimerkezi.app/wa/credit/transactions?limit=50" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "transactions": [ { "id": 90211, "type": "consume", "amount": 1, "balance_before": 9876, "balance_after": 9875, "source": "api", "reference_type": "reserve", "reference_id": "api_reserve:4f2a9c1e7b3d5a60", "note": "REST API rezervasyon (gönderim öncesi)", "created_at": "2026-10-10 10:15:03" } ], "next_cursor": "eyJpZCI6NTAsImsiOiJpZCIsImQiOiJkZXNjIn0", "has_more": true } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `reports:read` kapsamı yok. --- # Aramalar WhatsApp sesli aramalarını yönetin: gelen aramayı kabul edin ya da reddedin, izin veren müşteriyi arayın, arama ayarlarını ve geçmişini görün. Ses WebRTC ile sizin ucunuz ve Meta arasında akar; Veri Merkezi yalnız sinyal verisini (SDP) iletir. ## Call nesnesi - `call_id` (string): Arama kimliği. - `phone_number_id` (string): Numaranızın Meta kimliği. - `direction` (string): Yön. - `from` (string): Arayan. - `to` (string): Aranan. - `status` (string): Durum. - `started_at` (string): Başlangıç. - `ended_at` (string): Bitiş. - `duration_sec` (integer): Süre (saniye). - `billable_pulses` (integer): `⌈süre / 6⌉`; yalnız işletme başlatmalı aramada, bilgi amaçlı (Meta doğrudan faturalar). - `last_action` (string): Son eylem. - `errors` (array): Hatalar. - `is_test` (boolean): Test anahtarıyla oluşturuldu mu. - `created_at` (string): Kayıt zamanı. ## Arama ayarlarını getir `GET https://api.verimerkezi.app/wa/numbers/{phone_number_id}/calling` Numaranın arama ayarlarını ve arama uygunluğunu (`eligibility`) döndürür. Kapsam: `calls:read` ### Yol parametreleri - `phone_number_id` (string, zorunlu): Numaranızın Meta kimliği (yalnız rakam, 5–32 hane). ### Sorgu parametreleri - `raw` (boolean): `1` Meta'nın ham calling nesnesini ekler (`raw`). ```bash curl -X GET "https://api.verimerkezi.app/wa/numbers/1234567890/calling" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "phone_number_id": "1234567890", "enabled": true, "callback_permission": true, "call_icon_visibility": "DEFAULT", "call_hours": { "status": "ENABLED", "timezone_id": "Europe/Istanbul", "weekly_operating_hours": [ { "day_of_week": "MONDAY", "open_time": "0900", "close_time": "1800" } ] }, "messaging_limit_tier": "TIER_2K", "messaging_limit_ok": true, "eligibility": { "eligible_inbound": true, "eligible_outbound": true, "checks": [ { "key": "cloud_api_only", "ok": true, "applies_to": [ "inbound", "outbound" ], "title_tr": "Numara yalnız API'ye bağlı olmalı", "detail_tr": "Numara Cloud API'ye bağlı.", "how_to_fix_tr": null }, { "key": "messaging_limit", "ok": true, "applies_to": [ "inbound", "outbound" ], "title_tr": "Günlük mesaj limiti en az 2.000 olmalı", "detail_tr": "Limit yeterli.", "how_to_fix_tr": null }, { "key": "payment_method", "ok": null, "applies_to": [ "outbound" ], "title_tr": "Ödeme yöntemi", "detail_tr": "Bilgi Meta'dan okunamadı.", "how_to_fix_tr": "Meta Business Suite'te ödeme yöntemini kontrol edin." } ] } } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `calls:read` kapsamı yok. - 404 `number_not_found`: Numara size ait değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 422 `invalid_phone_number_id`: `phone_number_id` rakam değil. ## Arama ayarlarını güncelle `PATCH https://api.verimerkezi.app/wa/numbers/{phone_number_id}/calling` Aramayı açar/kapatır, arama saatlerini ve geri arama iznini ayarlar. Yalnız gönderdiğiniz alanlar değişir. Kapsam: `calls:write` ### Yol parametreleri - `phone_number_id` (string, zorunlu): Numaranızın Meta kimliği (yalnız rakam, 5–32 hane). ### İstek gövdesi - `enabled` (boolean): Aramayı açar/kapatır. - `callback_permission` (boolean): Müşteri aradığında geri arama izni önerilir. - `call_icon_visibility` (string): Sohbetteki arama simgesi. - `call_hours` (object): Arama saatleri. ```bash curl -X PATCH "https://api.verimerkezi.app/wa/numbers/1234567890/calling" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "enabled": true, "callback_permission": true, "call_icon_visibility": "DEFAULT", "call_hours": { "status": "ENABLED", "timezone_id": "Europe/Istanbul", "weekly_operating_hours": [ { "day_of_week": "MONDAY", "open_time": "0900", "close_time": "1800" } ] } }' ``` Yanıt (200): ```json { "ok": true, "phone_number_id": "1234567890", "enabled": true, "callback_permission": true, "call_icon_visibility": "DEFAULT", "call_hours": { "status": "ENABLED", "timezone_id": "Europe/Istanbul", "weekly_operating_hours": [ { "day_of_week": "MONDAY", "open_time": "0900", "close_time": "1800" } ] }, "messaging_limit_tier": "TIER_2K", "messaging_limit_ok": true, "eligibility": { "eligible_inbound": true, "eligible_outbound": true, "checks": [ { "key": "cloud_api_only", "ok": true, "applies_to": [ "inbound", "outbound" ], "title_tr": "Numara yalnız API'ye bağlı olmalı", "detail_tr": "Numara Cloud API'ye bağlı.", "how_to_fix_tr": null }, { "key": "messaging_limit", "ok": true, "applies_to": [ "inbound", "outbound" ], "title_tr": "Günlük mesaj limiti en az 2.000 olmalı", "detail_tr": "Limit yeterli.", "how_to_fix_tr": null }, { "key": "payment_method", "ok": null, "applies_to": [ "outbound" ], "title_tr": "Ödeme yöntemi", "detail_tr": "Bilgi Meta'dan okunamadı.", "how_to_fix_tr": "Meta Business Suite'te ödeme yöntemini kontrol edin." } ] }, "updated": true } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `calls:write` kapsamı yok. - 404 `number_not_found`: Numara size ait değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 422 `invalid_request`: Parametre hatalı. - 422 `calling_not_supported_for_number`: Coexistence ya da Cloud API dışı numara. - 422 `calling_messaging_limit_too_low`: Günlük mesaj limiti 2.000'in altında. - 422 `calling_prerequisites_unmet`: Teknik önkoşullar karşılanmıyor (Meta 138018). - 422 `calling_outbound_unavailable`: İşletme başlatmalı arama bu numara/ülkede yok (Meta 138013). - 422 `calling_disabled_quality`: Meta kalite nedeniyle aramayı geçici kapattı (Meta 138014). ## Arama geçmişini listele `GET https://api.verimerkezi.app/wa/calls` Arama kayıtlarını yeniden eskiye döndürür. Kapsam: `calls:read` ### Sorgu parametreleri - `phone_number_id` (string): Numara süzgeci. - `from` (string): Başlangıç (ISO 8601). - `to` (string): Bitiş (ISO 8601). - `peer` (string): Karşı taraf numarası. - `limit` (integer): Sayfa boyutu (1–100). - `cursor` (string): Önceki yanıttaki `next_cursor`. ```bash curl -X GET "https://api.verimerkezi.app/wa/calls?phone_number_id=1234567890" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "calls": [ { "call_id": "wacid.HBgLOTA1NTUxMjM0NTY3FQIAEhggQkE1", "phone_number_id": "1234567890", "direction": "user_initiated", "from": "905550000000", "to": "905550000001", "status": "completed", "started_at": "2026-10-09T14:00:05+03:00", "ended_at": "2026-10-09T14:01:19+03:00", "duration_sec": 74, "billable_pulses": null, "last_action": "accept", "errors": null, "is_test": false, "created_at": "2026-10-09T14:00:00+03:00" } ], "has_more": false, "next_cursor": null } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `calls:read` kapsamı yok. - 404 `number_not_found`: Numara size ait değil. ## Müşteriyi ara `POST https://api.verimerkezi.app/wa/calls` İşletme başlatmalı arama yapar. Müşterinin arama izni olmalıdır. Sonuç `call.status` ve Meta yanıtıyla gelen `call.connect` (SDP answer) olaylarıyla izlenir. Kapsam: `calls:write` ### Başlıklar - `Idempotency-Key` (): 8–128 karakterlik benzersiz anahtar (UUID önerilir). Aynı anahtar ve aynı gövdeyle yapılan tekrar, ilk yanıtı `Idempotent-Replay: true` başlığıyla döndürür. 24 saat saklanır. ### İstek gövdesi - `phone_number_id` (string, zorunlu): Numaranızın Meta kimliği. - `to` (string, zorunlu): Aranacak müşteri. - `sdp_offer` (string, zorunlu): WebRTC SDP teklifi (RFC 8866). - `biz_opaque` (string): En fazla 512 karakterlik eşleştirme değeri. ```bash curl -X POST "https://api.verimerkezi.app/wa/calls" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "sdp_offer": "v=0\r\no=- 4611731400430051336 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n...", "biz_opaque": "crm-ticket-42" }' ``` Yanıt (201): ```json { "ok": true, "call_id": "wacid.HBgLOTA1NTUxMjM0NTY3FQIAEhggQkE2", "status": "initiated", "direction": "business_initiated", "to": "905550000000" } ``` Hatalar: - 403 `call_permission_required`: Müşterinin arama izni yok. - 403 `insufficient_scope`: Anahtarda `calls:write` kapsamı yok. - 404 `number_not_found`: Numara size ait değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 409 `calling_not_enabled`: Numarada arama kapalı. - 409 `meta_outcome_unknown`: Sonuç belirsiz; tekrar göndermeyin, `call.*` olayını bekleyin. - 409 `idempotency_in_progress`: Aynı anahtarla bir istek hâlâ işleniyor. `Retry-After` kadar bekleyin. - 422 `idempotency_key_conflict`: Aynı anahtar farklı bir gövdeyle kullanılmış. - 422 `invalid_sdp`: SDP boş ya da geçersiz. - 422 `invalid_phone`: Telefon geçersiz. - 422 `calling_not_available_in_country`: Alıcının ülkesinde işletme araması yok. - 429 `call_rate_limited`: Arama sınırı aşıldı. ## Gelen aramayı ön-kabul et `POST https://api.verimerkezi.app/wa/calls/{call_id}/pre_accept` Bağlantıyı önceden kurmak için gelen aramayı SDP yanıtıyla ön-kabul eder; ardından `accept` çağırın. Kapsam: `calls:write` ### Yol parametreleri - `call_id` (string, zorunlu): Arama kimliği (URL-kodlu). İçinde `/` varsa yol yerine `_` yazıp gövdede `call_id` gönderin. ### İstek gövdesi - `sdp_answer` (string, zorunlu): WebRTC SDP yanıtı. - `call_id` (string): Yalnız yol `_` ise. ```bash curl -X POST "https://api.verimerkezi.app/wa/calls/wacid.HBgLOTA1NTUxMjM0NTY3FQIAEhggQkE1/pre_accept" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sdp_answer": "v=0\r\no=- 4611731400430051336 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n..." }' ``` Yanıt (200): ```json { "ok": true, "call_id": "wacid.HBgLOTA1NTUxMjM0NTY3FQIAEhggQkE1", "action": "pre_accept", "success": true } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `calls:write` kapsamı yok. - 404 `call_not_found`: Arama yok, size ait değil ya da test/canlı anahtar uyuşmuyor. - 409 `call_ended`: Arama sona ermiş. - 409 `call_action_conflict`: Aynı eylem farklı parametreyle uygulanmış. - 409 `action_in_progress`: Eylem hâlâ işleniyor. - 409 `invalid_action_for_direction`: Başlattığınız aramada yalnız `terminate`. - 422 `invalid_sdp`: SDP geçersiz. ## Gelen aramayı kabul et `POST https://api.verimerkezi.app/wa/calls/{call_id}/accept` Gelen aramayı SDP yanıtıyla kabul eder. Kapsam: `calls:write` ### Yol parametreleri - `call_id` (string, zorunlu): Arama kimliği (URL-kodlu). İçinde `/` varsa yol yerine `_` yazıp gövdede `call_id` gönderin. ### İstek gövdesi - `sdp_answer` (string, zorunlu): WebRTC SDP yanıtı. - `call_id` (string): Yalnız yol `_` ise. ```bash curl -X POST "https://api.verimerkezi.app/wa/calls/wacid.HBgLOTA1NTUxMjM0NTY3FQIAEhggQkE1/accept" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sdp_answer": "v=0\r\no=- 4611731400430051336 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n..." }' ``` Yanıt (200): ```json { "ok": true, "call_id": "wacid.HBgLOTA1NTUxMjM0NTY3FQIAEhggQkE1", "action": "accept", "success": true } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `calls:write` kapsamı yok. - 404 `call_not_found`: Arama bulunamadı. - 409 `call_ended`: Arama sona ermiş. - 409 `call_action_conflict`: Aynı eylem farklı parametreyle uygulanmış. ## Gelen aramayı reddet `POST https://api.verimerkezi.app/wa/calls/{call_id}/reject` Gelen aramayı reddeder. Kapsam: `calls:write` ### Yol parametreleri - `call_id` (string, zorunlu): Arama kimliği (URL-kodlu). İçinde `/` varsa yol yerine `_` yazıp gövdede `call_id` gönderin. ```bash curl -X POST "https://api.verimerkezi.app/wa/calls/wacid.HBgLOTA1NTUxMjM0NTY3FQIAEhggQkE1/reject" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "call_id": "wacid.HBgLOTA1NTUxMjM0NTY3FQIAEhggQkE1", "action": "reject", "success": true } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `calls:write` kapsamı yok. - 404 `call_not_found`: Arama bulunamadı. - 409 `call_ended`: Arama sona ermiş. ## Aramayı sonlandır `POST https://api.verimerkezi.app/wa/calls/{call_id}/terminate` Süren aramayı kapatır. İşletme başlatmalı aramalarda kullanılabilen tek eylemdir. Kapsam: `calls:write` ### Yol parametreleri - `call_id` (string, zorunlu): Arama kimliği (URL-kodlu). İçinde `/` varsa yol yerine `_` yazıp gövdede `call_id` gönderin. ```bash curl -X POST "https://api.verimerkezi.app/wa/calls/wacid.HBgLOTA1NTUxMjM0NTY3FQIAEhggQkE1/terminate" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "call_id": "wacid.HBgLOTA1NTUxMjM0NTY3FQIAEhggQkE1", "action": "terminate", "success": true } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `calls:write` kapsamı yok. - 404 `call_not_found`: Arama bulunamadı. - 409 `call_ended`: Arama sona ermiş. ## Arama izni iste `POST https://api.verimerkezi.app/wa/calls/permission-request` Müşteriye arama izni isteyen etkileşimli bir mesaj gönderir. Yanıt `call.permission_reply` olayıyla gelir. Normal mesaj gibi 1 kredi düşer; başarısızsa iade edilir. Kapsam: `calls:write` ### Başlıklar - `Idempotency-Key` (): 8–128 karakterlik benzersiz anahtar (UUID önerilir). Aynı anahtar ve aynı gövdeyle yapılan tekrar, ilk yanıtı `Idempotent-Replay: true` başlığıyla döndürür. 24 saat saklanır. ### İstek gövdesi - `phone_number_id` (string, zorunlu): Numaranızın Meta kimliği. - `to` (string, zorunlu): Müşteri telefonu. - `body` (string): Mesaj metni (en fazla 1024). ```bash curl -X POST "https://api.verimerkezi.app/wa/calls/permission-request" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "to": "905550000000", "body": "Siparişiniz hakkında sizi arayabilir miyiz?" }' ``` Yanıt (201): ```json { "ok": true, "message_id": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "to": "905550000000", "credits_consumed": 1, "balance": 4999 } ``` Hatalar: - 402 `insufficient_credits`: Kredi yetersiz. - 403 `insufficient_scope`: Anahtarda `calls:write` kapsamı yok. - 404 `number_not_found`: Numara size ait değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 409 `idempotency_in_progress`: Aynı anahtarla bir istek hâlâ işleniyor. `Retry-After` kadar bekleyin. - 422 `idempotency_key_conflict`: Aynı anahtar farklı bir gövdeyle kullanılmış. - 422 `outside_service_window`: 24 saatlik pencere kapalı. - 422 `invalid_phone`: Telefon geçersiz. - 429 `permission_request_limited`: Aynı kişiye 24 saatte 1, 7 günde 2 istek sınırı. ## Arama izni durumunu getir `GET https://api.verimerkezi.app/wa/calls/permission` Bir müşterinin arama izni durumunu döndürür. Kapsam: `calls:read` ### Sorgu parametreleri - `phone_number_id` (string, zorunlu): Numaranızın Meta kimliği. - `to` (string, zorunlu): Müşteri telefonu. ```bash curl -X GET "https://api.verimerkezi.app/wa/calls/permission?phone_number_id=1234567890&to=905550000000" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "status": "temporary", "expires_at": "2026-10-15T10:00:00+03:00", "can_request": false, "can_call": true, "actions": [] } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `calls:read` kapsamı yok. - 404 `number_not_found`: Numara size ait değil. - 422 `invalid_phone_number_id`: `phone_number_id` rakam değil. --- # Formlar WhatsApp içinde açılan çok ekranlı formları (WhatsApp Flows) oluşturun, yayınlayın ve izleyin. Formlar WABA düzeyindedir; yönetim kredisizdir. ## Flow nesnesi - `id` (string): Form kimliği; `flow_id` ile aynı değer (yalnız `GET /flows` listesinde). - `flow_id` (string): Form kimliği. - `name` (string): Form adı. - `status` (string): Durum. - `categories` (array): Kategoriler. - `validation_errors` (array): Meta Flow JSON doğrulama hataları. - `health_status` (object): Meta sağlık durumu (`can_send_message` vb.). - `json_version` (string): Flow JSON sürümü. - `data_api_version` (string): Data API sürümü. - `endpoint_uri` (string): Endpoint adresi (data_exchange). - `waba_id` (string): Formun bağlı olduğu WABA (yalnız `GET /flows` listesinde). - `phone_number_id` (string): Listeyi istediğiniz numara (yalnız `GET /flows` listesinde). - `updated_at` (string): Veri Merkezi kaydında formun son değişikliğinin gözlendiği zaman (ISO 8601, ofsetli). Meta form güncellenme zamanı vermez; ad, durum, kategori ya da doğrulama hataları değiştiğinde ilerler. Kayıt yazılamadıysa `null` (yalnız `GET /flows` listesinde). ## Formları listele `GET https://api.verimerkezi.app/wa/flows` Numaranın WABA'sındaki formları döndürür. İmleçli sayfalama: yanıttaki `next_cursor` değerini bir sonraki istekte `cursor` (ya da `after`) olarak gönderin; `has_more: false` olduğunda liste biter. Kapsam: `flows:read` ### Sorgu parametreleri - `phone_number_id` (string, zorunlu): Numaranızın Meta kimliği. - `limit` (integer): Sayfa boyutu (1–200; 200'den büyük değer 200'e indirilir). Verilmezse Meta varsayılanı (25). - `after` (string): Sayfalama imleci (`next_cursor`). `cursor` adıyla da gönderilebilir. - `cursor` (string): `after` ile aynı; önceki yanıttaki `next_cursor` değeri. ```bash curl -X GET "https://api.verimerkezi.app/wa/flows?phone_number_id=1234567890" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "phone_number_id": "1234567890", "waba_id": "1029384756", "flows": [ { "id": "1234567890123456", "flow_id": "1234567890123456", "name": "Kredi başvurusu", "status": "PUBLISHED", "categories": [ "LEAD_GENERATION" ], "validation_errors": [], "health_status": { "can_send_message": "AVAILABLE" }, "json_version": "7.2", "data_api_version": null, "endpoint_uri": null, "waba_id": "1029384756", "phone_number_id": "1234567890", "updated_at": "2026-10-14T10:32:05+03:00" } ], "paging": { "after": "QVFIUmxNQ2w0ZAFRB" }, "next_cursor": "QVFIUmxNQ2w0ZAFRB", "has_more": true } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `flows:read` kapsamı yok. - 404 `number_not_found`: Numara size ait değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 422 `invalid_phone_number_id`: `phone_number_id` zorunlu. - 422 `invalid_request`: `after` / `cursor` geçersiz (`field: after`). ## Form oluştur `POST https://api.verimerkezi.app/wa/flows` Formu verilen Flow JSON ile `DRAFT` durumunda oluşturur. Meta doğrulama hataları `validation_errors` alanında döner. Kapsam: `flows:write` ### Başlıklar - `Idempotency-Key` (): 8–128 karakterlik benzersiz anahtar (UUID önerilir). Aynı anahtar ve aynı gövdeyle yapılan tekrar, ilk yanıtı `Idempotent-Replay: true` başlığıyla döndürür. 24 saat saklanır. ### İstek gövdesi - `phone_number_id` (string, zorunlu): Formun WABA'sını belirleyen numara. - `name` (string, zorunlu): Form adı (en fazla 200). - `categories` (array, zorunlu): En az bir kategori. - `flow_json` (object | string, zorunlu): Flow JSON nesnesi ya da metni (en fazla 10 MB). - `endpoint_uri` (string): Data exchange için kendi `https` uç noktanız. ```bash curl -X POST "https://api.verimerkezi.app/wa/flows" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number_id": "1234567890", "name": "Kredi başvurusu", "categories": [ "LEAD_GENERATION" ], "flow_json": { "version": "7.2", "screens": [ { "id": "WELCOME", "title": "Başvuru", "terminal": true, "layout": { "type": "SingleColumnLayout", "children": [] } } ] } }' ``` Yanıt (201): ```json { "ok": true, "flow_id": "1234567890123456", "status": "DRAFT", "validation_errors": [] } ``` Hatalar: - 403 `test_mode_unsupported`: Yazma uçları test anahtarıyla çalışmaz. - 403 `insufficient_scope`: Anahtarda `flows:write` kapsamı yok. - 404 `flow_not_found`: Form yok ya da size ait değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 422 `invalid_flow_id`: `flow_id` yalnız rakam olmalı. - 422 `meta_flow_failed`: Meta isteği reddetti. Zincirlenmiş eşitlikte `error.hint` (`flow_chained_equality` | `crm_update_required`) ve `error.hint_message` eklenir. - 409 `idempotency_in_progress`: Aynı anahtarla bir istek hâlâ işleniyor. `Retry-After` kadar bekleyin. - 422 `idempotency_key_conflict`: Aynı anahtar farklı bir gövdeyle kullanılmış. - 422 `invalid_request`: Parametre hatalı. - 422 `invalid_flow_json`: Flow JSON geçersiz ya da 10 MB'ı aşıyor. - 409 `meta_outcome_unknown`: Sonuç belirsiz; `GET /flows` ile kontrol edin. ## Form ayrıntısını getir `GET https://api.verimerkezi.app/wa/flows/{flow_id}` Formun durumunu, doğrulama hatalarını ve sağlık bilgisini döndürür. Kapsam: `flows:read` ### Yol parametreleri - `flow_id` (string, zorunlu): Form kimliği (yalnız rakam). ### Sorgu parametreleri - `phone_number_id` (string): Yalnız Meta arayüzünde oluşturulmuş formlar için; formu hesabınıza bağlar. ```bash curl -X GET "https://api.verimerkezi.app/wa/flows/1234567890123456" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "flow_id": "1234567890123456", "name": "Kredi başvurusu", "status": "PUBLISHED", "categories": [ "LEAD_GENERATION" ], "validation_errors": [], "health_status": { "can_send_message": "AVAILABLE" }, "json_version": "7.2", "data_api_version": null, "endpoint_uri": null, "ok": true } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `flows:read` kapsamı yok. - 404 `flow_not_found`: Form yok ya da size ait değil. - 422 `invalid_flow_id`: `flow_id` yalnız rakam olmalı. ## Form JSON'unu güncelle `PUT https://api.verimerkezi.app/wa/flows/{flow_id}/json` Taslak formun Flow JSON'unu değiştirir. Yalnız `DRAFT` formlarda çalışır. Kapsam: `flows:write` ### Yol parametreleri - `flow_id` (string, zorunlu): Form kimliği (yalnız rakam). ### Sorgu parametreleri - `phone_number_id` (string): Yalnız Meta arayüzünde oluşturulmuş formlar için; formu hesabınıza bağlar. ### İstek gövdesi - `flow_json` (object | string, zorunlu): Yeni Flow JSON. ```bash curl -X PUT "https://api.verimerkezi.app/wa/flows/1234567890123456/json" \ -H "Authorization: Bearer $VM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "flow_json": { "version": "7.2", "screens": [] } }' ``` Yanıt (200): ```json { "ok": true, "flow_id": "1234567890123456", "validation_errors": [ { "error": "INVALID_PROPERTY_VALUE", "message": "Ekran listesi boş olamaz.", "line_start": 1 } ] } ``` Hatalar: - 403 `test_mode_unsupported`: Yazma uçları test anahtarıyla çalışmaz. - 403 `insufficient_scope`: Anahtarda `flows:write` kapsamı yok. - 404 `flow_not_found`: Form yok ya da size ait değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 422 `invalid_flow_id`: `flow_id` yalnız rakam olmalı. - 422 `meta_flow_failed`: Meta isteği reddetti. Zincirlenmiş eşitlikte `error.hint` (`flow_chained_equality` | `crm_update_required`) ve `error.hint_message` eklenir. - 409 `flow_not_draft`: Yalnız DRAFT formda güncellenebilir. - 422 `invalid_flow_json`: Flow JSON geçersiz. ## Formu yayınla `POST https://api.verimerkezi.app/wa/flows/{flow_id}/publish` Formu yayınlar. Yayınlanan form değiştirilemez; yeni sürüm için yeni form oluşturun. Kapsam: `flows:write` ### Yol parametreleri - `flow_id` (string, zorunlu): Form kimliği (yalnız rakam). ### Sorgu parametreleri - `phone_number_id` (string): Yalnız Meta arayüzünde oluşturulmuş formlar için; formu hesabınıza bağlar. ```bash curl -X POST "https://api.verimerkezi.app/wa/flows/1234567890123456/publish" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "flow_id": "1234567890123456", "status": "PUBLISHED" } ``` Hatalar: - 403 `test_mode_unsupported`: Yazma uçları test anahtarıyla çalışmaz. - 403 `insufficient_scope`: Anahtarda `flows:write` kapsamı yok. - 404 `flow_not_found`: Form yok ya da size ait değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 422 `invalid_flow_id`: `flow_id` yalnız rakam olmalı. - 422 `meta_flow_failed`: Meta isteği reddetti. Zincirlenmiş eşitlikte `error.hint` (`flow_chained_equality` | `crm_update_required`) ve `error.hint_message` eklenir. ## Formu kullanımdan kaldır `POST https://api.verimerkezi.app/wa/flows/{flow_id}/deprecate` Yayınlanmış formu kullanımdan kaldırır. Kapsam: `flows:write` ### Yol parametreleri - `flow_id` (string, zorunlu): Form kimliği (yalnız rakam). ### Sorgu parametreleri - `phone_number_id` (string): Yalnız Meta arayüzünde oluşturulmuş formlar için; formu hesabınıza bağlar. ```bash curl -X POST "https://api.verimerkezi.app/wa/flows/1234567890123456/deprecate" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "flow_id": "1234567890123456", "status": "DEPRECATED" } ``` Hatalar: - 403 `test_mode_unsupported`: Yazma uçları test anahtarıyla çalışmaz. - 403 `insufficient_scope`: Anahtarda `flows:write` kapsamı yok. - 404 `flow_not_found`: Form yok ya da size ait değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 422 `invalid_flow_id`: `flow_id` yalnız rakam olmalı. - 422 `meta_flow_failed`: Meta isteği reddetti. ## Taslak formu sil `DELETE https://api.verimerkezi.app/wa/flows/{flow_id}` Taslak formu siler. Yalnız `DRAFT` formlarda çalışır. Kapsam: `flows:write` ### Yol parametreleri - `flow_id` (string, zorunlu): Form kimliği (yalnız rakam). ### Sorgu parametreleri - `phone_number_id` (string): Yalnız Meta arayüzünde oluşturulmuş formlar için; formu hesabınıza bağlar. ```bash curl -X DELETE "https://api.verimerkezi.app/wa/flows/1234567890123456" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "flow_id": "1234567890123456", "deleted": true } ``` Hatalar: - 403 `test_mode_unsupported`: Yazma uçları test anahtarıyla çalışmaz. - 403 `insufficient_scope`: Anahtarda `flows:write` kapsamı yok. - 404 `flow_not_found`: Form yok ya da size ait değil. - 409 `number_detached`: Numara hesabınızdan ayrılmış; panelden yeniden bağlayın. - 422 `invalid_flow_id`: `flow_id` yalnız rakam olmalı. - 422 `meta_flow_failed`: Meta isteği reddetti. - 409 `flow_not_draft`: Yalnız DRAFT form silinebilir. ## Önizleme bağlantısı al `GET https://api.verimerkezi.app/wa/flows/{flow_id}/preview` Formun Meta önizleme bağlantısını döndürür. Kapsam: `flows:read` ### Yol parametreleri - `flow_id` (string, zorunlu): Form kimliği (yalnız rakam). ### Sorgu parametreleri - `invalidate` (boolean): `true` yeni bağlantı üretir. - `phone_number_id` (string): Yalnız Meta arayüzünde oluşturulmuş formlar için; formu hesabınıza bağlar. ```bash curl -X GET "https://api.verimerkezi.app/wa/flows/1234567890123456/preview" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "flow_id": "1234567890123456", "preview_url": "https://business.facebook.com/wa/manage/flows/1234567890123456/preview/?token=abc123", "expires_at": "2026-11-09T10:00:00+0000" } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `flows:read` kapsamı yok. - 404 `flow_not_found`: Form bulunamadı. ## Form metriklerini getir `GET https://api.verimerkezi.app/wa/flows/{flow_id}/metrics` Endpoint'li formların Meta metriklerini aynen döndürür. Varsayılan aralık son 7 gündür. Kapsam: `flows:read` ### Yol parametreleri - `flow_id` (string, zorunlu): Form kimliği (yalnız rakam). ### Sorgu parametreleri - `from` (string): Başlangıç (YYYY-AA-GG); varsayılan `to` − 6 gün. - `to` (string): Bitiş (YYYY-AA-GG); varsayılan bugün. - `metric` (string): Metrik. - `granularity` (string): Ayrıntı. - `phone_number_id` (string): Yalnız Meta arayüzünde oluşturulmuş formlar için; formu hesabınıza bağlar. ```bash curl -X GET "https://api.verimerkezi.app/wa/flows/1234567890123456/metrics" \ -H "Authorization: Bearer $VM_API_KEY" ``` Yanıt (200): ```json { "ok": true, "flow_id": "1234567890123456", "from": "2026-10-01", "to": "2026-10-07", "metric": { "name": "ENDPOINT_REQUEST_COUNT", "granularity": "DAY", "data_points": [] } } ``` Hatalar: - 403 `insufficient_scope`: Anahtarda `flows:read` kapsamı yok. - 404 `flow_not_found`: Form bulunamadı. --- # Webhook olayları Veri Merkezi, abone olduğunuz olayları HTTPS üzerinden `POST` ile sunucunuza gönderir. Meta kaynaklı olaylar (mesaj, durum, arama, form…) genellikle 1 saniye içinde teslim edilir; Meta dışından tetiklenen olaylar (`credit.*`, `number.detached` / `number.purged`, panelden `contact.opt_out_changed`, kampanya/otomasyon `message.sent`, `privacy.erasure_completed`) dakikalık kuyrukla en geç ~1 dakikada gider. Başarısız teslimler yeniden denenir. Her istek `event`, `event_id` (UUIDv7), `event_type`, `occurred_at`, `created_at` ve `data` alanlarını taşıyan bir zarf içerir. `*` aboneliği yalnız 12 klasik olayı kapsar; diğer 23 olayı açıkça seçmelisiniz. ## message.received Müşteri size mesaj gönderdi. Sohbet botları için temel olaydır. `data.text` düz metindir; medyada açıklamayı taşır, dosya `data.media.media_id` ile indirilir. Telefonu görünmeyen kullanıcıda `from` `null`, kimlik `user_id` olur. Buton yanıtlarında `button: { payload, text }` dolar. Etkileşimli mesajda `interactive: { type, name }` dolar (ör. `button_reply`, `list_reply`; form yanıtında `{ "type": "nfm_reply", "name": "flow" }`), diğer mesajlarda `null`. Düğme ve liste seçiminde `interactive` ayrıca `reply_id`, `reply_title` ve (yalnız listede) `reply_description` taşır. Bilinmeyen mesaj türlerinde ham Meta mesajı `data.raw` alanında gelir (boyut sınırını aşarsa `null`). `type: "unsupported"` olduğunda `data.unsupported` = `{ type, raw_type, error_code, error_title }` Meta'nın nedenini taşır (ör. `131060`: WhatsApp mesajın içeriğini iletmedi); diğer türlerde `null`. ```json { "event": "message.received", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb00", "event_type": "message.received", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhggQTNFQkQ2RjQ3MEIyQzk0RTE4AA==", "from": "905550000000", "user_id": null, "username": null, "name": "Ayşe Yılmaz", "type": "text", "text": "Merhaba, siparişim ne durumda?", "media": null, "timestamp": "1791619200", "phone_number_id": "1234567890", "contact": { "wa_id": "905550000000", "user_id": null, "username": null, "profile_name": "Ayşe Yılmaz" }, "context": null, "reaction": null, "location": null, "contacts": null, "original_message_id": null, "button": null, "interactive": null } } ``` ## message.echo Coexistence: işletme, telefondaki WhatsApp Business uygulamasından müşteriye yazdı. `from` işletme, `to` müşteridir. Botunuzun çift yanıt vermesini önlemek için kullanın. Etkileşimli mesajda `interactive: { type, name }` dolar (ör. `button_reply`, `list_reply`; form yanıtında `{ "type": "nfm_reply", "name": "flow" }`), diğer mesajlarda `null`. Düğme ve liste seçiminde `interactive` ayrıca `reply_id`, `reply_title` ve (yalnız listede) `reply_description` taşır. ```json { "event": "message.echo", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb01", "event_type": "message.echo", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSRUNITzE=", "from": "905550000001", "to": "905550000000", "type": "text", "text": "Teşekkürler, hemen dönüyorum.", "timestamp": "1791619300", "source": "coexistence_app", "phone_number_id": "1234567890", "user_id": null, "media": null, "context": null, "reaction": null, "location": null, "contacts": null, "original_message_id": null, "button": null, "interactive": null } } ``` ## message.status.sent Meta gönderimi kabul etti. ```json { "event": "message.status.sent", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb02", "event_type": "message.status.sent", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "recipient": "905550000000", "timestamp": "1791619260", "errors": null, "phone_number_id": "1234567890", "conversation": null, "pricing": null, "biz_opaque_callback_data": "018f3a2b-7c1d-7f4e-9b21-6d0c1e2f3a4b", "user_id": null, "direction": "outbound" } } ``` ## message.status.delivered Mesaj alıcının cihazına teslim edildi. ```json { "event": "message.status.delivered", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb03", "event_type": "message.status.delivered", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "recipient": "905550000000", "timestamp": "1791619260", "errors": null, "phone_number_id": "1234567890", "conversation": null, "pricing": null, "biz_opaque_callback_data": "018f3a2b-7c1d-7f4e-9b21-6d0c1e2f3a4b", "user_id": null, "direction": "outbound" } } ``` ## message.status.read Alıcı mesajı okudu. ```json { "event": "message.status.read", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb04", "event_type": "message.status.read", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "recipient": "905550000000", "timestamp": "1791619260", "errors": null, "phone_number_id": "1234567890", "conversation": null, "pricing": null, "biz_opaque_callback_data": "018f3a2b-7c1d-7f4e-9b21-6d0c1e2f3a4b", "user_id": null, "direction": "outbound" } } ``` ## message.status.failed Gönderim başarısız. `errors` Meta'nın hata listesini aynen taşır (`error_data.details` dahil). Her öğeye `message_tr`, `action_tr`, `severity`, `retryable`, `category` ve `known` (sözlükte var mı) eklenir; ilk hatanın özeti üst düzeyde: `error_code`, `error_message_tr`, `error_action_tr`, `error_severity`, `error_retryable`, `error_category`. ```json { "event": "message.status.failed", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb05", "event_type": "message.status.failed", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "recipient": "905550000000", "timestamp": "1791619260", "errors": [ { "code": 131047, "title": "Re-engagement message", "message": "Re-engagement message", "error_data": { "details": "Message failed to send because more than 24 hours have passed since the customer last replied to this number." }, "href": "https://developers.facebook.com/docs/whatsapp/cloud-api/support/error-codes/", "message_tr": "Kişinin son mesajının üzerinden 24 saatten fazla geçti; serbest mesaj gönderilemez.", "action_tr": "Onaylı bir şablon mesajı gönderin; kişi yanıt verince 24 saat yeniden açılır.", "severity": "info", "retryable": false, "category": "recipient", "known": true } ], "phone_number_id": "1234567890", "conversation": null, "pricing": null, "biz_opaque_callback_data": "018f3a2b-7c1d-7f4e-9b21-6d0c1e2f3a4b", "user_id": null, "direction": "outbound", "error_code": 131047, "error_message_tr": "Kişinin son mesajının üzerinden 24 saatten fazla geçti; serbest mesaj gönderilemez.", "error_action_tr": "Onaylı bir şablon mesajı gönderin; kişi yanıt verince 24 saat yeniden açılır.", "error_severity": "info", "error_retryable": false, "error_category": "recipient" } } ``` ## template.approved Şablonunuz Meta tarafından onaylandı. `rejected_reason`, `rejected_reason_text` (yalnız `REJECTED`), `previous_status`, `quality_score`, `occurred_at` ve `source` (`webhook` ya da `sync`) alanları da gelir. Meta webhook'u gelmediyse geçiş senkronda yakalanır (`source: "sync"`); aynı geçiş iki kez yayınlanmaz. `reason` eski alandır ve korunur. ```json { "event": "template.approved", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb06", "event_type": "template.approved", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "template_id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "template_name": "siparis_onayi", "language": "tr", "category": "UTILITY", "waba_id": "1029384756", "status": "APPROVED", "previous_status": "PENDING", "reason": null, "rejected_reason": null, "rejected_reason_text": null, "quality_score": "UNKNOWN", "occurred_at": "2026-10-10T10:00:00+03:00", "source": "webhook", "reason_code": null, "reason_tr": "Şablon onaylandı.", "action_tr": null, "paused_until": null, "meta_description": null } } ``` ## template.rejected Şablon reddedildi; sebep `reason` (ve aynı değerle `rejected_reason`) alanındadır, Türkçe açıklaması `rejected_reason_text`'tedir. `rejected_reason_text` her redde doludur; ayrıca `reason_code`, `reason_tr`, `action_tr`. ```json { "event": "template.rejected", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb07", "event_type": "template.rejected", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "template_id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "template_name": "siparis_onayi", "language": "tr", "category": "UTILITY", "waba_id": "1029384756", "status": "REJECTED", "previous_status": "PENDING", "reason": "INVALID_FORMAT", "rejected_reason": "INVALID_FORMAT", "rejected_reason_text": "Şablon biçimi geçersiz (değişken/örnek eksik olabilir).", "quality_score": "UNKNOWN", "occurred_at": "2026-10-10T10:00:00+03:00", "source": "webhook", "reason_code": "INVALID_FORMAT", "reason_tr": "Şablon reddedildi.", "action_tr": "Red nedenine göre şablonu düzenleyip yeniden gönderin ya da itiraz edin.", "paused_until": null, "meta_description": null } } ``` ## template.flagged Şablon işaretlendi (kalite düştü). Yük diğer durum olaylarıyla aynıdır. ```json { "event": "template.flagged", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb08", "event_type": "template.flagged", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "template_id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "template_name": "siparis_onayi", "language": "tr", "category": "UTILITY", "waba_id": "1029384756", "status": "FLAGGED", "previous_status": "PENDING", "reason": null, "rejected_reason": null, "rejected_reason_text": null, "quality_score": "UNKNOWN", "occurred_at": "2026-10-10T10:00:00+03:00", "source": "webhook", "reason_code": null, "reason_tr": null, "action_tr": null, "paused_until": null, "meta_description": null } } ``` ## template.paused Şablon geçici olarak durduruldu. Yük diğer durum olaylarıyla aynıdır. `reason_code` (Meta `other_info.title`: `FIRST_PAUSE`, `SECOND_PAUSE`, `RATE_LIMITING_PAUSE`, `UNPAUSE`, `DISABLED`), `reason_tr`, `action_tr`, `paused_until` (ilk duraklatmada 3, ikincide 6 saat sonrası; bilinmiyorsa `null`) ve `meta_description` gelir. ```json { "event": "template.paused", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb09", "event_type": "template.paused", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "template_id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "template_name": "siparis_onayi", "language": "tr", "category": "UTILITY", "waba_id": "1029384756", "status": "PAUSED", "previous_status": "PENDING", "reason": null, "rejected_reason": null, "rejected_reason_text": null, "quality_score": "UNKNOWN", "occurred_at": "2026-10-10T10:00:00+03:00", "source": "webhook", "reason_code": "FIRST_PAUSE", "reason_tr": "Şablon düşük kalite nedeniyle ilk kez duraklatıldı.", "action_tr": "İçeriği ve gönderim kitlesini gözden geçirin.", "paused_until": "2026-10-10T10:00:00Z", "meta_description": "Your template has been paused for 3 hours." } } ``` ## quality.changed Numaranızın kalite puanı değişti. ```json { "event": "quality.changed", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb0a", "event_type": "quality.changed", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "phone": "+90 555 000 00 00", "quality": "YELLOW" } } ``` ## account.alert Meta'dan hesap düzeyinde uyarı geldi. Gövde yapılandırılmıştır: `type` (`payment_method`, `business_verification`, `policy_violation`, `restriction`, `account_disabled`, `disconnected`, `reconnected`, `messaging_limit`, `other`), `severity`, `message_tr`, `action_tr`, `link`, `waba_id`, `alert_type`, `alert_status`, `meta_description`, `occurred_at`. `type` Meta'nın olay adından ve `alert_type` değerinden türetilir; bilinmeyen değerleri tolere edin. ```json { "event": "account.alert", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb0b", "event_type": "account.alert", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "field": "account_update", "event": "DISABLED_UPDATE", "type": "account_disabled", "severity": null, "message_tr": "WhatsApp işletme hesabı Meta tarafından kapatıldı ya da kapatılmak üzere.", "action_tr": "Meta Business Suite'teki bildirimi inceleyin ve itiraz edin; destek için bize yazın.", "link": "https://business.facebook.com/", "waba_id": "1029384756", "alert_type": null, "alert_status": null, "meta_description": null, "occurred_at": "2026-10-10T10:00:00+03:00" } } ``` ## message.revoked Müşteri ya da personel bir mesajı sildi. **`*` aboneliğine dahil değildir.** ```json { "event": "message.revoked", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb0c", "event_type": "message.revoked", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhgSUkVWT0tF", "original_message_id": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhggQTNFQkQ2RjQ3MEIyQzk0RTE4AA==", "direction": "inbound", "revoked_by": "customer", "from": "905550000000", "user_id": null, "phone_number_id": "1234567890", "timestamp": "1791619400" } } ``` ## message.edited Müşteri ya da personel bir mesajı düzenledi. **`*` aboneliğine dahil değildir.** ```json { "event": "message.edited", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb0d", "event_type": "message.edited", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhgSRURJVA==", "original_message_id": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhggQTNFQkQ2RjQ3MEIyQzk0RTE4AA==", "direction": "inbound", "edited_by": "customer", "from": "905550000000", "user_id": null, "phone_number_id": "1234567890", "timestamp": "1791619420", "type": "text", "text": "Merhaba, siparişim nerede?" } } ``` ## message.sent API dışından giden mesaj (panel, otomasyon, kampanya, abonelikten çıkış yanıtı). `source` kaynağı belirtir. **`*` aboneliğine dahil değildir.** ```json { "event": "message.sent", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb0e", "event_type": "message.sent", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "source": "campaign", "from": "905550000001", "to": "905550000000", "user_id": null, "type": "template", "text": null, "media": null, "template_name": "ekim_kampanyasi", "campaign_id": 88, "phone_number_id": "1234567890", "timestamp": "1791619500" } } ``` ## message.history Coexistence geçmiş aktarımından gelen eski bir mesaj. Parçalar sırasız gelebilir; `chunk_order` ile sıralayın. **`*` aboneliğine dahil değildir.** ```json { "event": "message.history", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb0f", "event_type": "message.history", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhgSSElTVA==", "history": true, "direction": "inbound", "from": "905550000000", "to": null, "thread_id": "905550000000", "type": "text", "text": "Eski mesaj", "media": null, "status": "READ", "timestamp": "1781000000", "phase": 1, "chunk_order": 3, "progress": 45, "phone_number_id": "1234567890" } } ``` ## number.status_changed Numara bağlandı, koptu, işaretlendi ya da kısıtlandı. `event` için bilinmeyen değerleri tolere edin. **`*` aboneliğine dahil değildir.** Kopma olayları: `PARTNER_REMOVED`, `PARTNER_APP_UNINSTALLED` (2.30.0), `ACCOUNT_DELETED`, `ACCOUNT_OFFBOARDED` → `status: "disconnected"`; Meta ikisini art arda gönderse de numara başına tek olay yayınlanır. Yetki kaybında `event: "REAUTH_REQUIRED"` (numara `status` değeri korunur, `GET /numbers` → `reauth_required: true`). 2.31.0: `disconnect_message_tr`, `disconnect_action_tr`, `reconnect_url` (sözlük: `GET /meta/disconnect-reasons`). `ACCOUNT_RESTRICTION` → `status: "restricted"` + `restriction_type`, `restriction_types`, `restricted_until` (ISO 8601), `remediation`. `DISABLED_UPDATE` → `reason` = `ban_info.waba_ban_state` (`DISABLE`, `SCHEDULE_FOR_DISABLE`, `REINSTATE` → `status: "connected"`) ve `ban_date`. Yeniden bağlanınca `event: "CONNECTED_VIA_PANEL"`. 2.31.1: Meta kopma olaylarını ters sırayla gönderebilir (önce nedensiz `PARTNER_APP_UNINSTALLED`, sonra nedenli `PARTNER_REMOVED`). Kopmadan sonraki 10 dakika içinde aynı WABA için nedenli bir olay gelirse `disconnect_reason` / `disconnect_initiated_by` güncellenir ve nedenli **tek** bir `number.status_changed` daha gönderilir; aynı neden tekrar gönderilmez. Aynı numara için ikinci kopma olayını önceki olayın güncellemesi olarak işleyin. `ACCOUNT_VIOLATION` olayı `status: "restricted"` ve `violation_type` ile yayınlanır. ```json { "event": "number.status_changed", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb10", "event_type": "number.status_changed", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "phone_number_id": "1234567890", "display_phone_number": "+90 555 000 00 00", "status": "disconnected", "event": "PARTNER_REMOVED", "reason": "PRIMARY_INACTIVITY", "initiated_by": "SYSTEM", "occurred_at": "2026-10-10T10:00:00+03:00", "disconnect_message_tr": "Telefondaki WhatsApp Business uygulaması uzun süre açılmadığı için Meta bağlantıyı kaldırdı.", "disconnect_action_tr": "Telefonda WhatsApp Business uygulamasını açın; ardından Veri Merkezi panelinde WhatsApp › Ayarlar › Numaralar sayfasından numarayı yeniden bağlayın.", "reconnect_url": "https://verimerkezi.app/panel/wa/ayarlar?bolum=numaralar" } } ``` ## credit.low Mesaj kredisi eşiğin altına düştü. Aynı durum için 24 saatte bir gönderilir. **`*` aboneliğine dahil değildir.** ```json { "event": "credit.low", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb11", "event_type": "credit.low", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "balance": 42, "threshold": 100, "unit": "messages", "occurred_at": "2026-10-10T10:30:00+03:00" } } ``` ## credit.exhausted Mesaj kredisi tükendi; gönderim engellenir. **`*` aboneliğine dahil değildir.** ```json { "event": "credit.exhausted", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb12", "event_type": "credit.exhausted", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "balance": 0, "unit": "messages", "occurred_at": "2026-10-10T11:00:00+03:00" } } ``` ## privacy.erasure_completed KVKK silme talebi (`POST /privacy/erasure`) tamamlandı. **`*` aboneliğine dahil değildir.** Talebi `erasure_id` ile eşleştirin; istekte `reference` verdiyseniz olayda da aynen döner (2.29.0, verilmediyse `null`). Olay, 202 yanıtı istemciye ulaşmadan önce yayınlanabilir. ```json { "event": "privacy.erasure_completed", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb13", "event_type": "privacy.erasure_completed", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "erasure_id": "ers_0123456789abcdef01234567", "phone_number_id": "1234567890", "wa_id": "905550000000", "user_id": null, "reference": "crm-silme-4711", "completed_at": "2026-10-10T09:00:00Z", "erased": { "messages": 42, "media": 3 } } } ``` ## contact.opt_out_changed Kişinin abonelik (opted_out) durumu değişti. **`*` aboneliğine dahil değildir.** ```json { "event": "contact.opt_out_changed", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb14", "event_type": "contact.opt_out_changed", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "contact_id": 26470, "phone": "905550000001", "opted_out": true, "previous_opted_out": false, "source": "whatsapp_ret", "phone_number_id": "1234567890", "changed_at": "2026-10-05T16:40:00+03:00" } } ``` ## template.category_changed Meta şablonun kategorisini değiştirdi; şablonun `category` alanı kendiliğinden güncellenir. **`*` aboneliğine dahil değildir.** ```json { "event": "template.category_changed", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb15", "event_type": "template.category_changed", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "template_id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "language": "tr", "waba_id": "1029384756", "previous_category": "UTILITY", "new_category": "MARKETING", "correct_category": null, "occurred_at": "2026-10-10T10:00:00+03:00" } } ``` ## template.quality_changed Şablon kalite puanı gerçekten değişti. **`*` aboneliğine dahil değildir.** ```json { "event": "template.quality_changed", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb16", "event_type": "template.quality_changed", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "template_id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "language": "tr", "category": "UTILITY", "waba_id": "1029384756", "previous_quality_score": "GREEN", "new_quality_score": "YELLOW", "occurred_at": "2026-10-10T10:00:00+03:00" } } ``` ## template.disabled Şablon Meta tarafından devre dışı bırakıldı (`status: "DISABLED"`; devre dışı şablon silinemez, gönderimde kullanılamaz). Yük `template.approved` ile aynı alanları taşır. **`*` aboneliğine dahil değildir.** ```json { "event": "template.disabled", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb40", "event_type": "template.disabled", "occurred_at": "2026-10-14T10:00:00+03:00", "created_at": "2026-10-14 10:00:00", "data": { "template_id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "template_name": "siparis_onayi", "language": "tr", "category": "UTILITY", "waba_id": "1029384756", "status": "DISABLED", "previous_status": "PAUSED", "reason": null, "rejected_reason": null, "rejected_reason_text": null, "quality_score": "UNKNOWN", "occurred_at": "2026-10-14T10:00:00+03:00", "source": "webhook", "reason_code": null, "reason_tr": "Şablon kullanıcı geri bildirimleri nedeniyle kapatıldı.", "action_tr": "Yeni bir şablon oluşturun ya da itiraz edin.", "paused_until": null, "meta_description": null } } ``` ## template.reinstated Duraklatma ya da işaret kalktı; şablon yeniden kullanılabilir (`status: "APPROVED"`). Yük `template.approved` ile aynı alanları taşır. **`*` aboneliğine dahil değildir.** ```json { "event": "template.reinstated", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb41", "event_type": "template.reinstated", "occurred_at": "2026-10-14T10:00:00+03:00", "created_at": "2026-10-14 10:00:00", "data": { "template_id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "template_name": "siparis_onayi", "language": "tr", "category": "UTILITY", "waba_id": "1029384756", "status": "APPROVED", "previous_status": "PAUSED", "reason": null, "rejected_reason": null, "rejected_reason_text": null, "quality_score": "UNKNOWN", "occurred_at": "2026-10-14T10:00:00+03:00", "source": "webhook", "reason_code": null, "reason_tr": null, "action_tr": null, "paused_until": null, "meta_description": null } } ``` ## number.name_changed Numaranın görünen adı için Meta kararı geldi. **`*` aboneliğine dahil değildir.** Redde `rejection_reason_tr` (Türkçe açıklama; Meta ad reddi için kod listesi yayınlamaz, ham neden `rejection_reason`da kalır). ```json { "event": "number.name_changed", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb17", "event_type": "number.name_changed", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "phone_number_id": "1234567890", "display_phone_number": "+90 555 000 00 00", "decision": "APPROVED", "requested_name": "Firma A.Ş.", "rejection_reason": null, "rejection_reason_tr": null, "occurred_at": "2026-10-10T10:00:00+03:00" } } ``` ## call.connect Gelen arama çalıyor (`sdp_offer` dolu) ya da başlattığınız aramada Meta yanıt verdi (`sdp_answer` dolu). SDP ilk teslimden sonra kayıtlarda maskelenir. **`*` aboneliğine dahil değildir.** SDP içeren teslimatlar 1 saatten eskiyse kuyrukta maskelenir (`[sdp N bytes]`); yeniden teslimde (`redeliver`) SDP gelmeyebilir. ```json { "event": "call.connect", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb18", "event_type": "call.connect", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "call_id": "wacid.HBgLOTA1NTUxMjM0NTY3FQIAEhggQkE1", "phone_number_id": "1234567890", "from": "905550000000", "to": "905550000001", "direction": "user_initiated", "name": "Ayşe Yılmaz", "sdp_offer": "v=0\r\no=- 4611731400430051336 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n...", "sdp_answer": null, "timestamp": "1791619600", "biz_opaque_callback_data": null } } ``` ## call.status Arama durumu: `ringing`, `accepted`, `rejected`, `missed`, `failed`. **`*` aboneliğine dahil değildir.** ```json { "event": "call.status", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb19", "event_type": "call.status", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "call_id": "wacid.HBgLOTA1NTUxMjM0NTY3FQIAEhggQkE2", "phone_number_id": "1234567890", "status": "ringing", "recipient": "905550000000", "timestamp": "1791619610", "biz_opaque_callback_data": "crm-ticket-42" } } ``` ## call.terminate Arama bitti. **`*` aboneliğine dahil değildir.** ```json { "event": "call.terminate", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb1a", "event_type": "call.terminate", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "call_id": "wacid.HBgLOTA1NTUxMjM0NTY3FQIAEhggQkE1", "phone_number_id": "1234567890", "direction": "user_initiated", "from": "905550000000", "to": "905550000001", "status": "completed", "duration_sec": 74, "start_time": "2026-10-10T14:00:05+03:00", "end_time": "2026-10-10T14:01:19+03:00", "billable_pulses": null, "errors": null, "timestamp": "1791619679", "biz_opaque_callback_data": null } } ``` ## call.permission_reply Müşteri arama izni isteğinizi yanıtladı. **`*` aboneliğine dahil değildir.** ```json { "event": "call.permission_reply", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb1b", "event_type": "call.permission_reply", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "phone_number_id": "1234567890", "from": "905550000000", "user_id": null, "granted": true, "response": "accept", "permanent": false, "expires_at": "2026-10-17T10:00:00+03:00", "response_source": "user_action", "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhggQTNFQkQ2RjQ3MEIyQzk0RTE4AA==", "timestamp": "1791619700" } } ``` ## flow.completed Müşteri formu gönderdi. Formdaki dosyalar sunucumuzda indirilir ve `GET /media/{media_id}` ile alınır. Aynı mesaj ayrıca `message.received` (`type: interactive`) olarak gelir (`data.interactive.type: nfm_reply`, `name: flow`). **`*` aboneliğine dahil değildir.** ```json { "event": "flow.completed", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb1c", "event_type": "flow.completed", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "event_id": "flow_7f3c2a91", "phone_number_id": "1234567890", "from": "905550000000", "user_id": null, "contact_name": "Ahmet", "message_id": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAEhggQTNFQkQ2RjQ3MEIyQzk0RTE4AA==", "context_message_id": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "flow_token": "crm-basvuru-42", "flow_id": "1234567890123456", "response": { "flow_token": "crm-basvuru-42", "ad": "Ahmet", "brans": "trafik" }, "response_raw": null, "media": [], "timestamp": "1791619800" } } ``` ## flow.status_changed Form durumu değişti (yayınlandı, kısıtlandı, engellendi, kaldırıldı). **`*` aboneliğine dahil değildir.** ```json { "event": "flow.status_changed", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb1d", "event_type": "flow.status_changed", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "flow_id": "1234567890123456", "waba_id": "1029384756", "old_status": "PUBLISHED", "new_status": "THROTTLED", "reason": "Flow Webhook Status Change", "occurred_at": "2026-10-10T12:00:00+03:00" } } ``` ## number.detached Numara hesabınızdan ayrıldı (`POST /numbers/{id}/detach`). **`*` aboneliğine dahil değildir.** ```json { "event": "number.detached", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb1e", "event_type": "number.detached", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "phone_number_id": "1234567890", "display_phone_number": "+90 555 000 00 00", "reason": "Numara başka sisteme taşındı", "detached_at": "2026-10-08T14:05:00+03:00", "cancelled_outbound": 3 } } ``` ## number.purged Ayrılan numaranın zamanlanmış kayıt silmesi tamamlandı (`purge_messages: true`). **`*` aboneliğine dahil değildir.** `counts.app_contacts`: silinen uygulama rehberi kaydı. ```json { "event": "number.purged", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb1f", "event_type": "number.purged", "occurred_at": "2026-10-10T10:00:00+03:00", "created_at": "2026-10-10 10:00:00", "data": { "phone_number_id": "1234567890", "purged_at": "2026-10-09T13:00:00+03:00", "counts": { "messages": 1520, "media_files": 84, "media_rows": 84, "conversations": 210, "calls": 6, "flow_sends": 12, "webhook_log": 3000, "api_log": 900, "campaign_recipients": 400, "webhook_deliveries": 1600, "deferred_jobs": 2, "app_contacts": 842 } } } ``` ## contact.synced Coexistence numarasının telefondaki WhatsApp Business uygulaması rehberinde kişi eklendi, düzenlendi ya da silindi. Numara başına toplu gelir; bir olayda en fazla 100 değişiklik bulunur, fazlası `batch.index` / `batch.count` ile bölünür. `sync_type`: `initial` (numara bağlanınca ilk aktarım) ya da `incremental`. `changes[].action`: `add`, `update`, `remove`; `remove`'da ad alanları `null`dır. `changes[].id`, `GET /app-contacts` kaydının kimliğidir. Kaçan olaylar `GET /app-contacts?updated_since=…&include_removed=true` ile telafi edilir. Kapsam: `contacts:read`. **`*` aboneliğine dahil değildir.** ```json { "event": "contact.synced", "event_id": "0192f3a1-0000-7000-8000-0000000000aa", "event_type": "contact.synced", "occurred_at": "2026-10-13T10:15:00+03:00", "created_at": "2026-10-13 10:15:00", "data": { "phone_number_id": "1234567890", "sync_type": "incremental", "batch": { "index": 1, "count": 1 }, "changes": [ { "action": "add", "id": 501, "phone": "+905551112233", "wa_id": "905551112233", "user_id": "TR.1A2B3C4D5E6F7G8H", "full_name": "Ayşe Yılmaz", "first_name": "Ayşe", "changed_at": "2026-10-13T10:15:00+03:00" }, { "action": "update", "id": 502, "phone": "+905552223344", "wa_id": "905552223344", "user_id": null, "full_name": "Mehmet Kaya (İş)", "first_name": "Mehmet", "changed_at": "2026-10-13T10:15:00+03:00" }, { "action": "remove", "id": 503, "phone": "+905553334455", "wa_id": "905553334455", "user_id": null, "full_name": null, "first_name": null, "changed_at": "2026-10-13T10:15:00+03:00" } ] } } ``` ## template.pending Şablon incelemeye girdi. **`*` aboneliğine dahil değildir.** ```json { "event": "template.pending", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb50", "event_type": "template.pending", "occurred_at": "2026-10-22T10:00:00+03:00", "created_at": "2026-10-22 10:00:00", "data": { "template_id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "template_name": "siparis_onayi", "language": "tr", "category": "UTILITY", "waba_id": "1029384756", "status": "PENDING", "previous_status": null, "reason": null, "rejected_reason": null, "rejected_reason_text": null, "quality_score": "UNKNOWN", "occurred_at": "2026-10-14T10:00:00+03:00", "source": "webhook", "reason_code": null, "reason_tr": "Şablon incelemede.", "action_tr": null, "paused_until": null, "meta_description": null } } ``` ## template.in_appeal Şablon için itiraz süreci başladı. **`*` aboneliğine dahil değildir.** ```json { "event": "template.in_appeal", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb51", "event_type": "template.in_appeal", "occurred_at": "2026-10-22T10:00:00+03:00", "created_at": "2026-10-22 10:00:00", "data": { "template_id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "template_name": "siparis_onayi", "language": "tr", "category": "UTILITY", "waba_id": "1029384756", "status": "IN_APPEAL", "previous_status": "REJECTED", "reason": null, "rejected_reason": null, "rejected_reason_text": null, "quality_score": "UNKNOWN", "occurred_at": "2026-10-14T10:00:00+03:00", "source": "webhook", "reason_code": null, "reason_tr": "Şablon itiraz sürecinde.", "action_tr": "İtiraz sonucunu bekleyin.", "paused_until": null, "meta_description": null } } ``` ## template.locked Şablon kilitlendi; düzenlenemez. **`*` aboneliğine dahil değildir.** ```json { "event": "template.locked", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb52", "event_type": "template.locked", "occurred_at": "2026-10-22T10:00:00+03:00", "created_at": "2026-10-22 10:00:00", "data": { "template_id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "template_name": "siparis_onayi", "language": "tr", "category": "UTILITY", "waba_id": "1029384756", "status": "LOCKED", "previous_status": "APPROVED", "reason": null, "rejected_reason": null, "rejected_reason_text": null, "quality_score": "UNKNOWN", "occurred_at": "2026-10-14T10:00:00+03:00", "source": "webhook", "reason_code": null, "reason_tr": "Şablon kilitlendi; düzenlenemez.", "action_tr": null, "paused_until": null, "meta_description": null } } ``` ## template.archived Şablon kullanılmadığı için Meta tarafından arşivlendi. **`*` aboneliğine dahil değildir.** ```json { "event": "template.archived", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb53", "event_type": "template.archived", "occurred_at": "2026-10-22T10:00:00+03:00", "created_at": "2026-10-22 10:00:00", "data": { "template_id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "template_name": "siparis_onayi", "language": "tr", "category": "UTILITY", "waba_id": "1029384756", "status": "ARCHIVED", "previous_status": "APPROVED", "reason": null, "rejected_reason": null, "rejected_reason_text": null, "quality_score": "UNKNOWN", "occurred_at": "2026-10-14T10:00:00+03:00", "source": "webhook", "reason_code": null, "reason_tr": "Şablon kullanılmadığı için arşivlendi; 28 gün içinde arşivden çıkarılmazsa silinir.", "action_tr": "Kullanmaya devam edecekseniz şablonu arşivden çıkarın.", "paused_until": null, "meta_description": null } } ``` ## template.unarchived Şablon arşivden çıkarıldı. **`*` aboneliğine dahil değildir.** ```json { "event": "template.unarchived", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb54", "event_type": "template.unarchived", "occurred_at": "2026-10-22T10:00:00+03:00", "created_at": "2026-10-22 10:00:00", "data": { "template_id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "template_name": "siparis_onayi", "language": "tr", "category": "UTILITY", "waba_id": "1029384756", "status": "UNARCHIVED", "previous_status": "ARCHIVED", "reason": null, "rejected_reason": null, "rejected_reason_text": null, "quality_score": "UNKNOWN", "occurred_at": "2026-10-14T10:00:00+03:00", "source": "webhook", "reason_code": null, "reason_tr": "Şablon arşivden çıkarıldı.", "action_tr": null, "paused_until": null, "meta_description": null } } ``` ## template.deleted Şablon silindi. **`*` aboneliğine dahil değildir.** ```json { "event": "template.deleted", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb55", "event_type": "template.deleted", "occurred_at": "2026-10-22T10:00:00+03:00", "created_at": "2026-10-22 10:00:00", "data": { "template_id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "template_name": "siparis_onayi", "language": "tr", "category": "UTILITY", "waba_id": "1029384756", "status": "DELETED", "previous_status": "APPROVED", "reason": null, "rejected_reason": null, "rejected_reason_text": null, "quality_score": "UNKNOWN", "occurred_at": "2026-10-14T10:00:00+03:00", "source": "webhook", "reason_code": null, "reason_tr": "Şablon silindi.", "action_tr": null, "paused_until": null, "meta_description": null } } ``` ## template.pending_deletion Şablon silinmek üzere. **`*` aboneliğine dahil değildir.** ```json { "event": "template.pending_deletion", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb56", "event_type": "template.pending_deletion", "occurred_at": "2026-10-22T10:00:00+03:00", "created_at": "2026-10-22 10:00:00", "data": { "template_id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "template_name": "siparis_onayi", "language": "tr", "category": "UTILITY", "waba_id": "1029384756", "status": "PENDING_DELETION", "previous_status": "APPROVED", "reason": null, "rejected_reason": null, "rejected_reason_text": null, "quality_score": "UNKNOWN", "occurred_at": "2026-10-14T10:00:00+03:00", "source": "webhook", "reason_code": null, "reason_tr": "Şablon silinmek üzere.", "action_tr": null, "paused_until": null, "meta_description": null } } ``` ## template.limit_exceeded Hesap şablon sayısı sınırına ulaştı. **`*` aboneliğine dahil değildir.** ```json { "event": "template.limit_exceeded", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb57", "event_type": "template.limit_exceeded", "occurred_at": "2026-10-22T10:00:00+03:00", "created_at": "2026-10-22 10:00:00", "data": { "template_id": 512, "meta_template_id": "1249033812345678", "name": "siparis_onayi", "template_name": "siparis_onayi", "language": "tr", "category": "UTILITY", "waba_id": "1029384756", "status": "LIMIT_EXCEEDED", "previous_status": null, "reason": null, "rejected_reason": null, "rejected_reason_text": null, "quality_score": "UNKNOWN", "occurred_at": "2026-10-14T10:00:00+03:00", "source": "webhook", "reason_code": null, "reason_tr": "Hesap şablon sınırına ulaştı.", "action_tr": "Kullanılmayan şablonları silin.", "paused_until": null, "meta_description": null } } ``` ## message.status.deleted Meta mesajın silindiğini bildirdi. Yük diğer durum olaylarıyla aynıdır. **`*` aboneliğine dahil değildir.** ```json { "event": "message.status.deleted", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb58", "event_type": "message.status.deleted", "occurred_at": "2026-10-22T10:00:00+03:00", "created_at": "2026-10-22 10:00:00", "data": { "wamid": "wamid.HBgMOTA1NTUxMjM0NTY3FQIAERgSMUI4QzY3RDFFNzBBNDlGRTlBAA==", "recipient": "905550000000", "timestamp": "1791619260", "errors": null, "phone_number_id": "1234567890", "conversation": null, "pricing": null, "biz_opaque_callback_data": null, "user_id": null, "direction": "outbound" } } ``` ## number.limit_changed Numaranın Meta mesaj limiti (24 saatte başlatılabilecek kişi sayısı) değişti. Yalnız limit gerçekten değişince gelir. `current_limit` / `old_limit`: `TIER_50`, `TIER_250`, `TIER_1K`, `TIER_2K`, `TIER_10K`, `TIER_100K`, `TIER_UNLIMITED`, `TIER_NOT_SET`; bilinmeyen değerleri tolere edin. **`*` aboneliğine dahil değildir.** ```json { "event": "number.limit_changed", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb59", "event_type": "number.limit_changed", "occurred_at": "2026-10-22T10:00:00+03:00", "created_at": "2026-10-22 10:00:00", "data": { "phone_number_id": "1234567890", "display_phone_number": "+90 555 000 00 00", "event": "UPGRADE", "current_limit": "TIER_10K", "old_limit": "TIER_2K", "limit_tr": "24 saatte 10.000 kişi", "occurred_at": "2026-10-22T10:00:00+03:00" } } ``` ## account.review_changed WhatsApp işletme hesabı incelemesi sonuçlandı (`decision`: `APPROVED`, `REJECTED`, `PENDING`, `DEFERRED`). **`*` aboneliğine dahil değildir.** ```json { "event": "account.review_changed", "event_id": "01a0c7ea-0d80-7d4d-aaf0-a2cc64a6bb60", "event_type": "account.review_changed", "occurred_at": "2026-10-22T10:00:00+03:00", "created_at": "2026-10-22 10:00:00", "data": { "waba_id": "1029384756", "decision": "APPROVED", "message_tr": "WhatsApp işletme hesabı incelemesi onaylandı.", "action_tr": null, "severity": "info", "occurred_at": "2026-10-22T10:00:00+03:00" } } ``` --- # Hata kodları - `invalid_media_id` (400): `media_id` yalnız rakamlardan oluşmalı. Çözüm: Webhook'taki `data.media.media_id` değerini aynen kullanın. - `invalid_phone_number_id` (400): `phone_number_id` rakam değil (yol parametresi). Arama ve form uçlarında 422 döner. Çözüm: `GET /numbers` yanıtındaki `phone_number_id` değerini kullanın. - `invalid_template_id` (400): Şablon kimliği geçersiz. Çözüm: `GET /templates` yanıtındaki `id` değerini kullanın. - `invalid_api_key` (401): Anahtar geçersiz, süresi dolmuş, iptal edilmiş ya da IP izin listesi dışından çağrılmış. Çözüm: Anahtarı ve IP kısıtını panelden kontrol edin; gerekirse yeni anahtar oluşturun. - `unauthorized` (401): `Authorization` başlığı yok. Çözüm: `Authorization: Bearer vmk_live_...` başlığını ekleyin. - `insufficient_credits` (402): Mesaj kredisi yetersiz (yalnız canlı anahtar). `error.balance` döner. Çözüm: Kredi paketi alın; `credit.low` olayına abone olun. - `call_permission_required` (403): Müşterinin arama izni yok. Çözüm: Önce `POST /calls/permission-request` ile izin isteyin. - `insufficient_scope` (403): Anahtarda gerekli kapsam yok. `error.required_scope` ya da `error.required_scopes` döner. Çözüm: Panelden anahtara kapsam ekleyin ya da uygun kapsamlı yeni anahtar oluşturun. - `test_mode_unsupported` (403): İşlem test anahtarıyla yapılamaz (numara ayırma, form yazma uçları). Çözüm: `vmk_live_` anahtarı kullanın. - `test_only` (403): Uç yalnız test anahtarıyla çalışır (`POST /test/inbound`). Çözüm: `vmk_test_` anahtarı kullanın. - `call_not_found` (404): Arama yok, size ait değil ya da test/canlı anahtar uyuşmuyor. Çözüm: Aramayı başlatan anahtar türünü kullanın. - `endpoint_moved` (404): API yanlış adresten çağrıldı. Çözüm: Taban adres olarak `https://api.verimerkezi.app/wa` kullanın. - `flow_not_found` (404): Form yok ya da size ait değil. Çözüm: Meta arayüzünde oluşturduğunuz formlar için `phone_number_id` ekleyin. - `media_file_missing` (404): Kayıt var, dosya depoda yok. Çözüm: Destek ekibine `X-Request-Id` ile bildirin. - `media_not_found` (404): Medya kaydı yok ya da başka hesaba ait. Çözüm: Kendi hesabınızın `media_id` değerini kullanın. - `not_found` (404): Arama uçlarında geçersiz eylem adı. Çözüm: Yalnız `pre_accept`, `accept`, `reject`, `terminate` kullanın. - `number_not_found` (404): Numara size ait değil ya da aktif değil. Çözüm: `GET /numbers` ile numaranın durumunu kontrol edin. - `template_not_found` (404): Şablon bulunamadı. Çözüm: Şablon kimliğini `GET /templates` ile doğrulayın. - `webhook_not_found` (404): Abonelik yok ya da size ait değil. Çözüm: `GET /webhooks` ile kimliği doğrulayın. - `action_in_progress` (409): Aynı arama için başka bir eylem hâlâ işleniyor. Çözüm: `retry_after` (2 sn) kadar bekleyip tekrar deneyin. - `call_action_conflict` (409): Aynı eylem farklı parametreyle uygulanmış. Çözüm: İlk eylemin sonucunu kullanın. - `call_ended` (409): Arama sona ermiş. Çözüm: Yeni aramayı bekleyin ya da başlatın. - `calling_not_enabled` (409): Numarada arama kapalı. Çözüm: `PATCH /numbers/{id}/calling` ile `enabled: true` yapın. - `contact_exists` (409): Telefon zaten rehberde kayıtlı. `error.id` mevcut kişiyi verir. Çözüm: Mevcut kişiyi kullanın ya da toplu eklemede `skip_duplicates: true` gönderin. - `duplicate_call` (409): Bu kişiyle zaten süren bir arama var (Meta 138003). Çözüm: Mevcut aramanın durumunu `GET /calls` ile izleyin. - `flow_not_draft` (409): İşlem yalnız DRAFT formda yapılır. Çözüm: Yayınlanmış form için yeni form oluşturun. - `history_completed` (409): Geçmiş aktarımı daha önce tamamlandı. Çözüm: Yeniden başlatmanız gerekmez. - `history_declined` (409): Müşteri geçmiş aktarımını reddetti. Çözüm: Aktarım bu numara için yeniden başlatılamaz. - `history_in_progress` (409): Geçmiş aktarımı zaten başlatılmış (benzer biçimde `history_requested`, `history_completed`, `history_declined`). Çözüm: `GET /numbers/{id}/history-import` ile durumu izleyin. - `history_requested` (409): Geçmiş aktarımı zaten istendi; müşterinin onayı bekleniyor. Çözüm: `GET /numbers/{phone_number_id}/history-import` ile durumu izleyin. - `history_window_expired` (409): Geçmiş aktarımının 24 saatlik penceresi kapandı. Çözüm: Geçmiş için numarayı kaldırıp yeniden bağlayın. - `idempotency_in_progress` (409): Aynı `Idempotency-Key` ile bir istek hâlâ işleniyor. Çözüm: `Retry-After` kadar bekleyip aynı anahtarla tekrar deneyin. - `invalid_action_for_direction` (409): Başlattığınız aramada yalnız `terminate` kullanılabilir. Çözüm: `POST /calls/{id}/terminate` kullanın. - `meta_outcome_unknown` (409): İstek Meta'ya iletildi ama sonucu doğrulanamadı (`retryable: false`). Çözüm: Aynı anahtarla tekrar göndermeyin; durum olayını bekleyin ya da `GET /messages` ile kontrol edin. - `number_detached` (409): Numara hesabınızdan ayrılmış. Hata nesnesinde `phone_number_id` ve `detached_at` de döner. Çözüm: Numarayı panelden yeniden bağlayın. - `waba_missing` (409): Numaranın işletme hesabı (WABA) bilgisi eksik; form işlemi yapılamaz. Çözüm: Numarayı panelden yeniden bağlayın ya da destekle iletişime geçin. - `webhook_url_exists` (409): Bu adrese zaten aktif bir abonelik var. `existing_id` döner. Çözüm: Mevcut aboneliği `PATCH` ile güncelleyin. - `template_exists` (409): Aynı WABA'da aynı ad ve dilde şablon zaten var (`field: name`, `template_id`). Çözüm: Mevcut şablonu düzenleyin (`POST /templates/{id}`) ya da farklı ad / dil kullanın. - `template_not_editable` (409): Şablon düzenlenemez: durum `APPROVED` / `REJECTED` / `PAUSED` dışında ya da Meta'ya hiç gönderilmemiş (`status` döner). Çözüm: Durum uygun olunca düzenleyin; `GET /templates/{id}` yanıtındaki `edit_limit.editable` alanına bakın. - `media_too_large` (413): Dosya türün Meta sınırını aşıyor (`max_bytes` döner). Çözüm: Dosyayı küçültün; sınırlar için Medya rehberine bakın. - `media_invalid_format` (415): Desteklenmeyen dosya türü (içerik doğrulanır). Çözüm: Desteklenen türlerden birine dönüştürün. - `unsupported_media_type` (415): Yükleme isteği `multipart/form-data` değil. Çözüm: `-F file=@...` biçiminde gönderin. - `range_not_satisfiable` (416): İstenen bayt aralığı geçersiz. Çözüm: `Range` başlığını dosya boyutuna göre düzeltin. - `calling_disabled_quality` (422): Meta kalite nedeniyle aramayı geçici kapattı (Meta 138014, `retryable: true`). Çözüm: Bir süre sonra tekrar deneyin. - `calling_messaging_limit_too_low` (422): Günlük mesaj limiti 2.000'in altında (Meta 138015). Çözüm: Mesajlaşma limitinizi yükseltin. - `calling_not_available_in_country` (422): Alıcının ülkesinde işletme araması yok. Çözüm: Bu alıcıyı arayamazsınız. - `calling_not_supported_for_number` (422): Numara coexistence ya da Cloud API dışı; arama açılamaz (Meta 141000). Çözüm: Aramayı ayrı bir API numarasında açın. - `calling_outbound_unavailable` (422): İşletme başlatmalı arama bu numara/ülkede yok (Meta 138013). Çözüm: Gelen aramalarla çalışın. - `calling_prerequisites_unmet` (422): Numaranın teknik önkoşulları karşılanmıyor (Meta 138018). Çözüm: Meta Business Suite'te numara ayarlarını kontrol edin. - `coexistence_readonly_profile` (422): Coexistence numarasında profil API ile güncellenemez. Çözüm: Profili telefondaki WhatsApp Business uygulamasından güncelleyin. - `deregister_not_supported` (422): Meta'dan numara kaldırma bu sürümde yok. Çözüm: `deregister_meta` göndermeyin. - `header_media_fetch_failed` (422): Şablon başlık medyası indirilemedi (`header_media_url`; `POST /templates/media` için `url` / `media_id`). Çözüm: `header_media_url` adresinin herkese açık olduğundan emin olun. - `history_failed` (422): Geçmiş aktarımı başarısız oldu. Çözüm: Destekle iletişime geçin. - `history_not_applicable` (422): Numara coexistence değil; geçmiş aktarımı yok. Çözüm: Yalnız coexistence numaralarda kullanın. - `history_not_found` (422): Bu numara için geçmiş aktarımı kaydı yok. Çözüm: Numaranın coexistence ile bağlandığını kontrol edin. - `idempotency_key_conflict` (422): Aynı `Idempotency-Key` farklı bir gövdeyle kullanılmış. Çözüm: Her farklı istek için yeni anahtar üretin. - `invalid_contacts` (422): Kişi kartı mesajı hatalı: kart sayısı 1–20 dışında ya da `name.formatted_name` eksik. Çözüm: `error.field` alanındaki kartı düzeltin. - `invalid_context` (422): `context.message_id` geçerli bir `wamid` değil. Çözüm: Webhook'taki `data.wamid` değerini kullanın. - `invalid_flow` (422): `POST /messages` içindeki `flow` nesnesi hatalı. Çözüm: `error.field` alanını düzeltin. - `invalid_flow_id` (422): `flow_id` yalnız rakam olmalı. Çözüm: Meta form kimliğini kullanın. - `invalid_flow_json` (422): Flow JSON geçersiz ya da 10 MB'ı aşıyor. Çözüm: Flow JSON'u doğrulayın. - `invalid_interactive` (422): Etkileşimli mesajın `interactive` nesnesi hatalı ya da Meta sınırını aşıyor. Çözüm: `error.field` alanındaki değeri Meta sınırlarına göre düzeltin. - `invalid_location` (422): Konum mesajında enlem/boylam eksik ya da aralık dışı, ad/adres çok uzun. Çözüm: `error.field` alanındaki değeri düzeltin; enlem −90…90, boylam −180…180. - `invalid_media` (422): Medya nesnesi hatalı (`link`/`id` yok, `https` değil, `audio.voice` boolean değil, boş dosya). Çözüm: Medya nesnesini düzeltin. - `invalid_name` (422): Kişi adı en az 2 karakter olmalı. Çözüm: `name` alanını doldurun. - `invalid_period` (422): `period` geçersiz. Çözüm: `7d`, `30d` ya da `90d` kullanın. - `invalid_phone` (422): Telefon numarası geçersiz. Çözüm: Ülke koduyla, `+` olmadan gönderin (ör. `905550000000`). - `invalid_reaction` (422): `reaction.message_id` ya da `emoji` geçersiz. Çözüm: Tek bir emoji ve geçerli `wamid` gönderin. - `invalid_request` (422): Zorunlu alan eksik ya da biçim hatalı. `error.field` sorunlu alanı belirtir. Çözüm: İstek gövdesini referanstaki alanlarla karşılaştırın. - `invalid_sdp` (422): SDP boş ya da geçersiz. Çözüm: WebRTC ucunuzdan geçerli bir SDP üretin. - `invalid_since` (422): `since` ISO 8601 değil ya da izinli aralık dışında. Çözüm: Örn. `2026-10-09T00:00:00Z`; mesajlarda 90, yeniden teslimde 7 gün sınırı. - `invalid_template_flow_button` (422): Şablon form butonu parametresi hatalı. Çözüm: `sub_type: "flow"` buton biçimine uyun. - `invalid_webhook` (422): Webhook adresi `https`/genel değil ya da olay listesi boş/geçersiz. Çözüm: Geçerli olay adları ve genel bir `https` adres kullanın. - `meta_call_failed` (422): Meta arama isteğini başka bir nedenle reddetti. Çözüm: `meta_code` ve `meta_message` alanlarına bakın. - `meta_error` (422): Meta isteği reddetti (okundu, profil). `meta_code` döner. Çözüm: Meta hata koduna göre düzeltin. - `meta_flow_failed` (422): Meta form isteğini reddetti. Çözüm: `validation_errors` alanını düzeltin. - `meta_send_failed` (422): Meta gönderimi reddetti. `meta_code` ve `meta_subcode` alanlarına bakın (`retryable: true`). Çözüm: `meta_code: 131047` ise 24 saat penceresi kapalıdır; şablon kullanın. - `meta_upload_failed` (422): Meta dosyayı kabul etmedi. Şablon başlık örneği yüklemesinde `meta_*` alanları da döner. Çözüm: Dosya türünü ve boyutunu kontrol edin. - `outside_service_window` (422): Arama izni isteği için 24 saatlik pencere kapalı. Çözüm: Müşteri size yazdıktan sonra tekrar deneyin. - `receiver_cannot_receive_calls` (422): Alıcı arama alamıyor (Meta 138001). Çözüm: Kişiye mesajla ulaşın; tekrar denemeyin (`retryable: false`). - `template_delete_failed` (422): Meta şablon silmeyi kabul etmedi (ör. `DISABLED` şablon silinemez; `meta_*` alanları döner). Çözüm: Tekrar deneyin; sürerse destek ekibine bildirin. - `template_edit_failed` (422): Meta şablon düzenlemesini kabul etmedi (`meta_*` alanları döner). Çözüm: Meta'nın sebebine (`meta_user_message`) göre bileşenleri düzeltin. - `template_invalid` (422): Şablon Meta kurallarına aykırı; Meta'ya gönderilmedi. Çözüm: `message` alanındaki kuralı düzeltin; `POST /templates/validate` ile önceden deneyin. - `template_not_in_waba` (422): Şablon gönderen numaranın WABA'sında yok ya da onaylı değil (Meta 132001). Çözüm: Şablonun aynı WABA'da `APPROVED` olduğundan emin olun. - `template_rejected` (422): Meta şablonu reddetti. Çözüm: Meta'nın sebebine göre içeriği düzeltip tekrar gönderin. - `too_many` (422): Toplu eklemede 1000 sınırı aşıldı. Çözüm: Listeyi 1000'lik parçalara bölün. - `upload_failed` (422): Dosya alınamadı. Çözüm: Yüklemeyi tekrar deneyin. - `waba_required` (422): Birden çok WABA'nız var; hedef belirtilmedi. Çözüm: `waba_id` gönderin. - `category_not_editable` (422): Onaylı şablonda kategori değiştirilemez. Çözüm: Kategoriyi göndermeyin ya da `POST /templates/{id}/copy` ile yeni şablon açın. - `invalid_template_parameters` (422): `POST /messages` şablon gönderiminde URL düğme bileşeni kayıtlı şablon tanımına uymuyor (`field` yolu söyler). Meta'ya gönderilmez; yalnız şablon Veri Merkezi'nde kayıtlıysa denetlenir, `AUTHENTICATION` hariç. Çözüm: Değişkenli URL butonu için `{type:"button", sub_type:"url", index:"0", parameters:[{type:"text", text:""}]}` gönderin; sabit adresli URL butonuna bileşen göndermeyin. - `business_call_limit` (429): Aynı kişiye 24 saatte yapılabilecek işletme araması sınırı doldu (Meta 138012). Çözüm: 24 saat sonra tekrar deneyin. - `call_rate_limited` (429): Arama sınırı aşıldı. Çözüm: Bekleyip tekrar deneyin. - `concurrent_call_limit` (429): Eşzamanlı arama sınırına ulaşıldı (Meta 138002). Çözüm: Süren bir arama bitince tekrar deneyin (`retryable: true`). - `permission_request_limited` (429): Aynı kişiye arama izni isteği sınırı (24 saatte 1, 7 günde 2). Çözüm: Sınır dolunca tekrar deneyin. - `rate_limited` (429): Dakikalık istek sınırı aşıldı. `Retry-After` ve `error.retry_after` döner. 2.27.0: `error.scope` (`key`, `account`, `account_share`), `error.bucket` ve `X-RateLimit-Scope` başlığı hangi sınırın aşıldığını gösterir. Çözüm: `Retry-After` saniye kadar bekleyin; isteklerinizi yayın. - `sync_rate_limited` (429): Şablon senkronu WABA başına dakikada 1. Çözüm: `Retry-After` kadar bekleyin. - `template_edit_limit` (429): Şablon düzenleme sınırı doldu (onaylı şablon 24 saatte 1, 30 günde 10). `next_edit_at` ve `retry_after` (saniye) döner; Meta kaynaklıysa `meta_*` alanları da gelir. Çözüm: `next_edit_at` zamanından sonra tekrar deneyin. - `erasure_failed` (500): KVKK silmesi tamamlanamadı. Çözüm: Tekrar deneyin; işlem idempotenttir. - `media_path_invalid` (500): Medya kaydının dosya yolu geçersiz. Çözüm: Destek talebinde `X-Request-Id` değerini paylaşın. - `media_unreadable` (500): Medya dosyası okunamadı. Çözüm: Kısa süre sonra tekrar deneyin; sürerse destekle iletişime geçin. - `send_failed` (500): Gönderim sistem hatasıyla tamamlanamadı; kredi iade edildi. Çözüm: Aynı `Idempotency-Key` ile güvenle tekrar deneyin. - `server_error` (500): Beklenmeyen sunucu hatası (arama, form, numara ayırma). Çözüm: Üstel bekleme ile tekrar deneyin. - `test_inbound_failed` (500): Sahte gelen mesaj işlenemedi. Çözüm: Tekrar deneyin. - `webhook_create_failed` (500): Abonelik oluşturulamadı. Çözüm: Tekrar deneyin. - `call_connection_error` (502): Arama bağlantısı kurulamadı (Meta 138004). Çözüm: Kısa bir beklemeden sonra tekrar deneyin (`retryable: true`). - `meta_unavailable` (502): Meta yanıt vermiyor (arama ve form uçları). Şablon uçlarında Meta'ya ulaşılamadığında da bu kod döner. Çözüm: Üstel beklemeyle tekrar deneyin (`retryable: true`). - `meta_unexpected_response` (502): Meta işlemi kabul etti ama kimlik döndürmedi; sonuç belirsiz. Çözüm: Aynı isteği tekrar göndermeyin; önce listeleyip durumu kontrol edin. - `sync_failed` (502): Meta şablon listesi alınamadı. Çözüm: Bir süre sonra tekrar deneyin. - `feature_unavailable` (503): Özellik geçici olarak kullanılamıyor. Çözüm: Kısa süre sonra tekrar deneyin. - `temporarily_unavailable` (503): Altyapı geçici olarak yoğun (ör. veritabanı bağlantısı kısa süre reddedildi). 2.27.0 öncesinde bu durumlar `500` dönebiliyordu. Çözüm: `Retry-After` (5 sn) sonra aynı isteği tekrarlayın; gönderimde aynı `Idempotency-Key` kullanın. - `rate_limit_unavailable` (503): Hız sınırı servisi geçici olarak kullanılamıyor (gönderim uçları güvenli tarafta kalır). Çözüm: `Retry-After` (5 sn) sonra tekrar deneyin. - `call_connect_timeout` (504): Arama bağlantısı zaman aşımına uğradı (Meta 138007). Çözüm: Tekrar deneyin (`retryable: true`). --- # SDK ve araçlar SDK'lar paket yöneticisinde yayımlı değildir; dosyayı indirip projenize ekleyin. ## Node.js Node.js 18+ (yerleşik fetch); bağımlılık yok ```bash curl -O https://api.verimerkezi.app/sdk/nodejs/verimerkezi-client.js ``` ``` const { VeriMerkeziClient } = require('./verimerkezi-client'); const vm = new VeriMerkeziClient(process.env.VM_API_KEY); const r = await vm.sendTemplate('1234567890', '905550000000', 'siparis_onayi', 'tr', [ { type: 'body', parameters: [{ type: 'text', text: 'Ahmet' }] }, ]); console.log(r.wamid); ``` ## Python Python 3.8+; bağımlılık: requests ```bash curl -o verimerkezi.py https://api.verimerkezi.app/sdk/python/verimerkezi.py.txt ``` ``` import os from verimerkezi import VeriMerkeziClient vm = VeriMerkeziClient(os.environ['VM_API_KEY']) r = vm.send_template('1234567890', '905550000000', 'siparis_onayi', 'tr', [ {'type': 'body', 'parameters': [{'type': 'text', 'text': 'Ahmet'}]} ]) print(r['wamid']) ``` ## PHP PHP 8.0+, cURL eklentisi; bağımlılık yok ```bash curl -O https://api.verimerkezi.app/sdk/php/VeriMerkeziClient.php ``` ``` sendTemplate('1234567890', '905550000000', 'siparis_onayi', 'tr', [ ['type' => 'body', 'parameters' => [['type' => 'text', 'text' => 'Ahmet']]], ]); echo $r['wamid']; ``` ## Postman /sdk/postman/VeriMerkezi.postman_collection.json ## OpenAPI /sdk/openapi.yaml.txt