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.
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/balancebunupriceolarak döndürür. -
Müşteri fiyatı, müşterinin şablon mesajı başına ödediği tutardır. Müşteriyi açarken veya güncellerken
priceile 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
failedolarak bildirilse bile ücretli kalır. 24 saatlik yanıt süresi içindeki serbest metin yanıtları (gelen kutusu yanıtları) ücretlendirilmez. -
creditile bakiyenizden müşterinin bakiyesine kredi gönderir,withdrawile 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/jsonveAccept: application/jsonile 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%2Bolarak 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) vepagealır;current_page,last_pagevetotaliçerenmetadö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).
balance ve biliniyorsa required bulunur.
X-Client-Id değeri).
Allow başlığındadır.
message ve code alanlarına bakın).
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.
-
creditvewithdraw:Idempotency-Keybaşlığı (ya da gövdedeidempotency_key). Anahtarlar müşteri ve yön bazında tutulur. Aynı anahtar başka bir tutarla gelirsecode: idempotency_key_reusedile 422 döner. -
messages/send: gövdedeidempotency_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 gelirsecode: idempotency_key_reusedile 422 döner. -
conversations/{id}/reply:Idempotency-Keybaşlığı (veya gövdedeidempotency_key), sohbet bazında. Tekrarlanan yanıttaIdempotent-Replayed: truebaşlığı bulunur; ilk istek hâlâ sürüyorsacode: in_progressile 409 alırsınız. -
otp/sendvecampaignsanahtar 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
Müşteri hesabı açın
Dönen
iddeğerini saklayın: bundan sonra onuX-Client-Idolarak gönderirsiniz. Kendiexternal_iddeğeriniz müşteriyi yeniden bulmanızı sağlar.Örnek istek —
POST /api/v1/reseller/clientscurl -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
Kredi gönderin
Bakiyenizden müşterinin bakiyesine 50.000 kredi gönderir.
Örnek istek —
POST /api/v1/reseller/clients/{client}/creditcurl -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
WhatsApp numarasını bağlatın
Dönen
urllinkini müşterinize gönderin ya da onun tarayıcısında açın. İşlem bitinceclient.connectedwebhook’u gelir ve müşteristatus=connectedeklenmiş olarakreturn_urladresinize döner.Örnek istek —
POST /api/v1/reseller/clients/{client}/connect-linkcurl -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
Onaylı bir şablon gönderin
Sizin başlattığınız mesajlar Meta’nın onayladığı bir şablonla gönderilmelidir (şablonu
POST /api/v1/templatesile oluşturabilirsiniz). Yanıt, ücreti ve müşterinin yeni bakiyesini gösterir.Örnek istek —
POST /api/v1/messages/sendcurl -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
Yanıtı webhook ile alın
Kişi yanıt yazdığında webhook adresinize
message.inboundgönderilir. Olay, müşteriyi (client_id,external_id) veconversation_iddeğ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
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}/replycurl -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
X-Client-Id olarak gönderin.external_id
string|null
name
string
email
string|null
null.panel_login
boolean
active
boolean
false.balance
integer
price
integer
currency
string
PODI.whatsapp
array
phone_number_id, display_phone_number, verified_name ve status içerir.created_at
string
GET
/api/v1/reseller/balance
Bakiyeniz, şablon mesajı başına fiyatınız ve müşteri sayınız.
Ö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ı.
Parametreler
external_id
external_id değerine sahip müşteriyi döndürür (tam eşleşme).per_page
page
Ö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.
Parametreler
name
external_id
email
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
email ile birlikte zorunludur.price
Ö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
{
"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.
Parametreler
client
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
{
"message": "Müşteri hesabı bulunamadı."
}
PATCH
/api/v1/reseller/clients/{client}
Müşteriyi günceller. Yalnızca gönderdiğiniz alanlar değişir.
Parametreler
client
id değeri.name
external_id
null değeri kimliği temizler.email
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
email ile birlikte zorunludur.active
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
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
POST
/api/v1/reseller/clients/{client}/credit
Bakiyenizden müşterinin bakiyesine kredi gönderir.
Parametreler
client
id değeri.amount
idempotency_key
Ö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
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"
}
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"
}
{
"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.
Parametreler
client
id değeri.amount
idempotency_key
Ö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
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"
}
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"
}
{
"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.
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
id değeri.from
to
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
POST
/api/v1/reseller/clients/{client}/connect-link
Müşterinin WhatsApp numarasını bağladığı tek kullanımlık link (Numara bağlama bölümüne bakın). 7 gün geçerlidir.
Parametreler
client
id değeri.return_url
status=connected eklenerek bu adrese gönderilir.Örnek istek
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();
Yanıt · HTTP 200
{
"url": "https://podimessage.io/connect/57?expires=1794208500&nonce=Qm9X…&return=https%3A%2F%2Fcrm.example.com%2Fclinics%2F1042%2Fwhatsapp&signature=3b1f…",
"expires_at": "2026-11-09T10:15:00+03:00"
}
Diğer yanıtlar
{
"message": "Askıya alınmış müşteri numara bağlayamaz. Önce hesabı yeniden etkinleştirin.",
"code": "client_inactive"
}
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
name
string
template
string
language
string
category
string
marketing, utility veya authentication.status
string
scheduled, queued, processing, completed, failed veya cancelled.scheduled_at
string|null
null.total_recipients
integer
sent
integer
failed
integer
delivered
integer
read
integer
started_at
string|null
completed_at
string|null
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.
Ö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.
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
template
language
tr veya en_US gibi şablon dil kodu. Varsayılan en_US olduğundan Türkçe şablonlarda mutlaka belirtin.params
{{1}}, {{2}}… alanlarını sırasıyla dolduran en fazla 10 metin (her biri en fazla 1.000 karakter).phone_number_id
idempotency_key
Ö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
{
"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"
}
phone_number_id aktif değil).
{
"message": "Bağlı ve aktif bir WhatsApp numarası yok. Panelden WhatsApp bağlantısını kurun."
}
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.
Parametreler
to
template
code
language
tr veya en_US gibi şablon dil kodu. Varsayılan en_US olduğundan Türkçe şablonlarda mutlaka belirtin.include_button
true. Şablonda düğme yoksa false gönderin.button_sub_type
url (varsayılan; tek dokunuşla doldurma ve kod kopyalama düğmelerinde çalışır) veya copy_code.phone_number_id
Ö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
phone_number_id aktif değil).
{
"message": "Bağlı ve aktif bir WhatsApp numarası yok. Panelden WhatsApp bağlantısını kurun."
}
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).
Parametreler
status
ALL (varsayılan), APPROVED, PENDING, REJECTED, PAUSED veya DISABLED.phone_number_id
Ö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
phone_number_id aktif değil).
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.
Parametreler
name
_; en fazla 512 karakter.language
tr gibi dil kodu.category
MARKETING, UTILITY veya AUTHENTICATION.components
example gerekir.phone_number_id
Ö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
phone_number_id aktif değil).
code Meta’nın hata kodudur.
DELETE
/api/v1/templates/{name}
Bir şablonu Meta’da siler.
Parametreler
name
phone_number_id
Ö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
phone_number_id aktif değil).
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.
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
template
language
recipients
recipients.*.phone
recipients.*.name
recipients.*.params
{{1}}, {{2}}… alanlarını dolduran en fazla 10 metin.scheduled_at
phone_number_id
Ö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
{
"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"
}
phone_number_id aktif değil).
{
"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.
Parametreler
campaign
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
{
"message": "Kampanya bulunamadı."
}
GET
/api/v1/campaigns
Müşterinin kampanyaları (panelden ve API’den açılanlar), en yeni başta.
Parametreler
status
scheduled, queued, processing, completed, failed veya cancelled.per_page
page
Ö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.
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
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
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
wa_id
string
name
string|null
phone_number_id
string|null
messages/send çağrısına verin.status
string
pool (Bekleyenler’de, henüz kimse üstlenmedi), assigned veya closed.unread_count
integer
last_message_preview
string|null
last_message_at
string|null
last_inbound_at
string|null
can_reply
boolean
true.reply_window_expires_at
string|null
null.updated_at
string|null
updated_since ile birlikte kullanın.Mesaj nesnesi
id
integer
direction
string
inbound (kişiden) veya outbound.type
string
text, image, video, audio, document, sticker, location, button, interactive…text
string|null
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
sent, delivered, read veya failed; gelen mesajlarda null.source
string|null
panel, api, auto_reply, template veya business_app (işletmenin telefonundaki WhatsApp Business uygulamasından gönderilen); gelen mesajlarda null.created_at
string
GET
/api/v1/conversations
Müşterinin sohbetleri, en son yazışılan başta.
Parametreler
status
pool (Bekleyenler’de, henüz kimse üstlenmedi), assigned, closed veya all (varsayılan).updated_since
wa_id
per_page
page
Ö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.
Parametreler
conversation
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
{
"message": "Sohbet bulunamadı."
}
GET
/api/v1/conversations/{conversation}/messages
Bir sohbetin mesajları, yeniden eskiye sıralı.
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
id değeri.per_page
before_id
after_id
Ö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
{
"message": "Sohbet bulunamadı."
}
POST
/api/v1/conversations/{conversation}/reply
24 saatlik yanıt süresi içinde serbest metinle yanıt verir. Ücretlendirilmez.
Parametreler
conversation
id değeri.text
Idempotency-Key
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
{
"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"
}
{
"message": "Bağlı ve aktif bir WhatsApp numarası yok. Panelden WhatsApp bağlantısını kurun.",
"code": "no_account"
}
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"
}
retryable yeniden denemenin işe yarayıp yaramayacağını söyler. Hiçbir şey kaydedilmedi.
{
"message": "(#131026) Message undeliverable",
"code": "131026",
"retryable": false
}
{
"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.
Parametreler
conversation
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
{
"message": "Sohbet bulunamadı."
}
GET
/api/v1/messages/{message}/media
Bir mesajdaki fotoğrafı, videoyu, sesli mesajı, belgeyi veya çıkartmayı WhatsApp’tan aktararak indirir.
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
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
{
"message": "Mesaj medyası bulunamadı."
}
{
"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).
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
client_low_balance_threshold
null. Göndermezseniz mevcut değer korunur.reseller_low_balance_threshold
null. Göndermezseniz mevcut değer korunur.active
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
{
"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."
]
}
}
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.
Ö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
{
"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.
Ö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
{
"message": "Henüz bir webhook tanımlanmadı. PUT /api/v1/reseller/webhook ile oluşturabilirsiniz."
}
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.
Ö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
{
"message": "Çok fazla test isteği gönderildi. 42 saniye sonra tekrar deneyin."
}
{
"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.
Parametreler
event
message.inbound.status
pending, delivered veya failed.per_page
page
Ö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.
Ö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
{
"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
User-Agent
Webhooks/1.0
X-Webhook-Id
9b2f7c1e-3d4a-4f6b-8c5d-1e2f3a4b5c6d
id ile aynı). Tekrarları ayıklamak için kullanın.X-Webhook-Event
message.inbound
event ile aynı).X-Webhook-Timestamp
1793603700
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
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 İsteğin ham gövdesini ayrıştırmadan önce okuyun: yeniden kodlanmış JSON eşleşmez.
-
2
İmza anahtarınızla
{X-Webhook-Timestamp}.{ham gövde}için HMAC-SHA256 (hex) hesaplayıp başınav1=ekleyin. -
3
Sonucu
X-Webhook-Signatureile sabit sürede karşılaştırın. - 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
failedolarak 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).PUTve"active": trueile yeniden açın. -
Olaylar birden fazla kez ve sırasız gelebilir. Daha önce işlediğiniz
X-Webhook-Iddeğerlerini atlayın vedataiçindekitimestampalanına göre sıralayın.message.statusaynıwamidve durum için tekrar edebilir; bunlara göre de ayıklayın. -
Her gönderim 30 gün saklanır:
GET …/deliveriesdenemeleri, 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
POST /api/v1/reseller/clients/{client}/connect-linkile bir link oluşturun; isterseniz yazılımınızdaki birreturn_urladresini de verin. -
2
Müşteriyi
urladresine 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
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
Numarayı kaydeder, linki kapatır ve size
client.connectedwebhook’unu göndeririz. Numara, müşteri nesnesindewhatsappaltında da görünür. -
5
Sayfa müşteriyi, sorgu dizesine
status=connectedekleyerekreturn_urladresinize geri gönderir.
- 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=connecteddeğerini yalnızca ekranınız için bir ipucu olarak kullanın; herkes bu değeri yazabilir. Webhook’a veyaGET /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.