API dokümanı

Bayi

API dokümanı

Müşterilerinizi kendi yazılımınızdan yönetin: müşteri hesabı açıp kredi gönderin, WhatsApp numaralarını bağlayın, mesaj gönderin, gelen kutularını yanıtlayın ve olayları webhook ile alın.

Temel URL

https://podimessage.io/api/v1
API anahtarı oluşturun →

Her istekte gönderilen başlıklar

Authorization: Bearer podi_xxxxxxxxxxxxxxxx
Accept: application/json
Content-Type: application/json
# yalnızca müşteri adına işlem yaparken
X-Client-Id: 57

Genel bakış

Hesabınız ve müşterileriniz

Bayi hesabınızda kendi bakiyeniz ve API anahtarınız bulunur. Hizmet verdiğiniz her işletme bunun altında bir müşteri hesabıdır; kendi bakiyesi, WhatsApp numarası, şablonları, kampanyaları ve gelen kutusu vardır. Müşteriye panel girişi verebilirsiniz ama gerekmez: hesabı tamamen API üzerinden yönetebilirsiniz.

Kredi ve fiyatlar

API’deki her tutar, fiyat ve eşik tam sayı kredidir: hesap para biriminizin 1 birimi 1.000 kredidir. Gönderdiğiniz tutar, fiyat ve eşikler 10’un katı olmalıdır.

Yanıtlar birimi currency alanında belirtir: kendi çağrılarınızda PODI, X-Client-Id ile bir müşteri adına yaptığınız çağrılarda CREDIT. Böylece müşteriye ilettiğiniz hiçbir şeyde platformun adı geçmez.

  • Sizin fiyatınız, bir şablon mesajının size maliyetidir. GET /api/v1/reseller/balance bunu price olarak döndürür.
  • Müşteri fiyatı, müşterinin şablon mesajı başına ödediği tutardır. Müşteriyi açarken veya güncellerken price ile belirlersiniz; belirlemezseniz müşteri sizin fiyatınızı öder. Sizin fiyatınızın altına inemez.
  • Müşteri şablon mesajı gönderdiğinde bakiyesinden müşteri fiyatı düşülür, aradaki fark (müşteri fiyatı eksi sizin fiyatınız) aynı adımda sizin bakiyenize eklenir. Örnek: sizin fiyatınız 10, müşteri fiyatı 20 ise müşteri şablon mesajı başına 20 öder, siz 10 kazanırsınız.
  • WhatsApp’ın gönderim anında reddettiği mesaj ücretlendirilmez: ücret müşteriye iade edilir, sizin payınız da geri alınır. WhatsApp’ın kabul ettiği mesaj, daha sonra failed olarak bildirilse bile ücretli kalır. 24 saatlik yanıt süresi içindeki serbest metin yanıtları (gelen kutusu yanıtları) ücretlendirilmez.
  • credit ile bakiyenizden müşterinin bakiyesine kredi gönderir, withdraw ile geri alırsınız. Müşteri yalnızca kendi bakiyesindeki krediyi harcayabilir.

Kimlik doğrulama

Bayi API anahtarınızı her istekte Authorization: Bearer … başlığıyla gönderin. Anahtarları API & OTP sayfasında oluşturup iptal edebilirsiniz; yalnızca sunucunuzda tutun, tarayıcıya veya mobil uygulamaya koymayın.

Bir müşteri adına işlem yapmak için müşterinin id değerini X-Client-Id başlığıyla ekleyin. İstek tam olarak o müşteri olarak çalışır: onun bakiyesi, numarası, şablonları ve gelen kutusu.

Size ait olmayan ya da var olmayan bir müşteri id’si her uç noktada 404, askıya alınmış bir müşteri 403 döner. X-Client-Id olmadan anahtar kendi hesabınız olarak çalışır. /api/v1/reseller/… uç noktaları yalnızca bu başlık olmadan çalışır; kendi müşterinizin id’siyle çağrılırsa 403 döner.

İstek sınırları

  • Mesaj, şablon, kampanya ve gelen kutusu uç noktaları: hesap başına dakikada 60 istek.
  • /api/v1/reseller/… uç noktaları: dakikada 120 istek.
  • Webhook test istekleri: dakikada 5.

X-Client-Id ile yapılan çağrılar o müşterinin kendi sınırından düşer; böylece yoğun bir müşteri diğerlerini yavaşlatmaz. Kendi çağrılarınız tek bir sayaçtan düşer; /api/v1/reseller/… uç noktalarının ayrı sayacı vardır. Yanıtlarda X-RateLimit-Limit ve X-RateLimit-Remaining başlıkları bulunur. Sınır aşılırsa saniye cinsinden Retry-After başlığıyla 429 alırsınız.

Biçimler

  • Content-Type: application/json ve Accept: application/json ile JSON gönderin.
  • Zamanlar saat farkıyla birlikte ISO 8601 biçimindedir, örneğin 2026-11-02T10:15:00+03:00. Sorgu dizesinde + işaretini %2B olarak kodlayın.
  • Telefon numaraları ülke koduyla birlikte, + ve baştaki 0 olmadan yalnızca rakamdır: 905321234567.
  • Listeler per_page (1–100, varsayılan 50) ve page alır; current_page, last_page ve total içeren meta döndürür. Sohbet mesajları ise id ile sayfalanır.

Hatalar

Hatalar message alanı olan JSON’dur; bazılarında makinenin okuyacağı bir code ve başka alanlar da bulunur. Metne değil, HTTP durum koduna ve code alanına göre işlem yapın: mesajlar insanlar için yazılır ve değişebilir. Mesajlar her zaman platformun varsayılan dilindedir; Accept-Language başlığı dili değiştirmez. Gönderim uç noktalarında kontroller şu sırayla yapılır: girdi (422), aktif WhatsApp numarası (409), kredi (402).

200 Başarılı.
201 Oluşturuldu.
204 Silindi; gövde yok.
401 API anahtarı eksik veya tanınmıyor.
402 Kredi yetersiz. Gövdede balance ve biliniyorsa required bulunur.
403 Müşteri askıya alınmış ya da uç nokta için yanlış türde çağrı (örneğin bayi uç noktasında kendi müşterinizin X-Client-Id değeri).
404 Bilinmeyen ya da başka bir hesaba ait id.
405 Yol doğru ama HTTP yöntemi yanlış; izin verilen yöntemler Allow başlığındadır.
409 Aktif WhatsApp numarası yok ya da aynı tekrar anahtarıyla gönderilen istek hâlâ işleniyor.
422 Geçersiz girdi ya da WhatsApp isteği reddetti (message ve code alanlarına bakın).
429 Çok fazla istek.
502 WhatsApp medya dosyasını vermedi.

Geçersiz girdi, hatalı alanları errors altında listeleyen 422 döndürür:

# HTTP 422
{
  "message": "amount 10 değerinin katı olmalıdır.",
  "errors": {
    "amount": [
      "amount 10 değerinin katı olmalıdır."
    ]
  }
}

Güvenli tekrar (idempotency)

Ağ hataları olur. Aşağıdaki uç noktalarda her işlemle birlikte benzersiz bir anahtar gönderin: aynı anahtarla 24 saat içinde tekrarlanan istek, işi ikinci kez yapmak yerine ilk sonucu döndürür.

  • credit ve withdraw: Idempotency-Key başlığı (ya da gövdede idempotency_key). Anahtarlar müşteri ve yön bazında tutulur. Aynı anahtar başka bir tutarla gelirse code: idempotency_key_reused ile 422 döner.
  • messages/send: gövdede idempotency_key, hesap bazında. Yalnızca başarılı gönderimler hatırlanır; başarısız bir gönderimi aynı anahtarla yeniden deneyebilirsiniz. Aynı anahtar başka bir alıcı ya da şablonla gelirse code: idempotency_key_reused ile 422 döner.
  • conversations/{id}/reply: Idempotency-Key başlığı (veya gövdede idempotency_key), sohbet bazında. Tekrarlanan yanıtta Idempotent-Replayed: true başlığı bulunur; ilk istek hâlâ sürüyorsa code: in_progress ile 409 alırsınız.
  • otp/send ve campaigns anahtar almaz: yeniden denemeden önce sonucu kontrol edin.

Hızlı başlangıç

Yeni bir müşteriden çift yönlü bir sohbete altı çağrıda ulaşın. 5. adımın size ulaşması için önce webhook’unuzu kurun (Webhook’lar bölümüne bakın).

  1. 1

    Müşteri hesabı açın

    Dönen id değerini saklayın: bundan sonra onu X-Client-Id olarak gönderirsiniz. Kendi external_id değeriniz müşteriyi yeniden bulmanızı sağlar.

    Örnek istek — POST /api/v1/reseller/clients

    curl -X POST 'https://podimessage.io/api/v1/reseller/clients' \
      -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
      -H 'Accept: application/json' \
      -H 'Content-Type: application/json' \
      -d '{
        "name": "Pati Klinik",
        "external_id": "crm-1042",
        "price": 20
      }'
    <?php
    $ch = curl_init('https://podimessage.io/api/v1/reseller/clients');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
            'Accept: application/json',
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'name' => 'Pati Klinik',
            'external_id' => 'crm-1042',
            'price' => 20,
        ]),
    ]);
    $response = json_decode(curl_exec($ch), true);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    const res = await fetch('https://podimessage.io/api/v1/reseller/clients', {
      method: 'POST',
      headers: {
        Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        name: 'Pati Klinik',
        external_id: 'crm-1042',
        price: 20,
      }),
    });
    const data = await res.json();
  2. 2

    Kredi gönderin

    Bakiyenizden müşterinin bakiyesine 50.000 kredi gönderir.

    Örnek istek — POST /api/v1/reseller/clients/{client}/credit

    curl -X POST 'https://podimessage.io/api/v1/reseller/clients/57/credit' \
      -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
      -H 'Accept: application/json' \
      -H 'Content-Type: application/json' \
      -d '{
        "amount": 50000,
        "idempotency_key": "topup-crm-1042-0001"
      }'
    <?php
    $ch = curl_init('https://podimessage.io/api/v1/reseller/clients/57/credit');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
            'Accept: application/json',
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'amount' => 50000,
            'idempotency_key' => 'topup-crm-1042-0001',
        ]),
    ]);
    $response = json_decode(curl_exec($ch), true);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    const res = await fetch('https://podimessage.io/api/v1/reseller/clients/57/credit', {
      method: 'POST',
      headers: {
        Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        amount: 50000,
        idempotency_key: 'topup-crm-1042-0001',
      }),
    });
    const data = await res.json();
  3. 3

    WhatsApp numarasını bağlatın

    Dönen url linkini müşterinize gönderin ya da onun tarayıcısında açın. İşlem bitince client.connected webhook’u gelir ve müşteri status=connected eklenmiş olarak return_url adresinize döner.

    Örnek istek — POST /api/v1/reseller/clients/{client}/connect-link

    curl -X POST 'https://podimessage.io/api/v1/reseller/clients/57/connect-link' \
      -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
      -H 'Accept: application/json' \
      -H 'Content-Type: application/json' \
      -d '{
        "return_url": "https://crm.example.com/clinics/1042/whatsapp"
      }'
    <?php
    $ch = curl_init('https://podimessage.io/api/v1/reseller/clients/57/connect-link');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
            'Accept: application/json',
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'return_url' => 'https://crm.example.com/clinics/1042/whatsapp',
        ]),
    ]);
    $response = json_decode(curl_exec($ch), true);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    const res = await fetch('https://podimessage.io/api/v1/reseller/clients/57/connect-link', {
      method: 'POST',
      headers: {
        Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        return_url: 'https://crm.example.com/clinics/1042/whatsapp',
      }),
    });
    const data = await res.json();
  4. 4

    Onaylı bir şablon gönderin

    Sizin başlattığınız mesajlar Meta’nın onayladığı bir şablonla gönderilmelidir (şablonu POST /api/v1/templates ile oluşturabilirsiniz). Yanıt, ücreti ve müşterinin yeni bakiyesini gösterir.

    Örnek istek — POST /api/v1/messages/send

    curl -X POST 'https://podimessage.io/api/v1/messages/send' \
      -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
      -H 'X-Client-Id: 57' \
      -H 'Accept: application/json' \
      -H 'Content-Type: application/json' \
      -d '{
        "to": "905321234567",
        "template": "appointment_reminder",
        "language": "tr",
        "params": [
          "Ayşe",
          "12.11.2026 14:30"
        ],
        "idempotency_key": "reminder-8812"
      }'
    <?php
    $ch = curl_init('https://podimessage.io/api/v1/messages/send');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
            'X-Client-Id: 57',
            'Accept: application/json',
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'to' => '905321234567',
            'template' => 'appointment_reminder',
            'language' => 'tr',
            'params' => ['Ayşe', '12.11.2026 14:30'],
            'idempotency_key' => 'reminder-8812',
        ]),
    ]);
    $response = json_decode(curl_exec($ch), true);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    const res = await fetch('https://podimessage.io/api/v1/messages/send', {
      method: 'POST',
      headers: {
        Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id': '57',
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        to: '905321234567',
        template: 'appointment_reminder',
        language: 'tr',
        params: ['Ayşe', '12.11.2026 14:30'],
        idempotency_key: 'reminder-8812',
      }),
    });
    const data = await res.json();
  5. 5

    Yanıtı webhook ile alın

    Kişi yanıt yazdığında webhook adresinize message.inbound gönderilir. Olay, müşteriyi (client_id, external_id) ve conversation_id değerini içerir.

    POST https://crm.example.com/webhooks/whatsapp
    X-Webhook-Event: message.inbound
    {
      "id": "9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d",
      "event": "message.inbound",
      "created_at": "2026-11-02T10:15:00+03:00",
      "data": {
        "client_id": 57,
        "external_id": "crm-1042",
        "conversation_id": 12,
        "message_id": 345,
        "phone_number_id": "106540352242922",
        "wa_id": "905321234567",
        "name": "Ayşe Yılmaz",
        "type": "text",
        "text": "Yarın 14:30 uygun mu?",
        "has_media": false,
        "timestamp": "2026-11-02T10:12:00+03:00"
      }
    }
  6. 6

    Yanıtlayın

    Müşterinin son mesajından sonraki 24 saat içinde serbest metinle yanıt verebilirsiniz. Bu yanıtlardan bakiye düşülmez; Meta ise her numarada ayda ilk 1.000 hizmet mesajından sonra kendi ücretini alır.

    Örnek istek — POST /api/v1/conversations/{conversation}/reply

    curl -X POST 'https://podimessage.io/api/v1/conversations/12/reply' \
      -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
      -H 'X-Client-Id: 57' \
      -H 'Idempotency-Key: reply-77f1c2' \
      -H 'Accept: application/json' \
      -H 'Content-Type: application/json' \
      -d '{
        "text": "Merhaba Ayşe, yarın 14:30 için randevunuzu oluşturduk."
      }'
    <?php
    $ch = curl_init('https://podimessage.io/api/v1/conversations/12/reply');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
            'X-Client-Id: 57',
            'Idempotency-Key: reply-77f1c2',
            'Accept: application/json',
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'text' => 'Merhaba Ayşe, yarın 14:30 için randevunuzu oluşturduk.',
        ]),
    ]);
    $response = json_decode(curl_exec($ch), true);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    const res = await fetch('https://podimessage.io/api/v1/conversations/12/reply', {
      method: 'POST',
      headers: {
        Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id': '57',
        'Idempotency-Key': 'reply-77f1c2',
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        text: 'Merhaba Ayşe, yarın 14:30 için randevunuzu oluşturduk.',
      }),
    });
    const data = await res.json();

Bayi uç noktaları

Müşterilerinizi ve bakiyenizi yönetin. Anahtarınızı X-Client-Id olmadan kullanın.

Müşteri nesnesi
id integer
Müşteri kimliği. X-Client-Id olarak gönderin.
external_id string|null
Müşteri için kendi kimliğiniz.
name string
Müşteri adı.
email string|null
Panel giriş e-postası; müşterinin panel girişi yoksa null.
panel_login boolean
Müşterinin panele giriş yapıp yapamayacağı.
active boolean
Müşteri askıdayken false.
balance integer
Müşterinin kredi cinsinden bakiyesi.
price integer
Müşterinin şablon mesajı başına ödediği kredi.
currency string
Tutarların birimi: sizin çağrılarınızda PODI.
whatsapp array
Bağlı numaralar; her biri phone_number_id, display_phone_number, verified_name ve status içerir.
created_at string
Hesabın açıldığı zaman.
GET /api/v1/reseller/balance Bakiyeniz, şablon mesajı başına fiyatınız ve müşteri sayınız.
Bayi anahtarı

Örnek istek

curl 'https://podimessage.io/api/v1/reseller/balance' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/reseller/balance');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'Accept: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/reseller/balance', {
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "balance": 250000,
  "price": 10,
  "currency": "PODI",
  "clients": 12
}
GET /api/v1/reseller/clients Müşterileriniz, eskiden yeniye sıralı.
Bayi anahtarı

Parametreler

external_id
string · sorgu
Yalnızca bu external_id değerine sahip müşteriyi döndürür (tam eşleşme).
per_page
integer · sorgu
1–100, varsayılan 50.
page
integer · sorgu
Sayfa numarası, varsayılan 1.

Örnek istek

curl 'https://podimessage.io/api/v1/reseller/clients?external_id=crm-1042' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/reseller/clients?external_id=crm-1042');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'Accept: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/reseller/clients?external_id=crm-1042', {
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "data": [
    {
      "id": 57,
      "external_id": "crm-1042",
      "name": "Pati Klinik",
      "email": null,
      "panel_login": false,
      "active": true,
      "balance": 50000,
      "price": 20,
      "currency": "PODI",
      "whatsapp": [
        {
          "phone_number_id": "106540352242922",
          "display_phone_number": "+90 216 555 01 42",
          "verified_name": "Pati Klinik",
          "status": "active"
        }
      ],
      "created_at": "2026-10-28T09:30:00+03:00"
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 1,
    "total": 1
  }
}
POST /api/v1/reseller/clients Müşteri hesabı açar. Hesap 0 krediyle başlar; credit ile kredi gönderin.
Bayi anahtarı

Parametreler

name
string · gövde zorunlu
En fazla 255 karakter. Numara bağlama sayfasında gösterilir.
external_id
string · gövde
Müşteri için kendi kimliğiniz; en fazla 100 karakter ve müşterileriniz arasında benzersiz. Müşteriyi bulmak ve webhook olaylarını eşleştirmek için kullanın.
email
string · gövde
Panel giriş e-postası, password ile birlikte gönderilir. Yalnızca API ile kullanılacak müşteride ikisini de göndermeyin. Başka bir hesapta kullanılıyor olmamalıdır.
password
string · gövde
Panel parolası, 8–255 karakter. email ile birlikte zorunludur.
price
integer · gövde
Müşterinin şablon mesajı başına ödeyeceği kredi: 10–1.000.000 arası, 10’un katı ve sizin fiyatınızın altında olmamalı. Göndermezseniz sizin fiyatınız uygulanır.

Örnek istek

curl -X POST 'https://podimessage.io/api/v1/reseller/clients' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Pati Klinik",
    "external_id": "crm-1042",
    "price": 20
  }'
<?php
$ch = curl_init('https://podimessage.io/api/v1/reseller/clients');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'name' => 'Pati Klinik',
        'external_id' => 'crm-1042',
        'price' => 20,
    ]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/reseller/clients', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Pati Klinik',
    external_id: 'crm-1042',
    price: 20,
  }),
});
const data = await res.json();

Yanıt · HTTP 201

{
  "id": 57,
  "external_id": "crm-1042",
  "name": "Pati Klinik",
  "email": null,
  "panel_login": false,
  "active": true,
  "balance": 0,
  "price": 20,
  "currency": "PODI",
  "whatsapp": [],
  "created_at": "2026-10-28T09:30:00+03:00"
}

Diğer yanıtlar

422 Fiyat, sizin fiyatınızın altında.
{
  "message": "Müşteri fiyatı sizin mesaj fiyatınızdan (10 Podi) düşük olamaz. En az 10 Podi girin ya da fiyat belirlemeyin; o zaman sizin fiyatınız uygulanır."
}
GET /api/v1/reseller/clients/{client} Tek bir müşteri, bağlı numaralarıyla birlikte.
Bayi anahtarı

Parametreler

client
integer · yol zorunlu
Müşterinin id değeri.

Örnek istek

curl 'https://podimessage.io/api/v1/reseller/clients/57' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/reseller/clients/57');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'Accept: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/reseller/clients/57', {
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "id": 57,
  "external_id": "crm-1042",
  "name": "Pati Klinik",
  "email": null,
  "panel_login": false,
  "active": true,
  "balance": 50000,
  "price": 20,
  "currency": "PODI",
  "whatsapp": [
    {
      "phone_number_id": "106540352242922",
      "display_phone_number": "+90 216 555 01 42",
      "verified_name": "Pati Klinik",
      "status": "active"
    }
  ],
  "created_at": "2026-10-28T09:30:00+03:00"
}

Diğer yanıtlar

404 Bu müşteri size ait değil.
{
  "message": "Müşteri hesabı bulunamadı."
}
PATCH /api/v1/reseller/clients/{client} Müşteriyi günceller. Yalnızca gönderdiğiniz alanlar değişir.
Bayi anahtarı

Parametreler

client
integer · yol zorunlu
Müşterinin id değeri.
name
string · gövde
Yeni ad, en fazla 255 karakter.
external_id
string|null · gövde
Yeni harici kimlik, müşterileriniz arasında benzersiz; null değeri kimliği temizler.
email
string · gövde
Panel giriş e-postası, password ile birlikte gönderilir. Yalnızca API ile kullanılacak müşteride ikisini de göndermeyin. Başka bir hesapta kullanılıyor olmamalıdır.
password
string · gövde
Panel parolası, 8–255 karakter. email ile birlikte zorunludur.
active
boolean · gövde
false müşteriyi askıya alır: X-Client-Id ile yapılan çağrılar 403 alır, panel girişi çalışmaz, numara bağlama linkleri açılmaz ve mesaj gönderilmez; gönderilmeyi bekleyen mesajlar ve askıdayken zamanı gelen kampanyalar başarısız olur ve hesap yeniden açılınca gönderilmez. true hesabı yeniden açar.
price
integer|null · gövde
Şablon mesajı başına yeni fiyat (kredi), aynı kurallarla; null sizin fiyatınıza döner.

Örnek istek

curl -X PATCH 'https://podimessage.io/api/v1/reseller/clients/57' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "price": 30,
    "active": true
  }'
<?php
$ch = curl_init('https://podimessage.io/api/v1/reseller/clients/57');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'price' => 30,
        'active' => true,
    ]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/reseller/clients/57', {
  method: 'PATCH',
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    price: 30,
    active: true,
  }),
});
const data = await res.json();

Yanıt · HTTP 200

{
  "id": 57,
  "external_id": "crm-1042",
  "name": "Pati Klinik",
  "email": null,
  "panel_login": false,
  "active": true,
  "balance": 50000,
  "price": 30,
  "currency": "PODI",
  "whatsapp": [
    {
      "phone_number_id": "106540352242922",
      "display_phone_number": "+90 216 555 01 42",
      "verified_name": "Pati Klinik",
      "status": "active"
    }
  ],
  "created_at": "2026-10-28T09:30:00+03:00"
}

Diğer yanıtlar

404 Bu müşteri size ait değil.
422 Fiyat, sizin fiyatınızın altında.
POST /api/v1/reseller/clients/{client}/credit Bakiyenizden müşterinin bakiyesine kredi gönderir.
Bayi anahtarı

Parametreler

client
integer · yol zorunlu
Müşterinin id değeri.
amount
integer · gövde zorunlu
Gönderilecek ya da geri alınacak kredi: 10–1.000.000.000 arası ve 10’un katı.
idempotency_key
string · gövde
İsteğe bağlı benzersiz anahtar, en fazla 128 karakter. 24 saat içinde tekrarlanırsa ilk sonuç döner ve kredi ikinci kez taşınmaz.

Örnek istek

curl -X POST 'https://podimessage.io/api/v1/reseller/clients/57/credit' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "amount": 50000,
    "idempotency_key": "topup-crm-1042-0001"
  }'
<?php
$ch = curl_init('https://podimessage.io/api/v1/reseller/clients/57/credit');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'amount' => 50000,
        'idempotency_key' => 'topup-crm-1042-0001',
    ]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/reseller/clients/57/credit', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    amount: 50000,
    idempotency_key: 'topup-crm-1042-0001',
  }),
});
const data = await res.json();

Yanıt · HTTP 200

{
  "status": "ok",
  "amount": 50000,
  "balance": 200000,
  "client": {
    "id": 57,
    "external_id": "crm-1042",
    "name": "Pati Klinik",
    "email": null,
    "panel_login": false,
    "active": true,
    "balance": 50000,
    "price": 20,
    "currency": "PODI",
    "whatsapp": [
      {
        "phone_number_id": "106540352242922",
        "display_phone_number": "+90 216 555 01 42",
        "verified_name": "Pati Klinik",
        "status": "active"
      }
    ],
    "created_at": "2026-10-28T09:30:00+03:00"
  }
}

Diğer yanıtlar

402 Bakiyeniz bu tutara yetmiyor; balance sizin bakiyenizdir.
{
  "message": "Bakiye yetersiz. Bakiye yükleyip isteği tekrarlayın. Yanıttaki balance alanı mevcut bakiyeyi, required alanı (varsa) gereken miktarı gösterir.",
  "balance": 20000,
  "currency": "PODI"
}
404 Bu müşteri size ait değil.
409 Aynı Idempotency-Key ile gönderilen istek hâlâ işleniyor.
{
  "message": "Bu idempotency anahtarıyla gönderilen istek hâlâ işleniyor. Biraz sonra tekrar deneyin.",
  "code": "in_progress"
}
422 Aynı anahtar daha önce farklı bir tutarla kullanıldı. Her aktarım için yeni bir anahtar kullanın.
{
  "message": "Bu idempotency anahtarı farklı bir istek için kullanıldı. Yeni bir anahtar kullanın.",
  "code": "idempotency_key_reused"
}
POST /api/v1/reseller/clients/{client}/withdraw Müşterinin kullanmadığı krediyi bakiyenize geri alır.
Bayi anahtarı

Parametreler

client
integer · yol zorunlu
Müşterinin id değeri.
amount
integer · gövde zorunlu
Gönderilecek ya da geri alınacak kredi: 10–1.000.000.000 arası ve 10’un katı.
idempotency_key
string · gövde
İsteğe bağlı benzersiz anahtar, en fazla 128 karakter. 24 saat içinde tekrarlanırsa ilk sonuç döner ve kredi ikinci kez taşınmaz.

Örnek istek

curl -X POST 'https://podimessage.io/api/v1/reseller/clients/57/withdraw' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "amount": 10000,
    "idempotency_key": "refund-crm-1042-0001"
  }'
<?php
$ch = curl_init('https://podimessage.io/api/v1/reseller/clients/57/withdraw');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'amount' => 10000,
        'idempotency_key' => 'refund-crm-1042-0001',
    ]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/reseller/clients/57/withdraw', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    amount: 10000,
    idempotency_key: 'refund-crm-1042-0001',
  }),
});
const data = await res.json();

Yanıt · HTTP 200

{
  "status": "ok",
  "amount": 10000,
  "balance": 210000,
  "client": {
    "id": 57,
    "external_id": "crm-1042",
    "name": "Pati Klinik",
    "email": null,
    "panel_login": false,
    "active": true,
    "balance": 40000,
    "price": 20,
    "currency": "PODI",
    "whatsapp": [
      {
        "phone_number_id": "106540352242922",
        "display_phone_number": "+90 216 555 01 42",
        "verified_name": "Pati Klinik",
        "status": "active"
      }
    ],
    "created_at": "2026-10-28T09:30:00+03:00"
  }
}

Diğer yanıtlar

402 Müşterinin bakiyesi amount değerinden az; balance müşterinin bakiyesidir.
{
  "message": "Bakiye yetersiz. Bakiye yükleyip isteği tekrarlayın. Yanıttaki balance alanı mevcut bakiyeyi, required alanı (varsa) gereken miktarı gösterir.",
  "balance": 4000,
  "currency": "PODI"
}
404 Bu müşteri size ait değil.
409 Aynı Idempotency-Key ile gönderilen istek hâlâ işleniyor.
{
  "message": "Bu idempotency anahtarıyla gönderilen istek hâlâ işleniyor. Biraz sonra tekrar deneyin.",
  "code": "in_progress"
}
422 Aynı anahtar daha önce farklı bir tutarla kullanıldı. Her aktarım için yeni bir anahtar kullanın.
{
  "message": "Bu idempotency anahtarı farklı bir istek için kullanıldı. Yeni bir anahtar kullanın.",
  "code": "idempotency_key_reused"
}
GET /api/v1/reseller/clients/{client}/usage Müşterinin bir dönemde gönderdiği şablon mesajları durumlarına göre ve bunlar için düşülen tutar. Varsayılan: bu ay.
Bayi anahtarı

by_status mesajları durumlarına göre sayar (sent, delivered, read, failed…); hiç mesaj yoksa boş bir listedir. charged iade edilen mesajları içermez.

Parametreler

client
integer · yol zorunlu
Müşterinin id değeri.
from
date · sorgu
İlk gün (YYYY-AA-GG). Varsayılan: bu ayın ilk günü.
to
date · sorgu
Son gün, dahil; from tarihinden önce olamaz. Varsayılan: bugün.

Örnek istek

curl 'https://podimessage.io/api/v1/reseller/clients/57/usage?from=2026-11-01&to=2026-11-30' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/reseller/clients/57/usage?from=2026-11-01&to=2026-11-30');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'Accept: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/reseller/clients/57/usage?from=2026-11-01&to=2026-11-30', {
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "client_id": 57,
  "from": "2026-11-01T00:00:00+03:00",
  "to": "2026-11-30T23:59:59+03:00",
  "messages": {
    "total": 120,
    "by_status": {
      "delivered": 84,
      "failed": 2,
      "read": 30,
      "sent": 4
    }
  },
  "charged": 2360,
  "currency": "PODI"
}

Diğer yanıtlar

404 Bu müşteri size ait değil.

Müşteri adına işlem

Bir müşteri hesabının yaptığı her şey: şablon ve doğrulama kodu gönderme, şablon yönetimi ve kampanyalar. Anahtarınızı X-Client-Id ile gönderin; tutarlar CREDIT olarak döner.

Kampanya nesnesi
id integer
Kampanya kimliği.
name string
Kampanya adı.
template string
Şablon adı.
language string
Şablon dili.
category string
Ücretlendirme kategorisi: marketing, utility veya authentication.
status string
scheduled, queued, processing, completed, failed veya cancelled.
scheduled_at string|null
Planlanan başlangıç; hemen gönderildiyse null.
total_recipients integer
Alıcı sayısı.
sent integer
WhatsApp’ın kabul ettiği mesajlar.
failed integer
Gönderilemeyen mesajlar.
delivered integer
Telefona teslim edilen mesajlar.
read integer
Okunan mesajlar.
started_at string|null
Gönderimin başladığı zaman.
completed_at string|null
Bütün alıcıların işlendiği zaman.
GET /api/v1/me Anahtarınızı ve X-Client-Id başlığınızı doğrulayın: isteğin hangi hesap olarak çalıştığını ve bakiyesini döndürür. Panel girişi olmayan müşterilerde email yer tutucu bir adrestir.
Anahtar + X-Client-Id

Örnek istek

curl 'https://podimessage.io/api/v1/me' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'X-Client-Id: 57' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/me');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id: 57',
        'Accept: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/me', {
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    'X-Client-Id': '57',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "id": 57,
  "name": "Pati Klinik",
  "email": "client-0b7d6c1e-8a51-4c43-9f0e-2f4d1c9a7e10@clients.invalid",
  "balance": 50000,
  "currency": "CREDIT"
}
POST /api/v1/messages/send Bir numaraya onaylı bir şablon gönderir. Mesaj hemen gönderilir ve ücreti müşteriden düşülür.
Anahtar + X-Client-Id

Son 24 saatte yazmış bir kişiyi yanıtlamak için bunun yerine gelen kutusundaki reply uç noktasını kullanın: bakiyeden ücret düşülmez.

Parametreler

to
string · gövde zorunlu
Alıcı numarası: ülke koduyla birlikte yalnızca rakam, en fazla 20 karakter.
template
string · gövde zorunlu
Müşterinin hesabındaki onaylı bir şablonun adı.
language
string · gövde
tr veya en_US gibi şablon dil kodu. Varsayılan en_US olduğundan Türkçe şablonlarda mutlaka belirtin.
params
array · gövde
Şablon gövdesindeki {{1}}, {{2}}… alanlarını sırasıyla dolduran en fazla 10 metin (her biri en fazla 1.000 karakter).
phone_number_id
string · gövde
Müşterinin bağlı numaralarından hangisinin kullanılacağı. Varsayılan: en son bağlanan aktif numara.
idempotency_key
string · gövde
İsteğe bağlı benzersiz anahtar, en fazla 128 karakter. 24 saat içinde tekrarlanırsa ilk başarılı sonuç döner ve hiçbir şey gönderilmez.

Örnek istek

curl -X POST 'https://podimessage.io/api/v1/messages/send' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'X-Client-Id: 57' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "to": "905321234567",
    "template": "appointment_reminder",
    "language": "tr",
    "params": [
      "Ayşe",
      "12.11.2026 14:30"
    ],
    "idempotency_key": "reminder-8812"
  }'
<?php
$ch = curl_init('https://podimessage.io/api/v1/messages/send');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id: 57',
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'to' => '905321234567',
        'template' => 'appointment_reminder',
        'language' => 'tr',
        'params' => ['Ayşe', '12.11.2026 14:30'],
        'idempotency_key' => 'reminder-8812',
    ]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/messages/send', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    'X-Client-Id': '57',
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    to: '905321234567',
    template: 'appointment_reminder',
    language: 'tr',
    params: ['Ayşe', '12.11.2026 14:30'],
    idempotency_key: 'reminder-8812',
  }),
});
const data = await res.json();

Yanıt · HTTP 200

{
  "status": "sent",
  "message_id": 1234,
  "wamid": "wamid.HBgMOTA1MzIxMjM0NTY3FQIAERgSQ0Y2…",
  "to": "905321234567",
  "cost": 20,
  "currency": "CREDIT",
  "balance": 49980
}

Diğer yanıtlar

402 Müşteri mesajın ücretini karşılayamıyor; hiçbir şey gönderilmedi.
{
  "message": "Bakiye yetersiz. Bakiye yükleyip isteği tekrarlayın. Yanıttaki balance alanı mevcut bakiyeyi, required alanı (varsa) gereken miktarı gösterir.",
  "required": 20,
  "balance": 0,
  "currency": "CREDIT"
}
409 Müşterinin aktif bir WhatsApp numarası yok (ya da belirttiğiniz phone_number_id aktif değil).
{
  "message": "Bağlı ve aktif bir WhatsApp numarası yok. Panelden WhatsApp bağlantısını kurun."
}
422 WhatsApp mesajı reddetti: code Meta’nın hata kodu, message_id başarısız kaydın kimliğidir. Ücret iade edilir.
{
  "message": "(#132001) Template name does not exist in the translation",
  "code": "132001",
  "message_id": 1234
}
POST /api/v1/otp/send Onaylı bir Kimlik Doğrulama (Authentication) şablonuyla doğrulama kodu gönderir. Kodu siz üretir ve siz kontrol edersiniz.
Anahtar + X-Client-Id

Parametreler

to
string · gövde zorunlu
Alıcı numarası: ülke koduyla birlikte yalnızca rakam, en fazla 20 karakter.
template
string · gövde zorunlu
Kimlik Doğrulama (Authentication) kategorisindeki onaylı bir şablonun adı.
code
string · gövde zorunlu
Ürettiğiniz kod, en fazla 30 karakter. Şablon gövdesine ve düğmesine yerleştirilir.
language
string · gövde
tr veya en_US gibi şablon dil kodu. Varsayılan en_US olduğundan Türkçe şablonlarda mutlaka belirtin.
include_button
boolean · gövde
Varsayılan true. Şablonda düğme yoksa false gönderin.
button_sub_type
string · gövde
url (varsayılan; tek dokunuşla doldurma ve kod kopyalama düğmelerinde çalışır) veya copy_code.
phone_number_id
string · gövde
Müşterinin bağlı numaralarından hangisinin kullanılacağı. Varsayılan: en son bağlanan aktif numara.

Örnek istek

curl -X POST 'https://podimessage.io/api/v1/otp/send' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'X-Client-Id: 57' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "to": "905321234567",
    "template": "login_code",
    "language": "tr",
    "code": "482913"
  }'
<?php
$ch = curl_init('https://podimessage.io/api/v1/otp/send');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id: 57',
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'to' => '905321234567',
        'template' => 'login_code',
        'language' => 'tr',
        'code' => '482913',
    ]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/otp/send', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    'X-Client-Id': '57',
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    to: '905321234567',
    template: 'login_code',
    language: 'tr',
    code: '482913',
  }),
});
const data = await res.json();

Yanıt · HTTP 200

{
  "status": "sent",
  "message_id": 1234,
  "wamid": "wamid.HBgMOTA1MzIxMjM0NTY3FQIAERgSQ0Y2…",
  "to": "905321234567",
  "cost": 20,
  "currency": "CREDIT",
  "balance": 49980
}

Diğer yanıtlar

402 Müşteri mesajın ücretini karşılayamıyor; hiçbir şey gönderilmedi.
409 Müşterinin aktif bir WhatsApp numarası yok (ya da belirttiğiniz phone_number_id aktif değil).
{
  "message": "Bağlı ve aktif bir WhatsApp numarası yok. Panelden WhatsApp bağlantısını kurun."
}
422 WhatsApp mesajı reddetti: code Meta’nın hata kodu, message_id başarısız kaydın kimliğidir. Ücret iade edilir.
GET /api/v1/templates Müşterinin şablonları, Meta’nın döndürdüğü hâliyle (en fazla 5 dakika önbelleklenir).
Anahtar + X-Client-Id

Parametreler

status
string · sorgu
ALL (varsayılan), APPROVED, PENDING, REJECTED, PAUSED veya DISABLED.
phone_number_id
string · sorgu
Müşterinin bağlı numaralarından hangisinin kullanılacağı. Varsayılan: en son bağlanan aktif numara.

Örnek istek

curl 'https://podimessage.io/api/v1/templates?status=APPROVED' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'X-Client-Id: 57' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/templates?status=APPROVED');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id: 57',
        'Accept: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/templates?status=APPROVED', {
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    'X-Client-Id': '57',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "data": [
    {
      "id": "1489022265523517",
      "name": "appointment_reminder",
      "language": "tr",
      "category": "UTILITY",
      "components": [
        {
          "type": "BODY",
          "text": "Merhaba {{1}}, {{2}} tarihli randevunuzu hatırlatırız.",
          "example": {
            "body_text": [
              [
                "Ayşe",
                "12.11.2026 14:30"
              ]
            ]
          }
        }
      ],
      "status": "APPROVED",
      "rejected_reason": "NONE"
    }
  ]
}

Diğer yanıtlar

409 Müşterinin aktif bir WhatsApp numarası yok (ya da belirttiğiniz phone_number_id aktif değil).
422 WhatsApp isteği reddetti; code Meta’nın hata kodudur.
POST /api/v1/templates Yeni bir şablonu Meta’nın incelemesine gönderir. Şablon PENDING olarak başlar; sonucu template.status webhook’u bildirir.
Anahtar + X-Client-Id

Parametreler

name
string · gövde zorunlu
Küçük harf, rakam ve _; en fazla 512 karakter.
language
string · gövde zorunlu
tr gibi dil kodu.
category
string · gövde zorunlu
MARKETING, UTILITY veya AUTHENTICATION.
components
array · gövde zorunlu
Meta’nın şablon biçiminde en fazla 10 bileşen (başlık, gövde, alt bilgi, düğmeler). Gövdedeki değişkenler için example gerekir.
phone_number_id
string · gövde
Müşterinin bağlı numaralarından hangisinin kullanılacağı. Varsayılan: en son bağlanan aktif numara.

Örnek istek

curl -X POST 'https://podimessage.io/api/v1/templates' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'X-Client-Id: 57' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "appointment_reminder",
    "language": "tr",
    "category": "UTILITY",
    "components": [
      {
        "type": "BODY",
        "text": "Merhaba {{1}}, {{2}} tarihli randevunuzu hatırlatırız.",
        "example": {
          "body_text": [
            [
              "Ayşe",
              "12.11.2026 14:30"
            ]
          ]
        }
      }
    ]
  }'
<?php
$ch = curl_init('https://podimessage.io/api/v1/templates');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id: 57',
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'name' => 'appointment_reminder',
        'language' => 'tr',
        'category' => 'UTILITY',
        'components' => [
            [
                'type' => 'BODY',
                'text' => 'Merhaba {{1}}, {{2}} tarihli randevunuzu hatırlatırız.',
                'example' => [
                    'body_text' => [
                        ['Ayşe', '12.11.2026 14:30'],
                    ],
                ],
            ],
        ],
    ]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/templates', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    'X-Client-Id': '57',
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'appointment_reminder',
    language: 'tr',
    category: 'UTILITY',
    components: [
      {
        type: 'BODY',
        text: 'Merhaba {{1}}, {{2}} tarihli randevunuzu hatırlatırız.',
        example: {
          body_text: [
            ['Ayşe', '12.11.2026 14:30'],
          ],
        },
      },
    ],
  }),
});
const data = await res.json();

Yanıt · HTTP 201

{
  "id": "1489022265523517",
  "status": "PENDING",
  "category": "UTILITY"
}

Diğer yanıtlar

409 Müşterinin aktif bir WhatsApp numarası yok (ya da belirttiğiniz phone_number_id aktif değil).
422 WhatsApp isteği reddetti; code Meta’nın hata kodudur.
DELETE /api/v1/templates/{name} Bir şablonu Meta’da siler.
Anahtar + X-Client-Id

Parametreler

name
string · yol zorunlu
Şablon adı. Bu ada sahip şablonun bütün dilleri silinir.
phone_number_id
string · sorgu
Müşterinin bağlı numaralarından hangisinin kullanılacağı. Varsayılan: en son bağlanan aktif numara.

Örnek istek

curl -X DELETE 'https://podimessage.io/api/v1/templates/appointment_reminder' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'X-Client-Id: 57' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/templates/appointment_reminder');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id: 57',
        'Accept: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/templates/appointment_reminder', {
  method: 'DELETE',
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    'X-Client-Id': '57',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "status": "deleted",
  "name": "appointment_reminder"
}

Diğer yanıtlar

409 Müşterinin aktif bir WhatsApp numarası yok (ya da belirttiğiniz phone_number_id aktif değil).
422 WhatsApp isteği reddetti; code Meta’nın hata kodudur.
POST /api/v1/campaigns Onaylı bir şablonu en fazla 5.000 alıcıya hemen ya da scheduled_at zamanında gönderir. Bakiye baştan kontrol edilir; her mesajın ücreti gönderildiğinde düşülür.
Anahtar + X-Client-Id

Hemen gönderilen kampanya queued → processing → completed, zamanlanmış kampanya scheduled → processing → completed sırasıyla ilerler; planlanan anda bakiye yetmezse failed olur. Zamanlanmış kampanyayı başlamadan DELETE /api/v1/campaigns/{campaign} ile iptal edebilirsiniz. Her mesaj ayrıca campaign_id içeren message.status webhook’ları gönderir.

Parametreler

name
string · gövde zorunlu
Kampanyaya verdiğiniz ad, en fazla 120 karakter.
template
string · gövde zorunlu
Onaylı bir şablonun adı.
language
string · gövde
Şablon dili. Belirtilmezse bu ada sahip ilk şablon kullanılır.
recipients
array · gövde zorunlu
1 ile 5.000 arasında alıcı.
recipients.*.phone
string · gövde zorunlu
Ülke koduyla numara, en fazla 20 karakter; boşluk ve işaretler silinir.
recipients.*.name
string · gövde
İsteğe bağlı, en fazla 120 karakter.
recipients.*.params
array · gövde
Bu alıcı için {{1}}, {{2}}… alanlarını dolduran en fazla 10 metin.
scheduled_at
date · gövde
İsteğe bağlı, ileri bir zaman (ISO 8601). Belirtilmezse gönderim hemen başlar.
phone_number_id
string · gövde
Müşterinin bağlı numaralarından hangisinin kullanılacağı. Varsayılan: en son bağlanan aktif numara.

Örnek istek

curl -X POST 'https://podimessage.io/api/v1/campaigns' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'X-Client-Id: 57' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Kasım aşı hatırlatmaları",
    "template": "appointment_reminder",
    "language": "tr",
    "recipients": [
      {
        "phone": "905321234567",
        "name": "Ayşe",
        "params": [
          "Ayşe",
          "12.11.2026 14:30"
        ]
      },
      {
        "phone": "905339876543",
        "name": "Mehmet",
        "params": [
          "Mehmet",
          "13.11.2026 10:00"
        ]
      }
    ],
    "scheduled_at": "2026-11-10T09:00:00+03:00"
  }'
<?php
$ch = curl_init('https://podimessage.io/api/v1/campaigns');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id: 57',
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'name' => 'Kasım aşı hatırlatmaları',
        'template' => 'appointment_reminder',
        'language' => 'tr',
        'recipients' => [
            [
                'phone' => '905321234567',
                'name' => 'Ayşe',
                'params' => ['Ayşe', '12.11.2026 14:30'],
            ],
            [
                'phone' => '905339876543',
                'name' => 'Mehmet',
                'params' => ['Mehmet', '13.11.2026 10:00'],
            ],
        ],
        'scheduled_at' => '2026-11-10T09:00:00+03:00',
    ]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/campaigns', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    'X-Client-Id': '57',
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Kasım aşı hatırlatmaları',
    template: 'appointment_reminder',
    language: 'tr',
    recipients: [
      {
        phone: '905321234567',
        name: 'Ayşe',
        params: ['Ayşe', '12.11.2026 14:30'],
      },
      {
        phone: '905339876543',
        name: 'Mehmet',
        params: ['Mehmet', '13.11.2026 10:00'],
      },
    ],
    scheduled_at: '2026-11-10T09:00:00+03:00',
  }),
});
const data = await res.json();

Yanıt · HTTP 201

{
  "id": 7,
  "name": "Kasım aşı hatırlatmaları",
  "template": "appointment_reminder",
  "language": "tr",
  "category": "utility",
  "status": "scheduled",
  "scheduled_at": "2026-11-10T09:00:00+03:00",
  "total_recipients": 2,
  "sent": 0,
  "failed": 0,
  "delivered": 0,
  "read": 0,
  "started_at": null,
  "completed_at": null
}

Diğer yanıtlar

402 Müşterinin bakiyesi bütün alıcılara yetmiyor.
{
  "message": "Bakiye yetersiz. Bakiye yükleyip isteği tekrarlayın. Yanıttaki balance alanı mevcut bakiyeyi, required alanı (varsa) gereken miktarı gösterir.",
  "required": 40,
  "balance": 20,
  "currency": "CREDIT"
}
409 Müşterinin aktif bir WhatsApp numarası yok (ya da belirttiğiniz phone_number_id aktif değil).
422 Bu ada (ve dile) sahip onaylı bir şablon yok.
{
  "message": "Bu adla (ve gönderildiyse bu dilde) onaylı bir şablon yok. template ve language değerlerini kontrol edin."
}
GET /api/v1/campaigns/{campaign} Bir kampanya ve ilerlemesi.
Anahtar + X-Client-Id

Parametreler

campaign
integer · yol zorunlu
Kampanyanın id değeri.

Örnek istek

curl 'https://podimessage.io/api/v1/campaigns/7' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'X-Client-Id: 57' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/campaigns/7');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id: 57',
        'Accept: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/campaigns/7', {
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    'X-Client-Id': '57',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "id": 7,
  "name": "Kasım aşı hatırlatmaları",
  "template": "appointment_reminder",
  "language": "tr",
  "category": "utility",
  "status": "scheduled",
  "scheduled_at": "2026-11-10T09:00:00+03:00",
  "total_recipients": 2,
  "sent": 0,
  "failed": 0,
  "delivered": 0,
  "read": 0,
  "started_at": null,
  "completed_at": null
}

Diğer yanıtlar

404 Bu hesapta böyle bir kampanya yok.
{
  "message": "Kampanya bulunamadı."
}
GET /api/v1/campaigns Müşterinin kampanyaları (panelden ve API’den açılanlar), en yeni başta.
Anahtar + X-Client-Id

Parametreler

status
string · sorgu
Yalnızca bu durumdaki kampanyalar: scheduled, queued, processing, completed, failed veya cancelled.
per_page
integer · sorgu
1–100, varsayılan 50.
page
integer · sorgu
Sayfa numarası, varsayılan 1.

Örnek istek

curl 'https://podimessage.io/api/v1/campaigns?status=scheduled&per_page=20' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'X-Client-Id: 57' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/campaigns?status=scheduled&per_page=20');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id: 57',
        'Accept: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/campaigns?status=scheduled&per_page=20', {
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    'X-Client-Id': '57',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "data": [
    {
      "id": 7,
      "name": "Kasım aşı hatırlatmaları",
      "template": "appointment_reminder",
      "language": "tr",
      "category": "utility",
      "status": "scheduled",
      "scheduled_at": "2026-11-10T09:00:00+03:00",
      "total_recipients": 2,
      "sent": 0,
      "failed": 0,
      "delivered": 0,
      "read": 0,
      "started_at": null,
      "completed_at": null
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 1,
    "total": 1
  }
}
DELETE /api/v1/campaigns/{campaign} Planlanmış bir kampanyayı başlamadan iptal eder: hiçbir mesaj gönderilmez ve bakiyeden bir şey düşülmez.
Anahtar + X-Client-Id

Yalnızca scheduled durumundaki kampanya iptal edilebilir. Planlanan zaman gelince kampanya processing olur ve artık durdurulamaz; iptali bundan önce yapın. İptal başarılı olduysa hiçbir mesaj gönderilmez.

Parametreler

campaign
integer · yol zorunlu
Kampanyanın id değeri.

Örnek istek

curl -X DELETE 'https://podimessage.io/api/v1/campaigns/7' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'X-Client-Id: 57' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/campaigns/7');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id: 57',
        'Accept: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/campaigns/7', {
  method: 'DELETE',
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    'X-Client-Id': '57',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "id": 7,
  "name": "Kasım aşı hatırlatmaları",
  "template": "appointment_reminder",
  "language": "tr",
  "category": "utility",
  "status": "cancelled",
  "scheduled_at": "2026-11-10T09:00:00+03:00",
  "total_recipients": 2,
  "sent": 0,
  "failed": 0,
  "delivered": 0,
  "read": 0,
  "started_at": null,
  "completed_at": null
}

Diğer yanıtlar

404 Bu hesapta böyle bir kampanya yok.
409 Kampanya artık planlanmış değil (başladı, bitti ya da zaten iptal edildi); status güncel durumunu gösterir. Başlamış bir kampanya durdurulamaz.
{
  "message": "Kampanya artık planlanmış durumda değil: başladı, bitti ya da zaten iptal edildi; iptal edilemez.",
  "code": "not_scheduled",
  "status": "processing"
}

Gelen kutusu

Her müşterinin WhatsApp sohbetlerini okuyun ve kendi yazılımınızdan yanıtlayın. Anahtarınızı X-Client-Id ile kullanın.

24 saatlik yanıt süresi

WhatsApp, işletmelerin serbest metni yalnızca kişinin son mesajından sonraki 24 saat içinde göndermesine izin verir. Yanıt süresinin dolup dolmadığını can_reply ve reply_window_expires_at gösterir. Süre dolduktan sonra reply, code: window_closed ile 422 döner: bunun yerine POST /api/v1/messages/send ile onaylı bir şablon gönderin (her şablon gibi ücretlendirilir). Kişi yeniden yazınca 24 saat yeniden başlar.

Güncel kalmak

message.inbound webhook’u yeni mesajları geldikleri anda bildirir. Yedek olarak GET /api/v1/conversations adresini updated_since ile yoklayın, yeni mesajları after_id ile alın.

API ile verilen yanıt sohbeti panelden devralmaz: sohbetin durumu ve atanan ekip üyesi aynı kalır, ancak kişi artık yanıt bekliyor sayılmaz. POST …/read okunmamış rozetini panel kullanıcıları için de temizler.

Sohbet nesnesi
id integer
Sohbet kimliği.
wa_id string
Kişinin WhatsApp numarası (rakam).
name string|null
Kişinin WhatsApp profil adı.
phone_number_id string|null
Sohbetin geçtiği, müşterinize ait numara. Aynı numaradan yazmak için messages/send çağrısına verin.
status string
Paneldeki gibi pool (Bekleyenler’de, henüz kimse üstlenmedi), assigned veya closed.
unread_count integer
Kişiden gelen, okunmamış mesaj sayısı.
last_message_preview string|null
Son mesajın kısa metni.
last_message_at string|null
Her iki yöndeki son mesajın zamanı.
last_inbound_at string|null
Kişinin son mesajının zamanı.
can_reply boolean
24 saatlik yanıt süresi dolmamışken ve numara aktifken true.
reply_window_expires_at string|null
Yanıt süresinin dolacağı zaman (dolduktan sonra geçmişte kalır); kişi hiç yazmadıysa null.
updated_at string|null
Son değişiklik; updated_since ile birlikte kullanın.
Mesaj nesnesi
id integer
Mesaj kimliği.
direction string
inbound (kişiden) veya outbound.
type string
text, image, video, audio, document, sticker, location, button, interactive…
text string|null
Metin veya medya açıklaması.
media object|null
null ya da type, mime, filename (belgelerde) ve download_path; bu yol API sunucusuna göredir ve aynı başlıklarla istenir.
status string|null
Giden mesajlarda sent, delivered, read veya failed; gelen mesajlarda null.
source string|null
Giden mesajın kaynağı: panel, api, auto_reply, template veya business_app (işletmenin telefonundaki WhatsApp Business uygulamasından gönderilen); gelen mesajlarda null.
created_at string
Mesajın kaydedildiği zaman.
GET /api/v1/conversations Müşterinin sohbetleri, en son yazışılan başta.
Anahtar + X-Client-Id

Parametreler

status
string · sorgu
pool (Bekleyenler’de, henüz kimse üstlenmedi), assigned, closed veya all (varsayılan).
updated_since
date · sorgu
Yalnızca bu zamanda veya sonrasında değişen sohbetler (ISO 8601). Yeni mesajlar, yanıtlar ve okundu işaretleri değişiklik sayılır.
wa_id
string · sorgu
Yalnızca bu numaradaki kişiyle olan sohbet; rakam dışındaki karakterler yok sayılır.
per_page
integer · sorgu
1–100, varsayılan 50.
page
integer · sorgu
Sayfa numarası, varsayılan 1.

Örnek istek

curl 'https://podimessage.io/api/v1/conversations?status=pool&updated_since=2026-11-02T10%3A00%3A00%2B03%3A00&per_page=20' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'X-Client-Id: 57' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/conversations?status=pool&updated_since=2026-11-02T10%3A00%3A00%2B03%3A00&per_page=20');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id: 57',
        'Accept: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/conversations?status=pool&updated_since=2026-11-02T10%3A00%3A00%2B03%3A00&per_page=20', {
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    'X-Client-Id': '57',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "data": [
    {
      "id": 12,
      "wa_id": "905321234567",
      "name": "Ayşe Yılmaz",
      "phone_number_id": "106540352242922",
      "status": "pool",
      "unread_count": 1,
      "last_message_preview": "Yarın 14:30 uygun mu?",
      "last_message_at": "2026-11-02T10:12:00+03:00",
      "last_inbound_at": "2026-11-02T10:12:00+03:00",
      "can_reply": true,
      "reply_window_expires_at": "2026-11-03T10:12:00+03:00",
      "updated_at": "2026-11-02T10:12:00+03:00"
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 1,
    "per_page": 20,
    "total": 1
  }
}
GET /api/v1/conversations/{conversation} Tek bir sohbet.
Anahtar + X-Client-Id

Parametreler

conversation
integer · yol zorunlu
Sohbetin id değeri.

Örnek istek

curl 'https://podimessage.io/api/v1/conversations/12' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'X-Client-Id: 57' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/conversations/12');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id: 57',
        'Accept: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/conversations/12', {
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    'X-Client-Id': '57',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "id": 12,
  "wa_id": "905321234567",
  "name": "Ayşe Yılmaz",
  "phone_number_id": "106540352242922",
  "status": "pool",
  "unread_count": 1,
  "last_message_preview": "Yarın 14:30 uygun mu?",
  "last_message_at": "2026-11-02T10:12:00+03:00",
  "last_inbound_at": "2026-11-02T10:12:00+03:00",
  "can_reply": true,
  "reply_window_expires_at": "2026-11-03T10:12:00+03:00",
  "updated_at": "2026-11-02T10:12:00+03:00"
}

Diğer yanıtlar

404 Bu hesapta böyle bir sohbet yok.
{
  "message": "Sohbet bulunamadı."
}
GET /api/v1/conversations/{conversation}/messages Bir sohbetin mesajları, yeniden eskiye sıralı.
Anahtar + X-Client-Id

Geriye doğru sayfalamak için has_more true olduğu sürece meta.next_before_id değerini before_id olarak gönderin.

Parametreler

conversation
integer · yol zorunlu
Sohbetin id değeri.
per_page
integer · sorgu
1–100, varsayılan 50.
before_id
integer · sorgu
Yalnızca bu id’den eski mesajlar.
after_id
integer · sorgu
Yalnızca bu id’den yeni mesajlar; yoklama için. Geriye sayfalarken de aynı değeri gönderin.

Örnek istek

curl 'https://podimessage.io/api/v1/conversations/12/messages?per_page=2' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'X-Client-Id: 57' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/conversations/12/messages?per_page=2');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id: 57',
        'Accept: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/conversations/12/messages?per_page=2', {
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    'X-Client-Id': '57',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "data": [
    {
      "id": 345,
      "direction": "inbound",
      "type": "text",
      "text": "Yarın 14:30 uygun mu?",
      "media": null,
      "status": null,
      "source": null,
      "created_at": "2026-11-02T10:12:00+03:00"
    },
    {
      "id": 344,
      "direction": "inbound",
      "type": "image",
      "text": "Aşı karnesi",
      "media": {
        "type": "image",
        "mime": "image/jpeg",
        "filename": null,
        "download_path": "/api/v1/messages/344/media"
      },
      "status": null,
      "source": null,
      "created_at": "2026-11-02T10:11:00+03:00"
    }
  ],
  "meta": {
    "per_page": 2,
    "has_more": true,
    "next_before_id": 344
  }
}

Diğer yanıtlar

404 Bu hesapta böyle bir sohbet yok.
{
  "message": "Sohbet bulunamadı."
}
POST /api/v1/conversations/{conversation}/reply 24 saatlik yanıt süresi içinde serbest metinle yanıt verir. Ücretlendirilmez.
Anahtar + X-Client-Id

Parametreler

conversation
integer · yol zorunlu
Sohbetin id değeri.
text
string · gövde zorunlu
Mesaj metni, 1–4.096 karakter.
Idempotency-Key
string · başlık
İsteğe bağlı benzersiz anahtar, en fazla 128 karakter (veya gövdede idempotency_key). 24 saat içinde tekrarlanırsa ilk yanıt döner ve hiçbir şey gönderilmez.

Örnek istek

curl -X POST 'https://podimessage.io/api/v1/conversations/12/reply' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'X-Client-Id: 57' \
  -H 'Idempotency-Key: reply-77f1c2' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "text": "Merhaba Ayşe, yarın 14:30 için randevunuzu oluşturduk."
  }'
<?php
$ch = curl_init('https://podimessage.io/api/v1/conversations/12/reply');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id: 57',
        'Idempotency-Key: reply-77f1c2',
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'text' => 'Merhaba Ayşe, yarın 14:30 için randevunuzu oluşturduk.',
    ]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/conversations/12/reply', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    'X-Client-Id': '57',
    'Idempotency-Key': 'reply-77f1c2',
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    text: 'Merhaba Ayşe, yarın 14:30 için randevunuzu oluşturduk.',
  }),
});
const data = await res.json();

Yanıt · HTTP 200

{
  "status": "sent",
  "message": {
    "id": 346,
    "direction": "outbound",
    "type": "text",
    "text": "Merhaba Ayşe, yarın 14:30 için randevunuzu oluşturduk.",
    "media": null,
    "status": "sent",
    "source": "api",
    "created_at": "2026-11-02T10:15:00+03:00"
  },
  "conversation": {
    "id": 12,
    "wa_id": "905321234567",
    "name": "Ayşe Yılmaz",
    "phone_number_id": "106540352242922",
    "status": "pool",
    "unread_count": 1,
    "last_message_preview": "Yarın 14:30 uygun mu?",
    "last_message_at": "2026-11-02T10:12:00+03:00",
    "last_inbound_at": "2026-11-02T10:12:00+03:00",
    "can_reply": true,
    "reply_window_expires_at": "2026-11-03T10:12:00+03:00",
    "updated_at": "2026-11-02T10:12:00+03:00"
  }
}

Diğer yanıtlar

422 24 saatlik yanıt süresi dolmuş (WhatsApp bildirdiğinde de); bunun yerine şablon gönderin.
{
  "message": "Müşteri son 24 saatte yazmadığı için 24 saatlik yanıt süresi doldu. Bunun yerine POST /api/v1/messages/send ile onaylı bir şablon gönderin.",
  "code": "window_closed",
  "template_endpoint": "/api/v1/messages/send"
}
409 Sohbetin numarası artık aktif değil.
{
  "message": "Bağlı ve aktif bir WhatsApp numarası yok. Panelden WhatsApp bağlantısını kurun.",
  "code": "no_account"
}
409 Aynı Idempotency-Key ile gönderilen istek hâlâ işleniyor.
{
  "message": "Bu idempotency anahtarıyla gönderilen istek hâlâ işleniyor. Biraz sonra tekrar deneyin.",
  "code": "in_progress"
}
422 WhatsApp yanıtı reddetti; retryable yeniden denemenin işe yarayıp yaramayacağını söyler. Hiçbir şey kaydedilmedi.
{
  "message": "(#131026) Message undeliverable",
  "code": "131026",
  "retryable": false
}
404 Bu hesapta böyle bir sohbet yok.
{
  "message": "Sohbet bulunamadı."
}
POST /api/v1/conversations/{conversation}/read Sohbeti okundu olarak işaretler (okunmamış sayısı 0); açık panel gelen kutularında da.
Anahtar + X-Client-Id

Parametreler

conversation
integer · yol zorunlu
Sohbetin id değeri.

Örnek istek

curl -X POST 'https://podimessage.io/api/v1/conversations/12/read' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'X-Client-Id: 57' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/conversations/12/read');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id: 57',
        'Accept: application/json',
    ],
    CURLOPT_POSTFIELDS => '',
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/conversations/12/read', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    'X-Client-Id': '57',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "id": 12,
  "wa_id": "905321234567",
  "name": "Ayşe Yılmaz",
  "phone_number_id": "106540352242922",
  "status": "pool",
  "unread_count": 0,
  "last_message_preview": "Yarın 14:30 uygun mu?",
  "last_message_at": "2026-11-02T10:12:00+03:00",
  "last_inbound_at": "2026-11-02T10:12:00+03:00",
  "can_reply": true,
  "reply_window_expires_at": "2026-11-03T10:12:00+03:00",
  "updated_at": "2026-11-02T10:12:00+03:00"
}

Diğer yanıtlar

404 Bu hesapta böyle bir sohbet yok.
{
  "message": "Sohbet bulunamadı."
}
GET /api/v1/messages/{message}/media Bir mesajdaki fotoğrafı, videoyu, sesli mesajı, belgeyi veya çıkartmayı WhatsApp’tan aktararak indirir.
Anahtar + X-Client-Id

Fotoğraf, video ve sesli mesajlar gerçek türleriyle satır içi gelir; diğer her şey (belgeler, SVG, …) application/octet-stream eki olarak gelir. WhatsApp medyayı yaklaşık 30 gün saklar.

Parametreler

message
integer · yol zorunlu
Mesajın id değeri (media.download_path içindeki gibi).

Örnek istek

curl 'https://podimessage.io/api/v1/messages/344/media' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'X-Client-Id: 57' \
  -H 'Accept: application/json' \
  -o media.jpg
<?php
$ch = curl_init('https://podimessage.io/api/v1/messages/344/media');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'X-Client-Id: 57',
        'Accept: application/json',
    ],
]);
file_put_contents('media.jpg', curl_exec($ch));
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
import { writeFile } from 'node:fs/promises';

const res = await fetch('https://podimessage.io/api/v1/messages/344/media', {
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    'X-Client-Id': '57',
    Accept: 'application/json',
  },
});
await writeFile('media.jpg', Buffer.from(await res.arrayBuffer()));

Yanıt · HTTP 200

Dosyanın kendisi, şu başlıklarla:

Content-Type: image/jpeg
Content-Disposition: inline; filename="media-344"
Content-Security-Policy: sandbox; default-src 'none'; img-src 'self' data:; media-src 'self'
X-Content-Type-Options: nosniff
Cache-Control: max-age=300, private

Diğer yanıtlar

404 Böyle bir mesaj yok ya da mesajda medya yok.
{
  "message": "Mesaj medyası bulunamadı."
}
502 WhatsApp dosyayı artık vermiyor; örneğin süresi dolmuş olabilir.
{
  "message": "Medya WhatsApp'tan alınamadı; süresi dolmuş olabilir.",
  "code": "100"
}

Webhook’lar

İmzalı olayları sizin belirlediğiniz tek bir HTTPS adresine POST ile göndeririz: gelen mesajlar, mesajların teslim durumları, şablon inceleme sonuçları, bağlanan numaralar ve düşük bakiyeler. Tek bir uç nokta hem kendi hesabınızı hem de bütün müşterilerinizi kapsar.

Kurulum

Uç noktanızı PUT /api/v1/reseller/webhook ile oluşturun. İlk çağrı imza anahtarını (secret, whsec_…) yalnızca bir kez döndürür: güvenli bir yerde saklayın. URL, 443 portunda, alan adlı ve herkese açık bir HTTPS adresi olmalıdır. Özel ve ayrılmış adresler reddedilir; adres her gönderimden önce yeniden kontrol edilir.

PUT /api/v1/reseller/webhook Uç noktanızı oluşturur (201, imza anahtarıyla) ya da değiştirir (200, imza anahtarı olmadan).
Bayi anahtarı

Sonraki çağrılar 200 döner ve imza anahtarını korur. Anahtarı ilk seferde saklayın: daha sonra yalnızca rotate-secret ile yenileyebilirsiniz.

Parametreler

url
string · gövde zorunlu
Uç noktanız, en fazla 2.048 karakter; her çağrıda zorunludur. 443 portunda HTTPS olmalı ve alan adı herkese açık bir adrese çıkmalıdır. Yeni bir URL hata sayacını sıfırlar.
client_low_balance_threshold
integer|null · gövde
Kredi cinsinden uyarı eşiği (10’un katı) ya da uyarıyı kapatmak için null. Göndermezseniz mevcut değer korunur.
reseller_low_balance_threshold
integer|null · gövde
Kredi cinsinden uyarı eşiği (10’un katı) ya da uyarıyı kapatmak için null. Göndermezseniz mevcut değer korunur.
active
boolean · gövde
false uç noktayı kapatır: kapalıyken oluşan olaylar gönderilmez ve sonradan da gönderilmez. true yeniden açar ve otomatik kapatmayı kaldırır.

Örnek istek

curl -X PUT 'https://podimessage.io/api/v1/reseller/webhook' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://crm.example.com/webhooks/whatsapp",
    "client_low_balance_threshold": 5000,
    "reseller_low_balance_threshold": 100000
  }'
<?php
$ch = curl_init('https://podimessage.io/api/v1/reseller/webhook');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'url' => 'https://crm.example.com/webhooks/whatsapp',
        'client_low_balance_threshold' => 5000,
        'reseller_low_balance_threshold' => 100000,
    ]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/reseller/webhook', {
  method: 'PUT',
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://crm.example.com/webhooks/whatsapp',
    client_low_balance_threshold: 5000,
    reseller_low_balance_threshold: 100000,
  }),
});
const data = await res.json();

Yanıt · HTTP 201

{
  "secret": "whsec_Lr4Tq8Wn2Hc6Vy0Bk3Mz7Pd1Xs5Fg9Ja2Ue6Ko4Ri8Nt0Qw3Yb",
  "url": "https://crm.example.com/webhooks/whatsapp",
  "active": true,
  "client_low_balance_threshold": 5000,
  "reseller_low_balance_threshold": 100000,
  "events": [
    "message.inbound",
    "message.status",
    "template.status",
    "client.connected",
    "client.balance_low",
    "reseller.balance_low",
    "ping"
  ],
  "consecutive_failures": 0,
  "last_success_at": null,
  "last_failure_at": null,
  "last_error": null,
  "disabled_at": null,
  "created_at": "2026-11-02T10:15:00+03:00",
  "updated_at": "2026-11-02T10:15:00+03:00"
}

Diğer yanıtlar

422 URL kabul edilmedi: HTTPS değil, IP adresi, 443 dışında bir port, çözümlenemeyen bir ad ya da özel bir adres.
{
  "message": "Webhook adresindeki alan adı özel ya da ayrılmış bir ağ adresine çıkıyor. İnternetten erişilebilen bir sunucunun adresini girin.",
  "errors": {
    "url": [
      "Webhook adresindeki alan adı özel ya da ayrılmış bir ağ adresine çıkıyor. İnternetten erişilebilen bir sunucunun adresini girin."
    ]
  }
}
429 Bir dakikada 10 URL değişikliğinden fazlası (reddedilen URL’ler de sayılır); Retry-After kadar bekleyin.
{
  "message": "Çok fazla değişiklik yapıldı. 42 saniye sonra tekrar deneyin."
}
GET /api/v1/reseller/webhook Uç noktanızın ayarları ve durumu. İmza anahtarı burada hiç gösterilmez.
Bayi anahtarı

Örnek istek

curl 'https://podimessage.io/api/v1/reseller/webhook' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/reseller/webhook');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'Accept: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/reseller/webhook', {
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "url": "https://crm.example.com/webhooks/whatsapp",
  "active": true,
  "client_low_balance_threshold": 5000,
  "reseller_low_balance_threshold": 100000,
  "events": [
    "message.inbound",
    "message.status",
    "template.status",
    "client.connected",
    "client.balance_low",
    "reseller.balance_low",
    "ping"
  ],
  "consecutive_failures": 0,
  "last_success_at": null,
  "last_failure_at": null,
  "last_error": null,
  "disabled_at": null,
  "created_at": "2026-11-02T10:15:00+03:00",
  "updated_at": "2026-11-02T10:15:00+03:00"
}

Diğer yanıtlar

404 Henüz bir webhook tanımlanmadı.
{
  "message": "Henüz bir webhook tanımlanmadı. PUT /api/v1/reseller/webhook ile oluşturabilirsiniz."
}
POST /api/v1/reseller/webhook/rotate-secret Yeni bir imza anahtarı üretir. Eski anahtar hemen geçersiz olur.
Bayi anahtarı

Örnek istek

curl -X POST 'https://podimessage.io/api/v1/reseller/webhook/rotate-secret' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/reseller/webhook/rotate-secret');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'Accept: application/json',
    ],
    CURLOPT_POSTFIELDS => '',
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/reseller/webhook/rotate-secret', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "secret": "whsec_Xc81LwQe2pTn6Rk0Vb4Ys9Hd3Jm7Fa5Gu1Zo8Ni2Ce6Ql4Wr",
  "url": "https://crm.example.com/webhooks/whatsapp",
  "active": true,
  "client_low_balance_threshold": 5000,
  "reseller_low_balance_threshold": 100000,
  "events": [
    "message.inbound",
    "message.status",
    "template.status",
    "client.connected",
    "client.balance_low",
    "reseller.balance_low",
    "ping"
  ],
  "consecutive_failures": 0,
  "last_success_at": null,
  "last_failure_at": null,
  "last_error": null,
  "disabled_at": null,
  "created_at": "2026-11-02T10:15:00+03:00",
  "updated_at": "2026-11-02T11:00:00+03:00"
}

Diğer yanıtlar

404 Henüz bir webhook tanımlanmadı.
{
  "message": "Henüz bir webhook tanımlanmadı. PUT /api/v1/reseller/webhook ile oluşturabilirsiniz."
}
429 Bir saatte 10 anahtar yenilemesinden fazlası; Retry-After kadar bekleyin.
{
  "message": "Çok fazla değişiklik yapıldı. 1800 saniye sonra tekrar deneyin."
}
POST /api/v1/reseller/webhook/test Hemen, bir kez ping gönderir ve sunucunuzun ne yanıt verdiğini gösterir. Uç nokta kapalıyken de çalışır ve hata sayacını etkilemez.
Bayi anahtarı

Örnek istek

curl -X POST 'https://podimessage.io/api/v1/reseller/webhook/test' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/reseller/webhook/test');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'Accept: application/json',
    ],
    CURLOPT_POSTFIELDS => '',
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/reseller/webhook/test', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "delivered": true,
  "response_code": 200,
  "error": null,
  "delivery": {
    "id": "9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d",
    "event": "ping",
    "status": "delivered",
    "attempts": 1,
    "response_code": 200,
    "last_error": null,
    "created_at": "2026-11-02T10:15:00+03:00",
    "last_attempt_at": "2026-11-02T10:15:00+03:00",
    "delivered_at": "2026-11-02T10:15:00+03:00",
    "payload": {
      "id": "9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d",
      "event": "ping",
      "created_at": "2026-11-02T10:15:00+03:00",
      "data": {
        "test": true
      }
    }
  }
}

Diğer yanıtlar

429 Bir dakikada 5 test isteğinden fazlası.
{
  "message": "Çok fazla test isteği gönderildi. 42 saniye sonra tekrar deneyin."
}
404 Henüz bir webhook tanımlanmadı.
{
  "message": "Henüz bir webhook tanımlanmadı. PUT /api/v1/reseller/webhook ile oluşturabilirsiniz."
}
GET /api/v1/reseller/webhook/deliveries Son gönderimler, yeniden eskiye sıralı ve gönderilen içerikle birlikte. 30 gün saklanır.
Bayi anahtarı

Parametreler

event
string · sorgu
Yalnızca bu olay, örneğin message.inbound.
status
string · sorgu
pending, delivered veya failed.
per_page
integer · sorgu
1–100, varsayılan 50.
page
integer · sorgu
Sayfa numarası, varsayılan 1.

Örnek istek

curl 'https://podimessage.io/api/v1/reseller/webhook/deliveries?status=failed&per_page=20' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/reseller/webhook/deliveries?status=failed&per_page=20');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'Accept: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/reseller/webhook/deliveries?status=failed&per_page=20', {
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    Accept: 'application/json',
  },
});
const data = await res.json();

Yanıt · HTTP 200

{
  "data": [
    {
      "id": "9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d",
      "event": "message.inbound",
      "status": "failed",
      "attempts": 6,
      "response_code": 503,
      "last_error": "http_503: Service Unavailable",
      "created_at": "2026-11-02T10:15:00+03:00",
      "last_attempt_at": "2026-11-02T13:41:00+03:00",
      "delivered_at": null,
      "payload": {
        "id": "9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d",
        "event": "message.inbound",
        "created_at": "2026-11-02T10:15:00+03:00",
        "data": {
          "client_id": 57,
          "external_id": "crm-1042",
          "conversation_id": 12,
          "message_id": 345,
          "phone_number_id": "106540352242922",
          "wa_id": "905321234567",
          "name": "Ayşe Yılmaz",
          "type": "text",
          "text": "Yarın 14:30 uygun mu?",
          "has_media": false,
          "timestamp": "2026-11-02T10:12:00+03:00"
        }
      }
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 1,
    "total": 1
  }
}
DELETE /api/v1/reseller/webhook Uç noktayı ve gönderim kaydını siler.
Bayi anahtarı

Örnek istek

curl -X DELETE 'https://podimessage.io/api/v1/reseller/webhook' \
  -H 'Authorization: Bearer podi_xxxxxxxxxxxxxxxx' \
  -H 'Accept: application/json'
<?php
$ch = curl_init('https://podimessage.io/api/v1/reseller/webhook');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer podi_xxxxxxxxxxxxxxxx',
        'Accept: application/json',
    ],
]);
curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
const res = await fetch('https://podimessage.io/api/v1/reseller/webhook', {
  method: 'DELETE',
  headers: {
    Authorization: 'Bearer podi_xxxxxxxxxxxxxxxx',
    Accept: 'application/json',
  },
});
console.log(res.status); // 204

Yanıt · HTTP 204

Gövde yok.

Diğer yanıtlar

404 Henüz bir webhook tanımlanmadı.
{
  "message": "Henüz bir webhook tanımlanmadı. PUT /api/v1/reseller/webhook ile oluşturabilirsiniz."
}

Zarf

Her olayın zarfı aynıdır. Bir müşteriyle ilgili olaylarda data, client_id (X-Client-Id olarak gönderin) ve external_id (sizin kimliğiniz, null olabilir) ile başlar. Kendi hesabınızla ilgili olaylarda bu iki alan bulunmaz.

{
  "id": "9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d",
  "event": "client.balance_low",
  "created_at": "2026-11-02T10:15:00+03:00",
  "data": {
    "client_id": 57,
    "external_id": "crm-1042",
    "balance": 4980,
    "threshold": 5000
  }
}

İstek başlıkları

Content-Type

application/json

Gövde JSON’dur.
User-Agent

Webhooks/1.0

Nötrdür; platform adı içermez.
X-Webhook-Id

9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d

Her olay için benzersizdir ve her denemede aynıdır (gövdedeki id ile aynı). Tekrarları ayıklamak için kullanın.
X-Webhook-Event

message.inbound

Olayın adı (gövdedeki event ile aynı).
X-Webhook-Timestamp

1793603700

Bu denemenin saniye cinsinden Unix zamanı. İmzanın parçasıdır.
X-Webhook-Signature

v1=6f1c0b7e4a…

v1= ve ardından imza anahtarınızla hesaplanan {timestamp}.{ham gövde} HMAC-SHA256 değeri (hex).
X-Webhook-Attempt

1

Kaçıncı deneme olduğu, 1 ile 6 arası.

Olaylar

message.inbound Bir kişi müşterinizin numarasına yazdı. text mesajın kendisi veya medya açıklamasıdır; diğer türler [location] gibi görünür. has_media true ise dosyayı GET /api/v1/messages/{message_id}/media ile indirin.

Örnek

{
  "id": "9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d",
  "event": "message.inbound",
  "created_at": "2026-11-02T10:15:00+03:00",
  "data": {
    "client_id": 57,
    "external_id": "crm-1042",
    "conversation_id": 12,
    "message_id": 345,
    "phone_number_id": "106540352242922",
    "wa_id": "905321234567",
    "name": "Ayşe Yılmaz",
    "type": "text",
    "text": "Yarın 14:30 uygun mu?",
    "has_media": false,
    "timestamp": "2026-11-02T10:12:00+03:00"
  }
}
message.status Bir mesajın teslim durumu sent, delivered, read veya failed oldu. kind: template şablon ya da kampanya mesajıdır: message_id, messages/send çağrısının döndürdüğü değerdir ve campaign_id ile gelir. kind: conversation gelen kutusu yanıtıdır ve conversation_id ile gelir. Başarısız mesajlarda error bulunur: Meta’nın sayısal code değeri ve title açıklaması ya da mesaj WhatsApp kabul etmeden önce başarısız olduysa (wamid null olur) bizim metin kodlarımızdan biri: account_unavailable (aktif numara yok), insufficient_balance, job_failed (birkaç denemeden sonra da gönderilemedi) veya header_media_required. Bu hata, messages/send yanıtında bildirilmiş olsa bile bir kez gönderilir. kind: webhook_event, müşterinin kendi webhook otomasyonundan gelen ve gönderilemeden başarısız olan gecikmeli bir mesajdır; message_id kuyruk kaydının kimliğidir. Başlayamayan planlı bir kampanya mesaj oluşturmaz, bu yüzden olay da göndermez; durumunu GET /api/v1/campaigns/{campaign} ile kontrol edin. Geç gelen daha düşük bir durum (okunduktan sonra teslim edildi gibi) gönderilmez.

Şablon mesajı teslim edildi

{
  "id": "9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d",
  "event": "message.status",
  "created_at": "2026-11-02T10:15:00+03:00",
  "data": {
    "client_id": 57,
    "external_id": "crm-1042",
    "message_id": 1234,
    "kind": "template",
    "wamid": "wamid.HBgMOTA1MzIxMjM0NTY3FQIAERgSQ0Y2…",
    "status": "delivered",
    "wa_id": "905321234567",
    "error": null,
    "campaign_id": null,
    "timestamp": "2026-11-02T10:16:00+03:00"
  }
}

Gelen kutusu yanıtı başarısız

{
  "id": "9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d",
  "event": "message.status",
  "created_at": "2026-11-02T10:15:00+03:00",
  "data": {
    "client_id": 57,
    "external_id": "crm-1042",
    "message_id": 346,
    "kind": "conversation",
    "wamid": "wamid.HBgMOTA1MzIxMjM0NTY3FQIAERgSRTk4…",
    "status": "failed",
    "wa_id": "905321234567",
    "error": {
      "code": 131047,
      "title": "Re-engagement message"
    },
    "conversation_id": 12,
    "timestamp": "2026-11-02T10:16:00+03:00"
  }
}

Şablon mesajı WhatsApp kabul etmeden başarısız oldu

{
  "id": "9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d",
  "event": "message.status",
  "created_at": "2026-11-02T10:15:00+03:00",
  "data": {
    "client_id": 57,
    "external_id": "crm-1042",
    "message_id": 1235,
    "kind": "template",
    "wamid": null,
    "status": "failed",
    "wa_id": "905321234567",
    "error": {
      "code": "insufficient_balance",
      "title": "Gönderilmedi: bakiye yetersiz."
    },
    "campaign_id": 7,
    "timestamp": "2026-11-02T10:17:00+03:00"
  }
}

Webhook otomasyonu mesajı başarısız

{
  "id": "9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d",
  "event": "message.status",
  "created_at": "2026-11-02T10:15:00+03:00",
  "data": {
    "client_id": 57,
    "external_id": "crm-1042",
    "message_id": 88,
    "kind": "webhook_event",
    "wamid": null,
    "status": "failed",
    "wa_id": "905321234567",
    "error": {
      "code": "account_unavailable",
      "title": "Mesaj gönderebilmek için önce aktif bir WhatsApp hesabı bağlamanız gerekir."
    },
    "timestamp": "2026-11-02T10:18:00+03:00"
  }
}
template.status Meta bir şablonu inceledi veya durumunu değiştirdi. status; approved, rejected, paused, disabled gibi bir değerdir. Meta bir gerekçe verdiyse reason bunu gösterir.

Örnek

{
  "id": "9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d",
  "event": "template.status",
  "created_at": "2026-11-02T10:15:00+03:00",
  "data": {
    "client_id": 57,
    "external_id": "crm-1042",
    "name": "appointment_reminder",
    "language": "tr",
    "status": "rejected",
    "reason": "INVALID_FORMAT"
  }
}
client.connected Müşteri numarasını bağlamayı tamamladı: numara bağlama linkiniz üzerinden ya da kendi panelinden.

Örnek

{
  "id": "9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d",
  "event": "client.connected",
  "created_at": "2026-11-02T10:15:00+03:00",
  "data": {
    "client_id": 57,
    "external_id": "crm-1042",
    "phone_number_id": "106540352242922",
    "display_phone_number": "+90 216 555 01 42",
    "verified_name": "Pati Klinik"
  }
}
client.balance_low Bir müşterinin bakiyesi client_low_balance_threshold eşiğinizin altına düştü. Her eşik geçişinde bir kez gönderilir; bakiye yeniden eşiğin üstüne çıkmadan tekrar gönderilmez.

Örnek

{
  "id": "9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d",
  "event": "client.balance_low",
  "created_at": "2026-11-02T10:15:00+03:00",
  "data": {
    "client_id": 57,
    "external_id": "crm-1042",
    "balance": 4980,
    "threshold": 5000
  }
}
reseller.balance_low Kendi bakiyeniz reseller_low_balance_threshold eşiğinizin altına düştü; aynı “geçiş başına bir kez” kuralı geçerlidir.

Örnek

{
  "id": "9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d",
  "event": "reseller.balance_low",
  "created_at": "2026-11-02T10:15:00+03:00",
  "data": {
    "balance": 99000,
    "threshold": 100000
  }
}
ping Yalnızca POST /api/v1/reseller/webhook/test ile gönderilir.

Örnek

{
  "id": "9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d",
  "event": "ping",
  "created_at": "2026-11-02T10:15:00+03:00",
  "data": {
    "test": true
  }
}

İmzayı doğrulama

  1. 1 İsteğin ham gövdesini ayrıştırmadan önce okuyun: yeniden kodlanmış JSON eşleşmez.
  2. 2 İmza anahtarınızla {X-Webhook-Timestamp}.{ham gövde} için HMAC-SHA256 (hex) hesaplayıp başına v1= ekleyin.
  3. 3 Sonucu X-Webhook-Signature ile sabit sürede karşılaştırın.
  4. 4 Tekrar saldırılarını önlemek için 5 dakikadan uzak zaman damgalarını reddedin.

Örnek

<?php
function verifyWebhook(string $secret, string $body, string $timestamp, string $signature): bool
{
    // Reject a missing timestamp or one more than 5 minutes away (replays).
    if (! ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) {
        return false;
    }

    $expected = 'v1='.hash_hmac('sha256', $timestamp.'.'.$body, $secret);

    return hash_equals($expected, $signature);
}

// The raw body: decode it only after the check.
$body = file_get_contents('php://input');

if (! verifyWebhook(
    getenv('WEBHOOK_SECRET'),
    $body,
    $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '',
    $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '',
)) {
    http_response_code(401);
    exit;
}

$event = json_decode($body, true);
// Skip $event['id'] if you have seen it, queue the work, answer at once.
http_response_code(200);
const http = require('node:http');
const crypto = require('node:crypto');

function verifyWebhook(secret, rawBody, timestamp, signature) {
  // Reject a missing timestamp or one more than 5 minutes away (replays).
  if (!/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    return false;
  }
  const expected = Buffer.from('v1=' + crypto
    .createHmac('sha256', secret)
    .update(timestamp + '.')
    .update(rawBody)
    .digest('hex'));
  const given = Buffer.from(signature);
  return expected.length === given.length && crypto.timingSafeEqual(expected, given);
}

http.createServer((req, res) => {
  const chunks = [];
  req.on('data', (chunk) => chunks.push(chunk));
  req.on('end', () => {
    const rawBody = Buffer.concat(chunks); // the raw bytes, not re-serialised JSON
    const ok = verifyWebhook(
      process.env.WEBHOOK_SECRET,
      rawBody,
      req.headers['x-webhook-timestamp'] || '',
      req.headers['x-webhook-signature'] || '',
    );
    res.writeHead(ok ? 200 : 401).end();
    if (ok) {
      const event = JSON.parse(rawBody.toString('utf8'));
      // Skip event.id if you have seen it, then handle the event.
    }
  });
}).listen(3000);

rotate-secret ile alınan yeni anahtar hemen geçerli olur. Geçiş sırasında reddettiğiniz gönderim daha sonra yeni anahtarla imzalanarak yeniden denenir.

Gönderim ve yeniden deneme

  • 10 saniye içinde herhangi bir 2xx durum koduyla yanıt verin; asıl işi yanıttan sonra yapın (örneğin bir kuyrukta).
  • Bunun dışındaki her şey (başka bir durum kodu, zaman aşımı, bağlantı hatası ya da yönlendirme; yönlendirmeler izlenmez) 1 dk, 5 dk, 20 dk, 1 sa ve 2 sa sonra yeniden denenir; toplam 6 deneme. Bundan sonra gönderim failed olarak işaretlenir.
  • Art arda 25 gönderim kesin olarak başarısız olursa ve 3 gündür hiçbir gönderim ulaşmadıysa uç nokta kapatılır (disabled_at). PUT ve "active": true ile yeniden açın.
  • Olaylar birden fazla kez ve sırasız gelebilir. Daha önce işlediğiniz X-Webhook-Id değerlerini atlayın ve data içindeki timestamp alanına göre sıralayın. message.status aynı wamid ve durum için tekrar edebilir; bunlara göre de ayıklayın.
  • Her gönderim 30 gün saklanır: GET …/deliveries denemeleri, yanıt kodlarını ve hataları gösterir.

Numara bağlama

Müşteri kendi WhatsApp Business numarasını Meta’nın resmi kayıt akışıyla bağlar. Siz bir link gönderirsiniz; müşterinin giriş yapması gerekmez.

  1. 1

    POST /api/v1/reseller/clients/{client}/connect-link ile bir link oluşturun; isterseniz yazılımınızdaki bir return_url adresini de verin.

  2. 2

    Müşteriyi url adresine yönlendirin: yeni bir sekmede açın veya tarayıcıyı yönlendirin (iframe içinde gösterilemez). Sayfa giriş gerektirmez; müşterinin dilinde, Marka sekmesinde belirlediğiniz ad ve logoyu (yoksa hesap adınızı), WhatsApp iş ortağınız olarak platformun adını ve müşterinin adını gösterir.

  3. 3

    Müşteri düğmeye tıklar ve Meta’nın resmi kayıt penceresini tamamlar: Facebook ile giriş yapar, işletmesini ve numarasını seçip onaylar.

  4. 4

    Numarayı kaydeder, linki kapatır ve size client.connected webhook’unu göndeririz. Numara, müşteri nesnesinde whatsapp altında da görünür.

  5. 5

    Sayfa müşteriyi, sorgu dizesine status=connected ekleyerek return_url adresinize geri gönderir.

Meta’nın penceresinde platformun Meta uygulamasının adı görünür. Bu pencere Meta’nın denetimindedir; bu ad gizlenemez veya değiştirilemez.
  • Bir link bir kez çalışır ve 7 gün sonra geçersiz olur; süresi dolmuş, kullanılmış ya da askıdaki bir müşteriye ait link markasız bir “link artık geçerli değil” sayfası (HTTP 410) gösterir. Her çağrı yeni bir link üretir; önceki linkler kullanılana ya da süreleri dolana kadar geçerli kalır.
  • status=connected değerini yalnızca ekranınız için bir ipucu olarak kullanın; herkes bu değeri yazabilir. Webhook’a veya GET /api/v1/reseller/clients/{client} yanıtına güvenin.
  • Askıya alınmış bir müşterinin linkleri, hesabı yeniden açana kadar açılmaz.