Developer Platform API v1

Müşteri iletişiminizi kendi sistemlerinize entegre edin

Mesajilet Developer API, kurumunuzun ERP, CRM, e-ticaret ve çağrı merkezi yazılımlarının WhatsApp Business, Instagram, Messenger ve web sohbeti kanallarında müşterilerle doğrudan iletişim kurmasını sağlar. Tek bir REST yüzeyi, kanaldan bağımsız veri modeli ve imzalı olay bildirimleriyle entegrasyon süresi saatlerle ölçülür.

  • REST / JSONUTF-8, ISO-8601 zaman
  • Bearer anahtarKapsam ve IP kısıtı
  • HMAC-SHA256İmzalı webhook olayları
  • Meta Resmi APIWhatsApp Cloud API altyapısı
POST /v1/messages/template
curl https://all.mesajilet.com/api/v1/messages/template \
  -H "Authorization: Bearer msj_live_…" \
  -H "Idempotency-Key: order-18421" \
  -d '{ "channel": "whatsapp", "to": "905321234567",
       "name": "siparis_onay",
       "variables": ["Ayşe", "18421", "3 Eylül"] }'

202 Accepted
{ "success": true,
  "data": { "id": "msg_1724412345678", "status": "queued" },
  "request_id": "req_01j9x…" }

Platform özellikleri

Kanaldan bağımsız veri modeliAlıcı, mesaj tipi ve içerik her kanalda aynı şema; channel alanı ile hedef kanal seçilir. Yeni kanallar mevcut entegrasyonunuzu etkilemez.
Kurumsal güvenlikAnahtarlar yalnız hash olarak saklanır; kapsam (scope) ve IP kısıtı tanımlanabilir. Tüm trafik TLS üzerinden, webhook olayları HMAC-SHA256 ile imzalanır.
İzlenebilirlikHer istek benzersiz request_id ile günlüklenir; teslimat durumları, webhook denemeleri ve hata kodları panelde raporlanır.
Güvenilir teslimatIdempotency-Key ile çift gönderim engellenir; webhook olayları üstel geri çekilmeyle beş kez denenir, hiçbir olay sessizce kaybolmaz.
Açıklayıcı hata sözleşmesiKararlı hata kodları; her yanıtta neden oluştuğu ve nasıl giderileceği yazılır. Sağlayıcı hataları Türkçe açıklamayla birlikte iletilir.
Pencere ve şablon yönetimi24 saat hizmet penceresi otomatik denetlenir; onaylı şablonlar, değişken şeması ve onay durumu API üzerinden sorgulanır.

Tipik entegrasyon senaryoları

E-ticaret / ERPSipariş onayı, kargo takibi ve teslimat bildirimlerini sipariş yaşam döngüsüne bağlayın; müşteri yanıtlarını CRM kaydına işleyin.
CRM ve çağrı merkeziGelen mesajları temsilci masaüstüne aktarın, yanıtları aynı kanaldan gönderin; teslim ve okundu bilgisiyle SLA takibi yapın.
Doğrulama ve bildirimOTP, randevu hatırlatma, fatura ve ödeme bildirimlerini UTILITY / AUTHENTICATION şablonlarıyla iletin.
Çok kanallı destekWhatsApp, Instagram, Messenger ve web sohbetini tek akışta toplayın; kanal fark etmeksizin aynı kodla yanıt verin.
Temel adreshttps://all.mesajilet.com/api/v1
SürümlemeYol tabanlı (/v1); geriye dönük uyumlu değişiklikler duyurulur
UyumlulukKVKK ve Meta WhatsApp Business politikalarına uygun; pazarlama çıkış (opt-out) tercihleri otomatik uygulanır
Destek[email protected] — taleplerde request_id belirtin

Hızlı başlangıç

  1. Panel › Ayarlar › Geliştirici (API) sayfasından API Anahtarı Üret. Anahtar bir kez gösterilir (msj_live_…).
  2. Bağlantıyı doğrulayın:
curl https://all.mesajilet.com/api/v1/me \
  -H "Authorization: Bearer msj_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
  1. İlk mesajınızı gönderin (müşteri size son 24 saatte yazdıysa serbest metin; yazmadıysa şablon):
curl -X POST https://all.mesajilet.com/api/v1/messages/template \
  -H "Authorization: Bearer msj_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-18421-confirm" \
  -d '{
    "channel": "whatsapp",
    "to": "905321234567",
    "name": "siparis_onay",
    "variables": ["Ayşe", "18421", "3 Eylül"]
  }'
$res = Http::withToken('msj_live_…')
    ->withHeaders(['Idempotency-Key' => 'order-18421-confirm'])
    ->post('https://all.mesajilet.com/api/v1/messages/template', [
        'channel'   => 'whatsapp',
        'to'        => '905321234567',
        'name'      => 'siparis_onay',
        'variables' => ['Ayşe', '18421', '3 Eylül'],
    ]);

if (!$res->json('success')) {
    $e = $res->json('error');
    // $e['code'], $e['message'], $e['reason'], $e['fix'], $e['request_id']
}
$messageId = $res->json('data.id');   // msg_…
const res = await fetch('https://all.mesajilet.com/api/v1/messages/template', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer msj_live_…',
    'Content-Type': 'application/json',
    'Idempotency-Key': 'order-18421-confirm',
  },
  body: JSON.stringify({
    channel: 'whatsapp', to: '905321234567',
    name: 'siparis_onay',
    variables: ['Ayşe', '18421', '3 Eylül'],
  }),
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message} — ${body.error.fix}`);
console.log(body.data.id); // msg_…
import requests

r = requests.post("https://all.mesajilet.com/api/v1/messages/template",
    headers={"Authorization": "Bearer msj_live_…", "Idempotency-Key": "order-18421-confirm"},
    json={"channel": "whatsapp", "to": "905321234567", "name": "siparis_onay",
          "variables": ["Ayşe", "18421", "3 Eylül"]})
body = r.json()
if not body["success"]:
    raise RuntimeError(f'{body["error"]["code"]}: {body["error"]["message"]} — {body["error"]["fix"]}')
print(body["data"]["id"])

Yanıt 202 Accepted — mesaj kuyruğa alındı; teslim ilerlemesini webhook ile ya da GET /messages/{id} ile izleyin.

Kimlik doğrulama

Her istekte Authorization: Bearer <anahtar> başlığı. Anahtarlar firma bazlıdır; isteğe bağlı IP kısıtı ve kapsam (scope) ile daraltılabilir. Anahtar sunucuda yalnız hash olarak saklanır — kaybolursa yenisi üretilir.

Anahtarınızı istemci tarafında (tarayıcı, mobil uygulama) asla kullanmayın. Sızdığından şüphelenirseniz panelden anında iptal edin.

Kapsamlar

Kapsamİzin
messages:sendMesaj gönderme (serbest + şablon)
messages:readMesaj durumu sorgulama
templates:readŞablon listesi
media:writeMedya yükleme
contacts:readKişi / 24 saat penceresi sorgusu
webhooks:testWebhook test olayı gönderme
conversations:readSohbet listesi ve mesaj geçmişi
conversations:writeSohbet kapatma / temsilciye atama
contacts:writeKişi oluşturma ve güncelleme
optouts:readPazarlama çıkış listesi
optouts:writePazarlama çıkışı ekleme/çıkarma
blocklist:readEngellenen numaralar
blocklist:writeNumara engelleme/kaldırma
insights:readİstatistik, etiket ve temsilci listesi

Kanallar

İsteklerde channel alanı kanalı seçer (varsayılan whatsapp). Birden fazla hat/sayfa bağlıysa connection_id ile seçin (GET /channels).

KanalAlıcı (to)Serbest mesaj penceresiMetinMedyaKonumEtkileşimliŞablon
whatsapp
WhatsApp
Uluslararası numara, rakam: 905321234567 24 saat
instagram
Instagram DM
Webhook'taki from.id (IG-scoped kullanıcı id) 24 saat
messenger
Facebook Messenger
Webhook'taki from.id (PSID) 24 saat
web
Web Canlı Destek
Ziyaretçi kimliği web_… Sınırsız
24 saat kuralı (WhatsApp/Instagram/Messenger): Müşteri size son 24 saat içinde yazdıysa serbest mesaj gönderebilirsiniz. Süre dolduysa WhatsApp'ta yalnız onaylı şablon gidebilir; Instagram/Messenger'da müşterinin yeniden yazması gerekir. Durumu önceden öğrenmek için GET /contacts/{id}/window.

Kurallar

Yanıt zarfı

// Başarı
{ "success": true, "data": { … }, "request_id": "req_01j…" }

// Hata
{
  "success": false,
  "error": {
    "code": "WINDOW_CLOSED",
    "http": 409,
    "message": "Müşteri hizmet penceresi kapalı (24 saat). Serbest mesaj gönderilemez.",
    "reason": "Kanal kuralı: müşterinin son mesajından 24 saat geçtikten sonra yalnız onaylı şablon gönderilebilir. Son müşteri mesajı: 2026-08-21T14:02:11+03:00.",
    "fix": "POST /v1/messages/template ile onaylı bir şablon gönderin; müşteri cevap verince pencere yeniden açılır.",
    "param": "to",
    "docs": "https://all.mesajilet.com/developers#err-WINDOW_CLOSED",
    "request_id": "req_01j…"
  }
}

request_id her yanıtta (başlıkta da X-Request-Id) bulunur; destek talebinde bu kimliği iletin — isteğinizi saniyesine kadar buluruz.

Idempotency

Yazma isteklerine Idempotency-Key başlığı ekleyin (ör. sipariş no + olay). Aynı anahtar + aynı gövde 24 saat içinde tekrar gelirse mesaj yeniden gönderilmez, ilk yanıt aynen döner (Idempotent-Replayed: true). Aynı anahtar farklı gövdeyle gelirse IDEMPOTENCY_CONFLICT.

Hız sınırı

Varsayılan 1800 istek/dakika (anahtar başına; panelden değiştirilebilir) ve saniyelik patlama (burst) sınırı dakikalık ortalamanın 2 katı, en az 10/sn (1800/dk için 60/sn). Her yanıtta X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Burst; aşımda 429 RATE_LIMITED + Retry-After. WhatsApp'ın numara başına saniyelik gönderim tavanı ayrıca sunucu tarafında otomatik uygulanır — kampanya trafiği API trafiğinizi yavaşlatmaz (öncelikli kuyruk).

Test modu (sandbox)

Panelden test anahtarı (msj_test_…) üretin. Test anahtarıyla yapılan isteklerde tüm doğrulamalar (alıcı, şablon, değişken, medya) gerçek çalışır; ancak mesaj sağlayıcıya gönderilmez, sohbet kutusuna düşmez ve hizmet penceresi kontrolü atlanır. Yanıt status: "sent" ve test: true döner; tanımlı webhook'a sent → delivered → read olayları "test": true işaretiyle simüle edilir. Yanıt başlığı: X-Mesajilet-Mode: test. Canlıya geçerken yalnız anahtarı değiştirmeniz yeterlidir.

Mesaj durumları

queuedsentdeliveredread · failed (sebep error alanında: sağlayıcı kodu + Türkçe açıklama).

Uçlar

GET /me

Anahtar, hesap, modüller, lisans ve bağlı kanallar. Entegrasyonun ilk sağlık kontrolü için kullanın.

GET /channels

Her kanal için yetenekler (capabilities), pencere süresi ve bağlantılar (connection_id değerleri: wa_15, ig_3, fb_7, web_2).

POST /messages

Serbest (oturum) mesajı. Müşterinin size yazmış olması gerekir (pencere). Alanlar:

AlanTipAçıklama
channelstringwhatsapp (varsayılan) · instagram · messenger · web
connection_idstringBirden çok hat/sayfa varsa; yoksa varsayılan.
to *stringAlıcı (kanala göre numara / kullanıcı id).
typestringtext (varsayılan) · image · video · audio · document · location · interactive
textstringtype=text için gövde (WhatsApp 4096, Instagram 1000, Messenger 2000 karakter; uzun metin IG/Messenger'da otomatik bölünür).
mediaobject{"id": "…"} (POST /media'dan) veya {"url": "https://…"} (herkese açık). IG/Messenger/web yalnız url.
caption, filenamestringMedya açıklaması / belge adı.
latitude, longitude, name, addressnumber/stringtype=location.
interactiveobject{"kind":"button","text":"…","buttons":[{"id":"yes","title":"Evet"}]} (≤3) veya {"kind":"list","text":"…","button":"Seçin","sections":[{"title":"…","rows":[{"id":"a","title":"…","description":"…"}]}]} (≤10 satır). Müşterinin seçimi webhook'ta button.payload olarak gelir.
reply_tostringAlıntılanacak mesajın external_id'si (wamid / mid).
// Görsel + açıklama (WhatsApp)
{ "to": "905321234567", "type": "image", "media": { "url": "https://cdn.site.com/u/fatura.jpg" }, "caption": "Faturanız ektedir." }

// Instagram DM'e hızlı yanıt butonları
{ "channel": "instagram", "to": "17841400000000000", "type": "interactive",
  "interactive": { "kind": "button", "text": "Siparişiniz hazır. Teslimat tercihiniz?", "buttons": [ {"id":"kargo","title":"Kargo"}, {"id":"magaza","title":"Mağazadan al"} ] } }

POST /messages/template

Onaylı WhatsApp şablonu. Pencere gerekmez; konuşmayı başlatmanın tek yolu budur. Değişkenler şablonla birebir doğrulanır — eksik/fazla değişken anında açıklayıcı hata döner (Meta'nın belirsiz 131008'ini beklemezsiniz).

AlanAçıklama
name *Şablon adı. Liste: GET /templates.
languageGenelde gerekmez — dil şablon adından çözülür. Yalnız aynı adı birden çok dilde tuttuysanız zorunlu olur; o durumda hata gövdesi mevcut dilleri listeler.
variablesPOSITIONAL şablonda dizi ["Ayşe","18421"]; NAMED şablonda nesne {"ad":"Ayşe","siparis":"18421"}. Satır sonu içeremez.
headerMedya başlıklı şablon: {"media_id":"…"} veya {"url":"https://…","filename":"fatura.pdf"}. Metin başlıkta değişken varsa {"text":"…"}. Boş bırakılırsa şablonun kendi örnek medyası kullanılır.
buttonsDinamik URL butonu için [{"index":0,"text":"18421"}].
Pazarlama (MARKETING) şablonları, "mesaj istemiyorum" diyen alıcılara RECIPIENT_OPTED_OUT ile reddedilir — yasal koruma otomatiktir.

GET /messages/template

Aynı gönderimin tek URL'lik hâli. Başlık ya da JSON gövdesi kuramayan çağırıcılar için: e-ticaret panelinin "sipariş durumu değişince şu adresi çağır" alanı, otomasyon araçları, hızlı deneme.

Anahtarı adresin içine koyabilirsiniz — tek parça URL, başlık gerekmez:

https://all.mesajilet.com/api/v1/messages/template/msj_live_…?to=905321234567&name=kargo_cikti

Başlık kurabiliyorsanız tercih edin; o zaman anahtar adreste görünmez:

https://all.mesajilet.com/api/v1/messages/template?to=905321234567&name=kargo_cikti
Authorization: Bearer msj_live_…
ParametreAçıklama
to *Alıcı numarası. phone da kabul edilir.
name *Şablon adı. template da kabul edilir.
var1, var2Sıralı değişkenler: &var1=Ahmet&var2=18421. İsimli şablonda &vars[ad]=Ahmet.
AnahtarÜç biçim: adres yolunda /messages/template/anahtar, sorguda &api_key=…, ya da Authorization başlığı. İlk ikisi yalnız bu GET ucunda geçerlidir.
Aynı istek 60 saniye içinde tekrarlanırsa mesaj yeniden gönderilmez, ilk yanıt Idempotent-Replayed: true ile döner. GET adresleri istem dışı tekrar çağrılır: tarayıcı önden yükler, vekil sunucu yeniden dener, kullanıcı yenilemeye basar. Kendi idempotency_key değerinizi verirseniz koruma 24 saate çıkar.
Güvenlik: Anahtarı adreste taşıdığınızda (yol ya da sorgu) sunucu erişim günlüklerine, Referer başlığına ve tarayıcı geçmişine düşer. Bu ucu kullanacaksanız yalnız messages:send yetkili ayrı bir anahtar üretin, sızdığında tek tıkla iptal edin. Panel istek günlüğüne anahtar yazılmaz.

GET /messages/{id}

Gönderim yanıtındaki id (msg_…) ile durum, external_id (wamid/mid) ve varsa hata detayı.

GET /templates

Filtreler: status, language, category. Her şablonda parameter_format, variables.names, header.format, dinamik butonlar ve örnek değerler döner — istek gövdenizi buna göre kurun.

POST /media

multipart/form-data, alan adı file. Dönen id 30 gün geçerlidir; aynı dosyayı binlerce alıcıya gönderirken URL yerine id kullanın (hız + Meta 131053 hatası yok). Sınırlar: görsel 5 MB, ses/video 16 MB, belge 100 MB.

curl -X POST https://all.mesajilet.com/api/v1/media -H "Authorization: Bearer msj_live_…" -F "[email protected]"

GET /contacts/{id}/window?channel=whatsapp

window_open, last_inbound_at, closes_at ve can_send.session_message / template. Göndermeden önce hangi yolu kullanacağınıza karar verin.

POST /webhooks/test

Gövde: {"event":"message.received"}. Tanımlı adresinize örnek olay gönderir, sunucunuzun HTTP yanıtını ve süresini döner.

GET /conversations

Sohbet listesi. Süzgeçler: status (open/closed/bot), channel, agent_id, unread=true, phone, from/to. Sayfalama: limit (≤100), cursor, order (desc/asc).

curl "https://all.mesajilet.com/api/v1/conversations?status=open&limit=25"   -H "Authorization: Bearer msj_live_…"

// Yanıt
{ "success": true, "data": {
    "conversations": [ { "phone": "905321234567", "channel": "whatsapp", "status": "open",
      "agent": { "id": 8, "name": "Zehra" }, "unread_count": 2,
      "last_message": { "text": "Kargom nerede?", "direction": "in", "at": "2026-08-29T10:12:00+03:00" } } ],
    "pagination": { "limit": 25, "count": 25, "has_more": true, "next_cursor": "NDIzOTQ0" } } }
Sayfalama imleçle yapılır. next_cursor değerini bir sonraki isteğe ?cursor= olarak geçirin; has_more:false gelene kadar sürdürün. İmleç kullanıldığı için araya yeni sohbet girse bile aynı kayıt iki kez dönmez.

GET /conversations/{phone}

Tek sohbetin özeti + kişi bilgisi. Numara 0532…, +90532… ya da 90532… biçiminde verilebilir.

GET /conversations/{phone}/messages

Mesaj geçmişi, en yeniden eskiye. Süzgeç: direction (in/out), limit (≤100). Sayfalama zaman damgasıyla: yanıttaki pagination.before değerini ?before= olarak gönderin.

PATCH /conversations/{phone}

Sohbeti kapatır/açar veya temsilciye atar.

curl -X PATCH https://all.mesajilet.com/api/v1/conversations/905321234567   -H "Authorization: Bearer msj_live_…" -H "Content-Type: application/json"   -d '{"status":"closed"}'

// Temsilciye atama (durum otomatik "open" olur)
{ "agent_id": 8 }

GET /contacts

Kişi listesi. search ad, görünen ad, kullanıcı adı, e-posta ve telefonda arar (telefon yazımı serbest: 0532 123 45 67 da bulur).

POST /contacts  ·  PATCH /contacts/{phone}

Alanlar: phone*, name, email, display_name, username. POST varsa günceller, yoksa oluşturur (upsert) — yeni kayıtta 201 döner.

GET /opt-outs  ·  POST  ·  DELETE /opt-outs/{phone}

Pazarlama iletisi istemeyenler. Bu listedeki numaraya kampanya ve akış mesajı gönderilmez.

Yasal uyarı: DELETE kişiyi listeden çıkarır ve tekrar pazarlama mesajı almaya başlar. Yalnız açık rıza yeniden alındıysa kullanın.

GET /blocked-numbers  ·  POST  ·  DELETE /blocked-numbers/{phone}

Engellenen numaralar — bu listedekilere hiçbir mesaj gitmez. Pazarlama çıkışından farkı: opt-out yalnız pazarlamayı durdurur, engel tüm iletişimi keser.

GET /stats

Dönem özeti (varsayılan son 30 gün; from/to ile değiştirilir): gelen/giden mesaj, teslim, okunma, başarısız sayıları ve oranları + sohbet durumu dağılımı.

GET /labels  ·  GET /agents

Etiket ve temsilci tanımları. Temsilci kaydında parola/oturum bilgisi dönmez.

Webhook — olay bildirimi

Panel › Geliştirici › Webhook Tanımla: https adresiniz + abone olacağınız olaylar. Her olay POST ile JSON gövde olarak gelir; 10 saniye içinde 2xx dönün (işlemeyi kuyruğa atın).

OlayNe zaman
message.receivedGelen mesaj (metin, medya, buton yanıtı, konum…)
message.statusMesaj durumu (sent → delivered → read / failed + sebep)
template.statusŞablon onay durumu (APPROVED / REJECTED + ret sebebi)
// Zarf
{
  "id": "whd_01j…",              // teslimat kimliği — aynı id iki kez gelirse yok sayın (dedupe)
  "event": "message.received",
  "api_version": "v1",
  "created_at": "2026-08-23T10:15:02+03:00",
  "company_id": 12,
  "attempt": 1,
  "data": {
    "message": {
      "id": "msg_in_1724412902123",
      "external_id": "wamid.HBgNOTA1…",
      "channel": "whatsapp",
      "connection_id": "wa_15",
      "from": { "id": "905321234567", "name": "Ayşe Yılmaz" },
      "type": "text",                    // text | image | video | audio | document | location | …
      "text": "Siparişim ne zaman gelir?",
      "media": { "url": "https://…", "mime_type": "image/jpeg" },   // medyalı mesajda
      "button": { "payload": "kargo", "title": "Kargo" },            // buton/liste yanıtında
      "reply_to": "wamid.…",                                         // alıntılı cevapta
      "received_at": "2026-08-23T10:15:01+03:00"
    }
  }
}

// message.status → data.message: { id, external_id, channel, to, status: "delivered", error: null|{code,title,detail,provider_message}, occurred_at }
//   Kapsam: API ve panelden gönderilen mesajlar. Toplu KAMPANYA alıcılarının durumları olay olarak GÖNDERİLMEZ
//   (on binlerce olayla sunucunuzu boğmamak için); kampanya raporu panelde ve kampanya dışa aktarımında yer alır.
// template.status → data.template: { name, language, category, status: "APPROVED"|"REJECTED"|…, rejection_reason }

İmza doğrulama

Her istekte X-Mesajilet-Signature: t=<unix>,v1=<hex>. v1 = HMAC_SHA256(secret, t + "." + hamGövde). Ham gövdeyi (yeniden serileştirmeden) kullanın; t 5 dakikadan eskiyse reddedin (tekrar saldırısı koruması). Diğer başlıklar: X-Mesajilet-Event, X-Mesajilet-Delivery-Id, X-Mesajilet-Attempt.

$secret = getenv('MESAJILET_WEBHOOK_SECRET');
$raw    = file_get_contents('php://input');
$hdr    = $_SERVER['HTTP_X_MESAJILET_SIGNATURE'] ?? '';
parse_str(str_replace(',', '&', $hdr), $p);           // ['t' => '…', 'v1' => '…']

$expected = hash_hmac('sha256', $p['t'] . '.' . $raw, $secret);
if (!hash_equals($expected, $p['v1'] ?? '') || abs(time() - (int) $p['t']) > 300) {
    http_response_code(401); exit;
}
$event = json_decode($raw, true);
// … kuyruğa at, hemen 200 dön
http_response_code(200);
import crypto from 'node:crypto';
// Express: app.post('/webhook', express.raw({ type: '*/*' }), handler)
function handler(req, res) {
  const sig = Object.fromEntries((req.get('X-Mesajilet-Signature') || '').split(',').map(kv => kv.split('=')));
  const expected = crypto.createHmac('sha256', process.env.MESAJILET_WEBHOOK_SECRET)
    .update(`${sig.t}.${req.body}`).digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(sig.t)) < 300;
  if (!fresh || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig.v1 || ''))) return res.sendStatus(401);
  const event = JSON.parse(req.body);
  res.sendStatus(200);         // önce yanıt, sonra işle
  queue.add(event);
}
import hmac, hashlib, time, json
from flask import request, abort

def webhook():
    raw = request.get_data()
    parts = dict(kv.split("=", 1) for kv in request.headers.get("X-Mesajilet-Signature", "").split(","))
    expected = hmac.new(SECRET.encode(), f"{parts['t']}.".encode() + raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, parts.get("v1", "")) or abs(time.time() - int(parts["t"])) > 300:
        abort(401)
    event = json.loads(raw)
    # kuyruğa at
    return "", 200

Yeniden deneme ve sağlık

2xx dışı yanıt ya da 10 sn zaman aşımı → 5 deneme: 1 dk, 5 dk, 30 dk, 2 sa, 12 sa sonra. Her deneme panelde görünür (HTTP kodu, yanıt gövdesinin ilk 500 karakteri). 50 ardışık başarısızlıkta adres otomatik devre dışı kalır; düzeltip panelden yeniden etkinleştirin. 410 Gone dönerseniz adres anında kapatılır. Olaylar sırasız gelebilir (ör. read, delivered'dan önce) — durumu ileri yönde güncelleyin.

Hata kataloğu

Tüm kodlar aşağıdadır; her yanıt reason (neden) ve fix (ne yapmalı) alanlarını taşır. Kodlar kararlıdır — programatik olarak bunlara göre dallanabilirsiniz.

KodHTTPMesajSebep → Çözüm
MISSING_API_KEY401Authorization başlığı eksik.İstekte "Authorization: Bearer msj_live_…" başlığı yok.
→ Panel › Geliştirici sayfasından ürettiğiniz anahtarı Authorization: Bearer <anahtar> başlığıyla gönderin.
INVALID_API_KEY401API anahtarı geçersiz.Anahtar biçimi hatalı ya da sistemde böyle bir anahtar yok.
→ Anahtarı eksiksiz kopyaladığınızdan emin olun (msj_live_ ile başlar, 49 karakter). Emin değilseniz yeni anahtar üretin.
API_KEY_REVOKED401API anahtarı iptal edilmiş.Bu anahtar panelden iptal edildi; artık hiçbir isteği yetkilendirmez.
→ Geçerli bir anahtar kullanın veya Panel › Geliştirici sayfasından yeni anahtar üretin.
IP_NOT_ALLOWED403Bu IP adresinden erişime izin verilmiyor ({ip}).Anahtar belirli IP adresleriyle sınırlandırılmış; isteğiniz başka bir adresten geldi.
→ İsteği izinli IP'den gönderin ya da anahtarın IP kısıtını panelden güncelleyin.
SCOPE_DENIED403Bu anahtarın "{scope}" yetkisi yok.Anahtar sınırlı kapsamla (scope) üretilmiş; bu uç o kapsamı gerektiriyor.
→ Panel › Geliştirici sayfasından anahtarın kapsamlarını genişletin ya da tam yetkili yeni anahtar üretin.
MODULE_DISABLED403Geliştirici API bu hesapta etkin değil.API modülü hesabınız için açılmamış.
→ Mesajilet yöneticinizle iletişime geçip API modülünün açılmasını isteyin.
WEBHOOK_MODULE_DISABLED403Webhook modülü bu hesapta etkin değil.Webhook modülü hesabınız için açılmamış.
→ Mesajilet yöneticinizle iletişime geçip Webhook modülünün açılmasını isteyin.
ACCOUNT_INACTIVE403Hesap pasif durumda.Firma hesabı yönetici tarafından pasife alınmış.
→ Mesajilet yöneticinizle iletişime geçin.
LICENSE_EXPIRED402Lisans süresi dolmuş.Hesabın lisansı sona erdiği için gönderim yapılamıyor.
→ Lisansınızı yenileyin; yenilendiği anda API otomatik açılır.
RATE_LIMITED429Hız sınırı aşıldı: dakikada en fazla {limit} istek.Bu anahtar izin verilen istek sayısını aştı.
→ {retry_after} saniye bekleyip tekrar deneyin. X-RateLimit-* başlıklarını izleyerek istek hızınızı ayarlayın; kalıcı artış için yöneticinize başvurun.
IDEMPOTENCY_CONFLICT409Idempotency-Key farklı bir istek gövdesiyle daha önce kullanılmış.Aynı Idempotency-Key, içeriği farklı bir istekle 24 saat içinde yeniden gönderildi.
→ Her farklı istek için benzersiz bir Idempotency-Key üretin (ör. UUID). Aynı isteği güvenle tekrarlamak için aynı anahtar + aynı gövde kullanın.
INVALID_JSON400İstek gövdesi geçerli JSON değil.Gövde ayrıştırılamadı (sözdizimi hatası ya da Content-Type eksik).
→ Content-Type: application/json gönderin ve gövdeyi bir JSON doğrulayıcıyla kontrol edin.
VALIDATION_FAILED422İstek doğrulaması başarısız: {detail}Bir veya daha fazla alan eksik ya da hatalı biçimde.
→ "errors" listesindeki her alanı düzeltin; alan açıklamaları için dokümantasyona bakın.
PAYLOAD_TOO_LARGE413İstek gövdesi çok büyük (en fazla {limit}).Gövde boyutu sınırı aşıyor.
→ Medya dosyalarını önce POST /v1/media ile yükleyip dönen id ile gönderin.
CHANNEL_UNSUPPORTED422Desteklenmeyen kanal: "{channel}".Bu kanal adı sistemde tanımlı değil.
→ Geçerli değerler: {supported}. Hesabınıza bağlı kanallar için GET /v1/channels çağırın.
CHANNEL_NOT_CONNECTED409"{channel}" kanalı bu hesaba bağlı değil.Hesapta bu kanal için aktif bir bağlantı (hat/sayfa) yok.
→ Panelden kanalı bağlayın; mevcut bağlantılar için GET /v1/channels çağırın.
CONNECTION_NOT_FOUND404Bağlantı bulunamadı: {connection_id}.Verilen connection_id bu hesaba ait aktif bir bağlantıya karşılık gelmiyor.
→ GET /v1/channels ile geçerli connection_id değerlerini alın.
CHANNEL_FEATURE_UNSUPPORTED422"{channel}" kanalı "{feature}" özelliğini desteklemiyor.Bu özellik kanalın altyapısında mevcut değil (ör. Instagram'da şablon mesajı yoktur).
→ Kanal özellikleri için GET /v1/channels yanıtındaki "capabilities" alanına bakın.
RECIPIENT_INVALID422Alıcı tanımlayıcısı geçersiz: "{to}".{why}
→ WhatsApp için uluslararası biçimde rakamlar (ör. 905321234567); Instagram/Messenger için webhook'ta gelen kullanıcı id'si.
RECIPIENT_BLOCKED403Bu alıcı engellenmiş: {to}.Numara/kullanıcı panelden engellenenler listesine alınmış.
→ Panel › Engellenen Numaralar'dan kaldırın ya da bu alıcıya gönderim yapmayın.
RECIPIENT_OPTED_OUT403Alıcı pazarlama mesajlarını durdurmuş: {to}.Kişi daha önce "mesaj istemiyorum" dedi; pazarlama kategorili şablon gönderilemez.
→ Yalnız işlemsel (UTILITY) şablon gönderin ya da kişiyi listeden çıkarmayın (yasal yükümlülük).
WINDOW_CLOSED409Müşteri hizmet penceresi kapalı ({hours} saat). Serbest mesaj gönderilemez.Kanal kuralı: müşterinin son mesajından {hours} saat geçtikten sonra yalnız onaylı şablon gönderilebilir. Son müşteri mesajı: {last_inbound}.
→ POST /v1/messages/template ile onaylı bir şablon gönderin; müşteri cevap verince pencere yeniden açılır.
WINDOW_NEVER_OPENED409Bu alıcı size hiç yazmamış; serbest mesaj gönderilemez.Kanal kuralı: konuşmayı yalnız müşteri başlatabilir (ilk temas şablonla yapılır).
→ POST /v1/messages/template ile onaylı bir şablon gönderin.
TEMPLATE_NOT_FOUND404Şablon bulunamadı: "{name}".Bu adla hesabınızda kayıtlı bir şablon yok.
→ GET /v1/templates ile mevcut şablonları listeleyin; ad birebir eşleşmeli.
TEMPLATE_NOT_APPROVED422Şablon "{name}" henüz onaylı değil (durum: {status}).Meta yalnız APPROVED durumundaki şablonların gönderimine izin verir.
→ Şablon onaylanana kadar bekleyin ya da GET /v1/templates?status=APPROVED ile onaylı bir şablon seçin.
TEMPLATE_VARIABLES_MISSING422Şablon değişkenleri eksik: {missing}.Şablon gövdesi {expected} değişken bekliyor; istekte {given} verildi.
→ "variables" alanını şablondaki tüm yer tutucularla doldurun. Değişken şeması için GET /v1/templates yanıtındaki "variables" alanına bakın.
TEMPLATE_VARIABLES_FORMAT422Şablon değişken biçimi uyumsuz: şablon {format} parametre kullanıyor.POSITIONAL şablonda "variables" sıralı dizi, NAMED şablonda {ad: değer} nesnesi olmalı.
→ "variables" alanını şablonun parametre biçimine göre gönderin (GET /v1/templates → parameter_format).
TEMPLATE_HEADER_MISSING422Şablon başlığı {format} medya bekliyor; "header" verilmedi.Şablon medya başlıklı; gönderimde medya referansı zorunlu.
→ "header": {"type":"{type}", "media_id":"…"} ya da {"url":"https://…"} ekleyin (media_id için POST /v1/media).
TEMPLATE_BUTTON_PARAM_MISSING422Dinamik URL butonu ({index}) için parametre eksik.Şablonda {{…}} içeren URL butonu var; değeri verilmedi.
→ "buttons": [{"index": {index}, "text": "…"}] ile buton parametresini gönderin.
MEDIA_REQUIRED422Medya referansı eksik.Medya tipli mesajda "media.id" ya da "media.url" verilmedi.
→ POST /v1/media ile yükleyip dönen id'yi kullanın ya da herkese açık bir https URL verin.
MEDIA_TOO_LARGE413Medya boyutu sınırı aşıyor: {size} (en fazla {limit}).Kanalın medya tipi için izin verilen boyut aşıldı.
→ Dosyayı sıkıştırın ya da küçültün. Sınırlar: görsel 5 MB, ses 16 MB, video 16 MB, belge 100 MB.
MEDIA_TYPE_UNSUPPORTED415Desteklenmeyen medya türü: {mime}.Bu MIME türü kanal tarafından kabul edilmiyor.
→ Desteklenen türler: görsel jpeg/png, video mp4/3gp, ses aac/mp3/ogg/amr, belge pdf/doc/xls/ppt/txt.
MEDIA_URL_INVALID422Medya URL'si geçersiz: {url}.URL https ile başlamalı ve herkese açık olmalı (kimlik doğrulama istememeli).
→ Dosyayı herkese açık https adresinde yayınlayın ya da POST /v1/media ile yükleyin.
MEDIA_UPLOAD_FAILED502Medya kanala yüklenemedi.Kanal sağlayıcısı dosyayı kabul etmedi: {why}
→ Dosya biçimini/boyutunu kontrol edip tekrar deneyin. Sorun sürerse request_id ile destek alın.
MESSAGE_NOT_FOUND404Mesaj bulunamadı: {id}.Bu kimlikle hesabınıza ait bir mesaj yok (ya da 90 günden eski).
→ Gönderim yanıtındaki "id" değerini (msg_…) kullanın.
WEBHOOK_NOT_CONFIGURED409Tanımlı aktif webhook adresi yok.Hesapta etkin bir webhook endpoint bulunmuyor.
→ Panel › Geliştirici › Webhook bölümünden adres ve olayları tanımlayın.
WEBHOOK_URL_INVALID422Webhook adresi kabul edilmedi: {why}Adres güvenlik kurallarına uymuyor.
→ https:// ile başlayan, internetten erişilebilir bir adres verin.
WEBHOOK_UNREACHABLE502Webhook adresine ulaşılamadı: {why}Test olayı gönderildi ancak sunucunuz 2xx dönmedi ya da yanıt vermedi.
→ Sunucunuzun POST isteklerini kabul ettiğinden, 10 sn içinde 2xx döndüğünden ve TLS sertifikasının geçerli olduğundan emin olun.
PROVIDER_REJECTED502Kanal sağlayıcısı mesajı reddetti: {title}{reason}
→ {fix}
PROVIDER_UNAVAILABLE503Kanal sağlayıcısı geçici olarak yanıt vermiyor.Meta/WhatsApp altyapısında geçici kesinti ya da yoğunluk var.
→ İsteği üstel bekleme ile (30 sn, 2 dk, 5 dk) tekrar deneyin; aynı Idempotency-Key ile tekrar güvenlidir.
NOT_FOUND404Uç bulunamadı: {method} {path}.Böyle bir API ucu yok ya da sürüm öneki eksik.
→ Uçlar /api/v1/… altındadır; doğru yol için dokümantasyona bakın.
METHOD_NOT_ALLOWED405{method} bu uçta desteklenmiyor.Uç farklı bir HTTP yöntemi bekliyor.
→ İzin verilen yöntemler: {allowed}.
INTERNAL_ERROR500Beklenmeyen bir sistem hatası oluştu.İstek işlenirken sunucu tarafında hata oluştu; ekibimize otomatik bildirildi.
→ İsteği tekrar deneyin; sorun sürerse request_id ile destek ekibine yazın.

Mesajlardaki {…} yer tutucuları gerçek değerlerle doldurulur (ör. {hours} → 24). PROVIDER_REJECTED yanıtında meta_error alanı sağlayıcının ham hatasını (kod + mesaj) da taşır.

Sürüm notları

v1 — 31.08.2026: İlk sürüm. Kanallar: WhatsApp, Instagram DM, Messenger, Web. Olaylar: message.received, message.status, template.status. OpenAPI 3.1: /developers/openapi.json.

Geriye dönük uyumluluk: /v1 altında alan kaldırılmaz; yeni alanlar eklenebilir — istemcinizi bilinmeyen alanlara toleranslı yazın.