API PLATFORMU

MMF Relay API dokümantasyonu

İş ortağı kendi panelinden MMF Relay'i kullanır: kişi ve rıza defterini okur, mesaj gönderir, olayının akıbetini görür, kendi müşterisi için çalışma alanı açar. Sözleşme HMAC imzalıdır, kimlik çerezle değil imzayla kurulur ve çalışma alanı isteğe hiç girmez — anahtarın kendisinden okunur.

Taban adreshttps://api.mmfrelay.com

Nasıl başlanır

Dört adım. Üçüncü adıma kadar hiçbir müşteriye mesaj gitmez; kurulumu canlı veriyle değil /v1/ping ile doğrularsınız.

  1. Anahtarı alın

    Çalışma alanının panelinde /ayarlar/api ekranından anahtar üretilir; ekran tenant.settings.manageyetkisi ister. Anahtar üretilirken hangi kapsamları taşıyacağı seçilir. Sır yalnız bir kez gösterilir — kasada şifreli durduğu için geri gösterilemez, kaybedilirse yalnız yenisi üretilir.

  2. İmzalı ilk isteği atın

    GET https://api.mmfrelay.com/v1/ping. Kapsam istemez, yalnız geçerli imza ister; yanlış imza API-1003, penceresi kaçmış zaman damgası API-1004 verir.

  3. Kapsamları yanıttan doğrulayın

    /v1/ping yanıtı tenant_id, partner ve anahtarın gerçekten taşıdığı scopes listesini döndürür. Eksik bir kapsam burada görünür; ilk gerçek istekte API-1005 olarak değil.

  4. Gerçek işi yapın

    Okuma uçlarıyla başlayın (/v1/templates, /v1/contacts), sonra gönderime geçin. Şablon mesajı POST /v1/messages, serbest metin POST /v1/conversations/{id}/messages.

Taban adres ve kimlik doğrulama

Taban adres https://api.mmfrelay.com. Her istek üç başlık taşır: anahtar kimliği satırı seçer, imza isteğin doğruluğunu kanıtlar. Çalışma alanı isteğe hiç girmez — anahtarın satırından okunur, dolayısıyla gövdedeki hiçbir değer hangi çalışma alanına yazılacağını seçemez.

Her istekte zorunlu üç başlık
BaşlıkNe taşır
X-MMF-KeyAnahtar kimliği (32 onaltılık hane). Satırı bu seçer; gizli değildir, tahmin edilemez olması yeterlidir.
X-MMF-TimestampUnix saniyesi. Sunucu saatinden ±300 saniyeden fazla sapan istek, imza doğru olsa bile reddedilir (API-1004).
X-MMF-Signaturesha256=<64 onaltılık hane>. Gizli anahtarla üretilen HMAC-SHA256 özeti; isteğin doğruluğunu kanıtlar.

İmza tabanı

zaman_damgası + "." + METOT + "." + yol + "." + ham_gövde

imza = "sha256=" + HMAC_SHA256(taban, gizli_anahtar). Metot büyük harftir. Yol sorgu dizesi olmadan yazılır ve sunucuda yeniden kurulur (sondaki eğik çizgi atılır); başlıktan gelen bir değere güvenilmez. Gövdesiz isteklerde son parça boş dizedir — taban zaten metot ve yolla ayrıştığı için bu güvenlidir: bir ucun imzası başka bir uçta, başka bir metotla veya başka bir gövdeyle yeniden oynatılamaz.

taban   = "1756400000.GET./v1/usage."
imza    = "sha256=" + HMAC_SHA256(taban, gizli_anahtar)
istek   = GET https://api.mmfrelay.com/v1/usage

En sık yapılan iki hata

Gövdeyi iki kez serileştirmeyin. İmzalanan dize ile gönderilen baytlar birebir aynı olmalı; ikinci bir json_encode alan sırasını veya kaçış biçimini değiştirir ve imza tutmaz.

Sorgu dizesini imzaya katmayın. /v1/contacts?limit=100 değil /v1/contacts. Sorgu parametreleri isteğin URL'sinde durur, imza tabanında durmaz.

Zaman penceresi

X-MMF-Timestamp Unix saniyesidir ve tolerans ±300 saniyedir. Pencere imzadan önce sınanır: eski bir isteğin imzası doğru olsa bile penceresi kapanmışsa tekrar oynatılamaz. Sunucunuzun saati kayıyorsa istekler API-1004 alır — NTP ile senkron tutun.

Gövde sınırı

Sözleşme sınırı 256 KiB; aşan istek API-2001 alır. Tek istisna medya ucudur (POST /v1/conversations/{id}/media): base64 dosya taşıdığı için tavanı 24 MiB'dır. Base64 yaklaşık %33 şişirdiği için pratik dosya tavanı ~16 MB olur.

Çalışan örnekler

İkisi de aynı iki kuralı gösterir: gövde bir kez üretilir ve yola sorgu dizesi girmez.

PHP

<?php
// Anahtar panelden alınır: /ayarlar/api → "Yeni anahtar". Sır YALNIZ bir kez
// gösterilir; kaybedilirse geri gösterilemez, yalnız yenisi üretilir.
$keyId  = getenv('MMF_KEY_ID');   // X-MMF-Key başlığına gider
$secret = getenv('MMF_SECRET');   // yalnız imza üretiminde kullanılır

function mmfRequest(string $method, string $path, ?array $payload = null): array
{
    global $keyId, $secret;

    // Gövde BİR KEZ üretilir: imzalanan dize ile gönderilen baytlar aynı olmalı.
    // Gövdesiz isteklerde (GET, DELETE, /v1/templates/sync) taban boş dizedir.
    $body = $payload === null ? '' : json_encode($payload, JSON_UNESCAPED_UNICODE);

    $timestamp = (string) time();
    // taban = zaman + "." + METOT + "." + yol + "." + ham_gövde
    // Yola sorgu dizesi GİRMEZ: "/v1/contacts?limit=100" değil "/v1/contacts".
    $base      = $timestamp . '.' . $method . '.' . $path . '.' . $body;
    $signature = 'sha256=' . hash_hmac('sha256', $base, $secret);

    $ch = curl_init('https://api.mmfrelay.com' . $path);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_POSTFIELDS     => $body,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'X-MMF-Key: ' . $keyId,
            'X-MMF-Timestamp: ' . $timestamp,
            'X-MMF-Signature: ' . $signature,
        ],
    ]);
    $raw    = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    return ['status' => $status, 'body' => json_decode($raw, true)];
}

// 1) Kurulum doğrulaması: kapsam eksiği üretime çıkmadan burada görünür.
$ping = mmfRequest('GET', '/v1/ping');
// => ["ok" => true, "tenant_id" => "...", "partner" => "...", "scopes" => [...]]

// 2) Şablon mesajı. Yanıt 202 ve status "queued": mesaj kuyruğa alındı,
//    Meta'ya isteği kuyruk tüketicisi atar. 200 demek yanlış olurdu.
$sent = mmfRequest('POST', '/v1/messages', [
    'phone'             => '+905321234567',
    'template_name'     => 'fatura_bildirimi',
    'template_language' => 'tr',
    'variables'         => [
        ['key' => 'musteri_adi', 'target' => 'body', 'value' => 'Ayşe Yılmaz'],
    ],
]);

if ($sent['status'] !== 202) {
    // Hata gövdesi her zaman { "error": { "code": "API-xxxx", "message": "..." } }
    error_log('MMF Relay: ' . $sent['body']['error']['code']);
}

Node.js

import { createHmac } from "node:crypto";

const BASE_URL = "https://api.mmfrelay.com";
const KEY_ID = process.env.MMF_KEY_ID;
const SECRET = process.env.MMF_SECRET;

/**
 * @param {"GET"|"POST"|"PUT"|"PATCH"|"DELETE"} method
 * @param {string} path  Sorgu dizesi HARİÇ yol, ör. "/v1/contacts"
 * @param {object} [payload]
 * @param {Record<string,string>} [query]
 */
async function mmfRequest(method, path, payload, query) {
  // Gövde bir kez üretilir; imzalanan dize ile gönderilen baytlar aynı olmalı.
  const body = payload === undefined ? "" : JSON.stringify(payload);
  const timestamp = Math.floor(Date.now() / 1000).toString();

  // taban = zaman + "." + METOT + "." + yol + "." + ham_gövde
  // Sorgu dizesi tabana GİRMEZ; sunucu yolu kendisi yeniden kurar.
  const signature = createHmac("sha256", SECRET)
    .update(`${timestamp}.${method}.${path}.${body}`)
    .digest("hex");

  const search = query ? `?${new URLSearchParams(query).toString()}` : "";
  const response = await fetch(`${BASE_URL}${path}${search}`, {
    method,
    headers: {
      "Content-Type": "application/json",
      "X-MMF-Key": KEY_ID,
      "X-MMF-Timestamp": timestamp,
      "X-MMF-Signature": `sha256=${signature}`,
    },
    body: body === "" ? undefined : body,
  });

  return { status: response.status, body: await response.json() };
}

// 1) Kurulum doğrulaması.
const ping = await mmfRequest("GET", "/v1/ping");
console.log(ping.body.scopes); // anahtarın gerçekten taşıdığı kapsamlar

// 2) Sorgu dizeli okuma: imza yalnız "/v1/contacts" üzerinden hesaplanır.
//    ?phone= tam eşleşmedir ve normalleştirme sunucudadır: +90…, 90…, 0…
//    ve çıplak biçim aynı kişiyi bulur; bozuk numara API-2003 döner.
const page = await mmfRequest("GET", "/v1/contacts", undefined, {
  phone: "05321234567",
  tag: "vip",
});
// { data: [...], next_cursor: "..." | null }

// 3) Şablon mesajı. Aynı işi tekrar denerken Idempotency-Key kullanın:
//    aynı anahtar + aynı gövde saklanan yanıtı döndürür, ikinci mesaj çıkmaz.
const sent = await mmfRequest("POST", "/v1/messages", {
  contact_id: page.body.data[0]?.contact_id,
  template_name: "fatura_bildirimi",
  template_language: "tr",
  variables: [{ key: "musteri_adi", target: "body", value: "Ayşe Yılmaz" }],
});

if (sent.status !== 202) {
  console.error(sent.body.error.code, sent.body.error.message);
}

Yetki kapsamları

API anahtarının kullanıcısı yoktur, dolayısıyla rolü de yoktur; yetkisi bir kapsam listesidir. Kapsam uç bazındadır, iş ortağı bazında değil: bir gün yalnız okuma yetkisi vermek istediğinizde gönderim yetkisini de vermek zorunda kalmayasınız diye. messages:send bilerek contacts:write'tan ayrıdır — kişi güncellemek ile müşteriye mesaj yollamak aynı riske sahip değildir.

Anahtar bir ucun istediği kapsamı taşımıyorsa istek API-1005 ile döner ve uç hiç çalışmaz.

Kapsam listesi — kaynağı app/modules/api/api-scopes.ts
KapsamNe açar
events:readOlay durumunu oku
usage:readKullanım ve kotayı oku
templates:readŞablonları oku
templates:writeŞablon oluştur ve düzenle
contacts:readKişileri ve rızaları oku
contacts:writeKişi, etiket ve rıza yaz
conversations:readKonuşmaları oku
conversations:writeKonuşma durumu ve not yaz
messages:sendMesaj gönder
campaigns:readToplu gönderimleri oku
campaigns:writeToplu gönderim oluştur ve başlat
automations:readOtomasyonları ve çalışmaları oku
automations:writeOtomasyon aç/kapat ve düzenle
connection:readBağlantı ve numara durumunu oku
tenants:provisionÇalışma alanı aç ve bağlama bağlantısı üret Yalnız MMF Relay yönetimi verir; panel seçicisinde görünmez.

Uçlar (44)

Yol eşleşmesi metottan bağımsız kaydedilir: yanlış metotla çağrılan bir uç 404 değil 405 alır ve yanıt Allow başlığı taşır. Böyle bir uç yoksa API-2004 döner.

Sağlık ve durum

Entegrasyonu kurarken ilk çağrılan uçlar. /v1/ping kapsam istemez: geçerli imza yeter ve yanıt anahtarın hangi çalışma alanına, hangi kapsamlarla bağlı olduğunu söyler.

MetotYolKapsamNe yapar
GET/v1/ping— (yok)Kimlik ve kapsam listesi; kurulum doğrulaması.
GET/v1/usageusage:readAylık kota, ek kota havuzu, kullanım ve tahmini maliyet. Rakam tahmindir: mesaj faturasını Meta doğrudan işletmeye keser. quota_alert alanı, gönderilebilecek toplam hakkın %80'i tükenince uyarı taşır.
GET/v1/connectionconnection:readNumara bağlı mı, hangi akışla bağlı, verim tavanı ve kalite derecesi ne. Mesajlaşma kademesi ham (messaging_tier) ve sayısal (messaging_tier_limit) döner; bilinmiyorsa ikisi de null.

Olaylar

Olay ucundan (ör. ParkHesap) gönderilmiş kayıtların akıbeti. Ham gövde geri dönmez: iş ortağı onu zaten kendisi gönderdi ve geri vermek gereksiz bir kişisel veri yolu açardı.

MetotYolKapsamNe yapar
GET/v1/eventsevents:readGönderilen olayların işlenme durumu. ?status= ile süzülür.
GET/v1/events/{event_id}events:readTek olayın durumu ve atlanmışsa gerekçesi.

Şablonlar

Dört yazma ucu da panelin şablon çekirdeğinden geçer: yerel doğrulayıcı Meta'ya istekten önce çalışır. Onaylı şablonda düzenleme hakkı 24 saatte 1, 30 günde 10'dur — yerelde reddedilen bir deneme bu hakkı yakmaz.

MetotYolKapsamNe yapar
GET/v1/templatestemplates:readBütün şablonlar; durum, değişken yuvaları ve gönderilebilirlik.
POST/v1/templatestemplates:writeŞablon oluşturur ve Meta onayına gönderir.
POST/v1/templates/synctemplates:writeMeta'daki durumları yerel aynaya çeker. Gövdesiz.
PATCH/v1/templates/{template_code}templates:writeŞablonu düzenler. name ve language gönderilemez; onaylı şablonda kategori kilitlidir.
DELETE/v1/templates/{template_code}templates:writeŞablonu siler (tek sürüm, bütün diller değil). Silinen ad 30 gün yeniden kullanılamaz.

Kişiler ve rıza

Kişi listesi e-posta taşımaz, tekil kişi taşır; rıza kanıtı (evidence) da yalnız tekil kişide döner. Toplu okuma kişisel verinin en kolay sızdığı yoldur.

MetotYolKapsamNe yapar
GET/v1/contactscontacts:readKişi listesi (imleçli). ?status=, ?tag=, ?consent=, ?phone= süzgeçli. ?phone= tam eşleşmedir ve dört yaygın biçimi de kabul eder; çözülemeyen numara API-2003 alır.
GET/v1/contacts/{contact_id}contacts:readKişi + rıza defteri + değiştirilemez rıza geçmişi.
POST/v1/contactscontacts:writeKişi oluşturur; kaynak integration olarak yazılır. Kayıtlı numarada 409 (API-3002) döner ve hata gövdesi çakışan kişinin contact_id değerini taşır.
PATCH/v1/contacts/{contact_id}contacts:writeKişiyi günceller. Alanın hiç gönderilmemesi «dokunma», null gönderilmesi «temizle» demektir.
PUT/v1/contacts/{contact_id}/consentcontacts:writeRıza yazar. granted her zaman kanıt ister; withdrawn üzerine yazılamaz (API-6007).
POST/v1/contacts/{contact_id}/tagscontacts:writeEtiket ekler ve tag_added otomasyon kurallarını uyandırır. Etiket normalleştirilir.
DELETE/v1/contacts/{contact_id}/tags/{tag}contacts:writeEtiket kaldırır. İdempotenttir: olmayan etiketi kaldırmak da başarıdır.

Gelen kutusu

Konuşma okuma, konuşma yönetimi ve pencere içi gönderim. Müşteriye ÇIKAN her şey messages:send ister; panelde kalan (not, okundu) conversations:write ile yeter.

MetotYolKapsamNe yapar
GET/v1/conversationsconversations:readKonuşma listesi ve hizmet penceresi durumu. Son mesajın önizlemesini taşımaz.
GET/v1/conversations/{conversation_id}/messagesconversations:readMesajlar, teslimat durumu ve medya üstverisi.
GET/v1/messages/{message_id}conversations:readTek mesaj, kimliğiyle. POST /v1/messages yanıtındaki message_id ile akıbet sorgulanır; gövde konuşma mesajları listesindeki elemanla birebir aynıdır.
PATCH/v1/conversations/{conversation_id}conversations:writeKonuşma durumu (open / pending / resolved) ve temsilci ataması.
POST/v1/conversations/{conversation_id}/messagesmessages:sendSerbest metin VEYA butonlu/listeli interaktif mesaj. Yalnız hizmet penceresi içinde.
POST/v1/conversations/{conversation_id}/mediamessages:sendMedya mesajı (base64). Gövde tavanı 24 MiB; Meta'ya yükleme kuyruktadır.
POST/v1/conversations/{conversation_id}/reactionsmessages:sendMesaja emoji tepkisi. Tepki kaldırma desteklenmez.
POST/v1/conversations/{conversation_id}/notesconversations:writeDahili not. Müşteriye gitmez, kuyruğa girmez, ücretlenmez; pencere kapalıyken de yazılır.
POST/v1/conversations/{conversation_id}/readconversations:writeOkunmamış sayacını sıfırlar ve okundu bildirimini en iyi çabayla dener. Gövdesiz.

Şablon mesajı

Hizmet penceresinden bağımsız tek gönderim yolu; dayanağı pencere değil rıza defteridir. Yanıt 202 ve status: queued taşır, çünkü Meta'ya isteği yalnız kuyruk tüketicisi atar.

MetotYolKapsamNe yapar
POST/v1/messagesmessages:sendOnaylı şablon mesajını kuyruğa bırakır. Idempotency-Key yalnız burada desteklenir.

Otomasyonlar

Kural yazımı tam sözleşme ister: tetikleyici, koşullar, bekleme süresi ve bütün değişken yuvalarını kapatan bağlamalar. «Olay → şablon adı» kısayolu yoktur — şablon adı tek başına mesaj üretmez.

MetotYolKapsamNe yapar
GET/v1/automationsautomations:readKurallar; tetikleyici, koşul ve bağlamalarıyla.
GET/v1/automations/{automation_id}/runsautomations:readÇalışma günlüğü ve atlama gerekçesi. Kişi adı ve telefon taşımaz, contact_id taşır.
POST/v1/automationsautomations:writeKural kurar. Kural draft doğar ve hiç değerlendirilmez; açmak ayrı bir adımdır.
PUT/v1/automations/{automation_id}automations:writeKural içeriğini bütünüyle değiştirir ve durumu korur; kuralı gizlice açmaz.
PATCH/v1/automations/{automation_id}automations:writeKuralı açar veya duraklatır. Gövde yalnız { active } taşır.
DELETE/v1/automations/{automation_id}automations:writeKuralı ve çalışma günlüğünü siler.

Kampanyalar

Toplu gönderim. Alıcılar başlatma anında dondurulur: gönderim sırasında kitle yeniden hesaplanmaz, çünkü çağıran belirli bir sayıyı ve maliyeti onayladı. Medya başlıklı şablonla kampanya API'den kurulamaz (API-8004).

MetotYolKapsamNe yapar
GET/v1/campaignscampaigns:readToplu gönderim listesi ve sayaçları.
GET/v1/campaigns/{campaign_id}campaigns:readKampanya özeti + alıcı raporu (kendi imleciyle sayfalanır).
POST/v1/campaignscampaigns:writeKampanya taslağı kurar: kitle, şablon, değişken bağlamaları.
PATCH/v1/campaigns/{campaign_id}campaigns:writeTaslağı düzenler. Başlatılmış kampanya kilitlidir (API-8007).
DELETE/v1/campaigns/{campaign_id}campaigns:writeYalnız taslağı siler. Başlatılmış kampanya silinmez; raporu Meta maliyetinin kanıtıdır.
POST/v1/campaigns/{campaign_id}/launchcampaigns:writeKampanyayı hazırlamaya alır (status: preparing); alıcılar başlatma anının damgasıyla arka planda dondurulur, hazırlık bitince kampanya kendiliğinden koşar. İlerleme GET ile izlenir.
POST/v1/campaigns/{campaign_id}/pausecampaigns:writeDuraklatır; her dağıtım turunun başında etkir.
POST/v1/campaigns/{campaign_id}/resumecampaigns:writeSürdürür ve dağıtımı yeniden kuyruğa bırakır.

Çalışma alanı açma (sağlayıcı)

Yalnız tenants:provision kapsamı taşıyan anahtarla çalışır ve bu kapsamı yalnız MMF Relay yönetimi verir; panelin kapsam seçicisinde hiç görünmez, panel yolundan istenirse sunucuda reddedilir (API-5007). Sağlayıcı zinciri düzdür: açılan alanın kendi anahtarı bu kapsamı taşımaz.

MetotYolKapsamNe yapar
POST/v1/tenantstenants:provisionÇalışma alanı açar; anahtarı (sır yalnız bu yanıtta) ve numara bağlama bağlantısını döndürür.
GET/v1/tenantstenants:provisionAçılan alanlar; paket ataması ve bağlantı durumlarıyla.
GET/v1/tenants/{tenant_id}/connect-linktenants:provisionBağlama bağlantısını yeniler. Yenisi öncekini iptal eder; eski bağlantı yeniden üretilemez.

Sayfalama, idempotency ve hız sınırı

Sayfalama — imleç, sayfa numarası değil

Sayfa numarası yoktur. Liste okunurken yeni kayıt yazılır ve sayfa numarası kayar: 2. sayfayı isteyen çağıran, araya giren bir kayıt yüzünden bir satırı iki kez veya hiç görmez. Bütün liste uçları aynı zarfı döndürür.

{
  "data": [ /* ... */ ],
  "next_cursor": "eyJrIjoiY29udGFjdHMiLCJ2IjoiMjAy…" | null
}
  • ?limit= varsayılan 50, tavan 200. Geçersiz değer hata değil varsayılandır ve tavan sessizce uygulanır: 10.000 satır isteyen bir çağıranı reddetmek yerine 200 vermek, isteğin yine de işine yaraması demektir.
  • ?cursor= bir önceki yanıtın next_cursor değeridir. İmleç base64url ve opaktır; ayrıştırmayın.
  • İmleç liste türünü de taşır: bir listenin imleciyle başka bir listeyi sayfalamak API-2007 verir. Sessizce yanlış sonuç dönmez.
  • next_cursor null ise liste bitmiştir.

Idempotency-Key

Yalnız POST /v1/messages destekler. Başlık 1–200 yazdırılabilir ASCII karakterdir. Aynı işi yeniden denerken kullanın: ağ zaman aşımından sonra atılan ikinci istek sessizce ikinci bir mesaj üretmesin diye.

DurumYanıt
Aynı anahtar + aynı gövde, ilk istek 202 ile tamamlanmışSaklanan yanıt, üstünde Idempotency-Replay: true başlığı
İlk istek bir hatayla dönmüş (4xx/5xx)Anahtar serbest bırakılır; yeniden deneme isteği yeniden çalıştırır
Aynı anahtar + aynı gövde, ilk istek hâlâ işleniyorAPI-3002
Aynı anahtar + farklı gövdeAPI-2006

Yalnız mesaj gerçekten üretilen sonuç (202) saklanır. Sözleşmenin tek amacı çift mesaj göndermemektir; hata yanıtında ortada gönderilmiş bir mesaj yoktur, dolayısıyla saklanacak bir şey de yoktur. API-6001 (rıza yok) veya API-4002 (kota dolu) alan bir istek anahtarı kilitlemez: rızayı yazdıktan ya da kota yükseltildikten sonra aynı anahtarla yeniden deneme mesajı gönderir. Kayıt 24 saat yaşar.

Hız sınırı

Sınır anahtar başınadır ve token bucket'tır.

TürHızPatlama
Okuma (GET)20 istek/sn40
Yazma (POST / PUT / PATCH / DELETE)5 istek/sn10

Her yanıt X-RateLimit-Remaining ve X-RateLimit-Reset (epoch saniye) taşır. Sınır aşıldığında API-4001 döner ve yanıt ayrıca Retry-After taşır; o değer hiçbir zaman sıfır olamaz.

Sıra kimlik → hız sınırı → kapsam'dır: imzasız bir sel jeton yakamaz, kapsamı eksik bir anahtar da sınırdan muaf değildir. Bu sınır bizim sınırımızdır; Meta'nın numara başına tavanı (Cloud API 80 mesaj/sn, coexistence 20 mesaj/sn ve sabit, aynı kişiye ~0,17 mesaj/sn) ayrı bir şeydir ve gönderim yolunda ayrıca uygulanır.

Rıza asimetrisi

Panel withdrawn üzerine granted yazabilir; API yazamaz.

Bir kişinin rızası withdrawn iken API'den gelen granted yazılmaz ve istek API-6007 alır. Aynı görünen iki yolun farklı davranması, söylenmediğinde entegratörün saatlerini yakar; bu yüzden burada açıkça yazıyor.

Gerekçe: geri alma mutlaktır. Bir kişi «artık yazmayın» dedikten sonra o kaydı tersine çevirmek yeni ve daha güçlü bir kanıt gerektirir — kişinin kendisinin yeniden rıza vermesi. İşletmenin panelden bilinçli bir işlemi bu kanıtı taşıyabilir: ekranda oturan bir kullanıcı, adı denetim kaydında duran biri, kanıt alanını dolduran biri. Bir entegrasyon anahtarının rutin senkron yazması taşıyamaz: bir ERP kaydı «bu kişi tekrar rıza verdi» demez, «bu kişiyle ticari ilişkim var» der ve ikisi aynı şey değildir.

İkinci fark: API'den gelen her granted kanıt (evidence) taşımak zorundadır, kanal ne olursa olsun. Panel yalnız devralınan kanallarda ister. Kanıtsız rıza Meta denetiminde savunulamaz ve bir makinenin ürettiği rıza kaydının dayanağını sonradan bulmak imkânsızdır.

withdrawn yazmak her zaman serbesttir ve kanıt istemez: çıkış talebini evraka bağlamak, geri almayı geciktirmek olurdu.

Hizmet penceresi

WhatsApp'ta serbest metin yalnız müşterinin son mesajından itibaren 24 saat içinde gönderilebilir. MMF Relay'de bu iki gönderim yolunu birbirinden ayırır:

Pencere kapısıDayanağı
POST /v1/messagesYok. Onaylı şablon pencereden bağımsız gider.Rıza defteri (kategori bazında)
POST /v1/conversations/{id}/messages · /media · /reactionsVar. Pencere kapalıysa API-6010 ve mesaj satırı bile yazılmaz.Müşterinin son mesajından itibaren 24 saat
POST /v1/conversations/{id}/notes · /readYok. Müşteriye bir şey gitmiyor.

Pencere kapısı rıza kategorisine bakmaz: «DUR» yazan bir müşterinin kendi sorusuna yanıt engellenmez, ama tanıtım bu kanaldan meşrulaşmaz. Konuşmanın penceresi açık mı, GET /v1/conversations yanıtından okunur.

Gönderim uçlarının hepsi 202 ve status: "queued" döndürür. 200 demek mesajın Meta'ya teslim edildiğini söylemek olurdu ve o an henüz doğru değildir: Meta'ya isteği yalnız kuyruk tüketicisi atar. Teslimat durumu GET /v1/conversations/{id}/messages üzerinden okunur.

Teslimat bildirimi (giden webhook)

Mesajın durumu değiştiğinde biz size imzalı bir POST göndeririz; durumu sormanız gerekmez. Uç çalışma alanı başına bir tanedir ve /ayarlar/api ekranından kurulur. Kaydedildiği anda bir imza anahtarı üretilir ve yalnız o an gösterilir.

Sözleşme, sizin bize gönderdiğiniz olay ucunun aynasıdır: aynı başlık adları, aynı imza tabanı. Gönderirken yazdığınız imza kodunu alırken doğrulama için yeniden kullanabilirsiniz.

BaşlıkNe taşır
X-MMF-Eventmessage.status veya webhook.test
X-MMF-TimestampUnix epoch saniye (tam sayı, UTC)
X-MMF-Signaturesha256= + HMAC-SHA256 özeti
X-MMF-DeliveryHer HTTP denemesi için benzersiz; yeniden denemede değişir
imza_tabanı = X-MMF-Timestamp + "." + ham_gövde
{
  "event_id": "evt_9f2c…",        // yeniden denemede AYNI kalır
  "event": "message.status",
  "occurred_at": "2026-09-15T08:14:02.000Z",
  "data": {
    "message_id": "0f1a…",        // POST /v1/messages yanıtındaki kimlik
    "conversation_id": "7c33…",
    "contact_id": "b81e…",
    "status": "delivered",        // sent | delivered | read | failed
    "error_code": null,           // yalnız failed: MSG-xxxx
    "meta_error_code": null,      // yalnız failed: Meta'nın sayısal kodu
    "meta_error_subcode": null
  }
}
  • Tekilleştirmeyi event_id ile yapın. Kimlik (çalışma alanı, mesaj, durum) üçlüsünden türetilir: aynı durum ikinci kez üretilse bile kimlik aynı çıkar. X-MMF-Delivery ile karıştırmayın — o her denemede değişir.
  • Hızlı ve 2xx dönün. Yanıtın gövdesi okunmaz. 408, 429 ve 5xx yeniden denenir (1 → 2 → 4 → 8 dakika, en fazla 5 deneme); diğer 4xx terminaldir. İstek 10 saniyede zaman aşımına uğrar.
  • Sıra garantisi yoktur. Meta'nın bildirimleri sırasız gelir (delivered, sent'ten önce ulaşabilir) ve biz de aynı sırayla iletiriz. Durumu yalnız ileri yönde işleyin.
  • wa_message_id gönderilmez. Meta'nın kimliği base64 gövdesinde alıcının telefon numarasını taşıyor ve günlüğünüze düşerdi. Eşleştirme message_id ile yapılır — gönderirken zaten size döndü.
  • accepted ve played bildirilmez. Birincisi bizim iç durumumuzdur (Meta isteği kabul etti) ve Meta'nın sent bildirimi saniyeler içinde gelir; ikincisi kapsam dışıdır.
  • Art arda 20 başarısız teslimatta uç kapatılır ve panelde gerekçesiyle görünür. Adres yeniden kaydedilince açılır ve sayaç sıfırlanır.

Hata kodları

Kod kararlıdır ve değişmez; metin değişebilir. Günlüğünüzde koda dallanın, metne değil. Her hata yanıtı aynı zarfı taşır:

{
  "error": {
    "code": "API-6002",
    "message": "Kişi bu kategoride rızasını geri almış; mesaj gönderilmedi."
  }
}

Biçim hatalarında mesajın sonuna hangi alanın bozuk olduğu eklenir ( (alan: template_language) ). Şablon yazma hataları ayrıca bir support_id taşır; o kimlikle bize yazarsanız sunucu günlüğündeki Graph kaydını buluruz.

1xxx · Kimlik ve yetki

Anahtar, imza, zaman penceresi ve kapsam. Biçimsiz bir anahtar ile hiç var olmayan bir anahtar aynı kodu alır: hangi anahtarların var olduğu dışarıya sızdırılmaz.

KodHTTPAnlamı
API-1001401API anahtarı bulunamadı veya biçimsiz.
API-1002401Bu API anahtarı artık geçerli değil.
API-1003401İmza doğrulanamadı.
API-1004401Zaman damgası kabul penceresinin dışında.
API-1005403Bu anahtar bu işlem için gereken yetkiyi taşımıyor.

2xxx · İstek biçimi ve medya sınırları

Bilinmeyen alan yok sayılır; tanınan alan biçimsizse istek reddedilir ve yanıt hangi alanın bozuk olduğunu söyler.

KodHTTPAnlamı
API-2001413Gövde 256 KiB sınırını aşıyor.
API-2002400Gövde JSON olarak ayrıştırılamadı.
API-2003400Gövde şemaya uymuyor.
API-2004404Böyle bir uç yok.
API-2005405Bu uç bu HTTP metodunu kabul etmiyor.
API-2006409Bu Idempotency-Key daha önce farklı bir gövdeyle kullanıldı.
API-2007400Sayfalama imleci geçersiz.
API-2008422WhatsApp bu dosya türünü kabul etmiyor.
API-2009413Dosya, WhatsApp'ın bu tür için izin verdiği boyutu aşıyor.

3xxx · Kaynak durumu

Kayıt bu çalışma alanında yok, ya da işlemi kabul edecek durumda değil.

KodHTTPAnlamı
API-3001404Kayıt bulunamadı.
API-3002409Kaynak bu işlemi kabul edecek durumda değil.
API-3003409Çalışma alanının WhatsApp bağlantısı yok. Numara bağlanmadan mesaj gönderilemez.

4xxx · Hız ve kota

İkisi de 429 döner ama farklı şeyler söyler: biri «çok hızlısınız», diğeri «bu ayın hakkı bitti».

KodHTTPAnlamı
API-4001429İstek sınırı aşıldı.
API-4002429Aylık kota ve ek kota havuzu tükendi.

5xxx · Anahtar yönetimi (panel)

Bu seri uca değil panele konuşur: anahtarı üreten yol paneldedir ve hatası kullanıcıya görünür. Destek kaydında API-5001 gören kişi hangi ekrana bakacağını bilir.

KodHTTPAnlamı
API-5001404Bu API anahtarı bulunamadı.
API-5002409Bu API anahtarı zaten iptal edilmiş.
API-5003400En az bir yetki kapsamı seçin.
API-5004400İş ortağı adı yalnız küçük harf, rakam ve alt çizgi içerebilir; harfle başlamalı ve 2-40 karakter olmalıdır.
API-5005400Etiket en fazla 120 karakter olabilir.
API-5006400Tanınmayan bir yetki kapsamı gönderildi.
API-5007403Bu yetki kapsamını yalnız MMF Relay yönetimi verebilir. Sağlayıcı olmak için bizimle iletişime geçin.
API-5008400Webhook adresi kabul edilmedi. Adres https:// ile başlayan, gerçek bir alan adı taşıyan ve kimlik bilgisi içermeyen bir adres olmalı.
API-5009404Bu çalışma alanında kurulu bir webhook ucu yok. Önce bir adres kaydedin.

6xxx · Gönderim ve rıza kapıları

Ortak gönderim servisinin her atlama gerekçesine birebir karşılık gelir. «Mesaj gitmedi» tek bir koda indirgenmez; teşhis kabiliyeti tam olarak buradadır.

KodHTTPAnlamı
API-6001422Kişinin bu kategoride rıza kaydı yok; mesaj gönderilmedi.
API-6002422Kişi bu kategoride rızasını geri almış; mesaj gönderilmedi.
API-6003422Kişi aktif değil (arşivli veya engelli); mesaj gönderilmedi.
API-6004422Şablon onaylı değil; onaysız şablonla gönderim yapılmaz.
API-6005422Şablon standart kopyasından sapmış; panelden geri döndürülmeden gönderilmez.
API-6006422Şablonun beklediği bir değişken eksik veya boş.
API-6007409Rıza geri alınmış durumda; API üzerinden yeniden rıza yazılamaz.
API-6008422Bu şablon API üzerinden gönderilemez (medya başlığı veya desteklenmeyen bileşen).
API-6009409Bu çalışma alanına henüz paket atanmadı; paket atanana kadar mesaj gönderilemez. Paket ataması için MMF Relay yönetimiyle iletişime geçin.
API-601042224 saatlik hizmet penceresi kapalı; bu konuşmaya serbest mesaj gönderilemez. Onaylı şablonla göndermek için POST /v1/messages kullanın.
API-6011409Meta bu numaranın gönderimini kalite nedeniyle kısıtladı; şablon gönderimi duraklatıldı. Ne zaman açılacağı GET /v1/connection yanıtındaki send_restricted_until alanındadır.
API-6012429Meta'nın bu numara için 24 saatlik mesajlaşma kademesi (benzersiz alıcı tavanı) doldu. Pencere kayan 24 saattir; tavan GET /v1/connection yanıtındaki messaging_tier_limit alanındadır.

7xxx · Şablon yazma

Meta'nın kod 100 altındaki alt kodları asıl gerekçeyi taşır ve ayrı kodlarla döner. Bu yanıtlar ayrıca bir support_id taşır; Graph hatası her durumda aynı kimlikle sunucu günlüğüne yazılır.

KodHTTPAnlamı
API-7001409Bu ad ve dilde bir şablon zaten var. Silinen şablonun adı 30 gün boyunca yeniden kullanılamaz.
API-7002422Meta şablon içeriğini geçersiz buldu.
API-7003422Meta gövde metninin biçimini geçersiz buldu. Kalın/italik işaretlerinin çift olduğundan ve değişkenlerin metnin başında veya sonunda durmadığından emin olun.
API-7004422Meta başlık biçimini geçersiz buldu. Başlıkta biçimlendirme desteklenmez ve en fazla bir değişken kullanılabilir.
API-7005422Meta alt bilgi biçimini geçersiz buldu. Alt bilgide değişken ve biçim işareti kullanılamaz.
API-7006422Şablonda metne göre çok fazla değişken var. Değişken sayısını azaltın veya metni uzatın.
API-7007422Değişken metnin en başında veya en sonunda duramaz. Değişkenin önüne veya arkasına metin ekleyin.
API-7008422Bir alan Meta'nın karakter sınırını aşıyor.
API-7009409Meta bu şablonun düzenlenmesini reddetti; şablonda hiçbir değişiklik uygulanmadı. Ayrıntı için destek koduyla iletişime geçin.
API-7010409Onaylı şablonun kategorisi değiştirilemez; yalnız içerik ve geçerlilik süresi düzenlenebilir.
API-7011409Şablon bu durumda düzenlenemez; yalnız APPROVED, REJECTED veya PAUSED şablonlar düzenlenebilir.
API-7012422Meta bu içeriği politika gereği engelledi.
API-7013429Meta'nın istek sınırına ulaşıldı. Bir süre sonra tekrar deneyin.
API-7014409Meta erişim yetkisi geçersiz; WhatsApp bağlantısı panelden yenilenmeli.
API-7015503Meta geçici olarak erişilemiyor. Daha sonra tekrar deneyin.
API-7016502Meta şablon işlemi tamamlanamadı.
API-7017409Bu şablon API üzerinden düzenlenemeyen bir bileşen taşıyor (medya başlığı, carousel veya salt okunur buton).
API-7018409Meta bu işlem için gerekli izni vermiyor.

8xxx · Kampanya (80xx) ve otomasyon (81xx) yazma

Kapılarla kesişen gerekçeler kendi serilerinde kalır: onaysız şablon API-6004, eksik değişken API-6006, desteklenmeyen bileşen API-6008, paketsiz alan API-6009.

KodHTTPAnlamı
API-8001422Seçilen etiketlerle gönderilebilecek kimse bulunamadı.
API-8002422Hedef kitle tek kampanya için çok büyük. Etiketle daraltın veya kampanyayı bölün.
API-8003422Zamanlanan an geçmişte olamaz.
API-8004422Bu şablonun başlığı bir dosya istiyor; kampanya medyası API üzerinden yüklenemez. Medyalı kampanyayı panelden kurun.
API-8005409Kampanyanın başlık dosyası hazır değil. Kampanyayı silip panelden yeniden kurun.
API-8006502Başlık dosyası Meta'ya yüklenemedi. Bir süre sonra yeniden başlatmayı deneyin.
API-8007409Başlatılmış bir kampanyanın içeriği değiştirilemez veya silinemez.
API-8008409Bu kampanya duraklatılamaz veya sürdürülemez.
API-8101409Bu çalışma alanında daha fazla otomasyon kuralı oluşturulamaz (tavan 50).
API-8102409ParkHesap tetikleyicili kural için önce ParkHesap entegrasyonu bağlanmalı.
API-8103422Olay alanı kaynaklı bir değişken, bu olayın sözlüğünde olmayan bir alanı istiyor.
API-8104422Tam belge adresi buton değişkenine bağlanamaz; buton için belge erişim kodu alanını kullanın.

9xxx · Beklenmeyen hata

API-9001 (HTTP 500) bizim tarafımızdaki beklenmeyen bir hatadır. İç ayrıntı dışarı sızmaz ama yanıt bir request_id taşır ve aynı kimlik sunucu günlüğüne yazılır: destek kaydında o kimliği verin. Bu kod yeniden denenebilir.

Bilerek yapılmayanlar

Aşağıdakiler eksiklik değil karardır. Gerekçesiyle birlikte yazıyoruz ki entegratör olmayan bir şeyi aramakla vakit kaybetmesin.

  • Meta'nın uçları proxy'lenmedi. POST /v1/messages kendi gönderim yolunu kurmaz; ortak servisi çağırır. Bir gönderim noktası ortak yoldan çıkarsa dört şeyi birden kaybeder: kota kapısı çalışmaz, mesaj gelen kutusunda görünmez, ücretlendirme kaydı yazılmaz ve teslimat bildirimi eşleşecek bir kimlik bulamaz.
  • Numara bağlama sunucudan sunucuya yapılamaz. Meta'nın Embedded Signup akışı bir tarayıcı akışıdır: kurumun kendi Meta hesabıyla giriş yapması ve numarayı doğrulaması gerekir. Böyle bir onay API'den vekâleten verilemez. Çözüm tek kullanımlık bağlama bağlantısıdır (POST /v1/tenants yanıtındaki connect_link, 7 gün geçerli).
  • Şablon ve kampanya medyası API'den yüklenemez. Medya tanıtıcısı yalnız panelin yeniden başlatılabilir yükleme akışından alınabilir; kampanya medyası ise kampanyanın kendisine aittir (tek dosya, tek Meta kimliği) ve Meta kimliği 30 günde ölür. Medyalı kampanya panelden kurulur, başlatması API'den yapılabilir. Karusel şablonlar da kapsam dışıdır.
  • «Olay → şablon adı» kısayolu yok. API'den kural kurmak, panelin kurduğu her şeyi söylemektir: tetikleyici, koşullar, bekleme süresi ve bütün yuvaları kapatan bağlamalar. Şablon adı tek başına mesaj üretmez ve kuralın koşullarını düşürerek baypas etmenin yolu yoktur.
  • Tepki kaldırma ucu yok. Meta gönderim tarafı için bir «kaldır» gövdesi dokümante etmedi. Boş emoji sessizce yutulmaz, API-2003 alır.
  • Yazıyor göstergesi ucu yok. Gösterge bir mesaj değildir, saklanmaz ve 25 saniyede söner.