- Taban adres
- https://app.flowoy.com
- Sürüm
- v1
- Kimlik
- X-API-Key
- Sınır
- 120 istek/dk
- Plan
- Profesyonel
Hızlı başlangıç
Kargo bildirimi kuran bir ekibin izlediği yol — beş çağrı, sırayla.
# 1 — anahtar doğru mu, hangi hesaba bağlı? curl https://app.flowoy.com/api/v1/me -H "X-API-Key: $KEY" # 2 — hangi şablon kaç değişken istiyor? curl https://app.flowoy.com/api/v1/templates -H "X-API-Key: $KEY" # 3 — müşteriyi deftere yaz (izniyle birlikte) curl -X POST https://app.flowoy.com/api/v1/customers \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"phone":"05551112233","firstName":"Ayşe","marketingConsent":true}' # 4 — kargoya verilince şablonu gönder curl -X POST https://app.flowoy.com/api/v1/messages/template \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"phone":"05551112233","template":"kargo_takip_butonlu", "variables":{"1":"Ayşe","2":"SP-1042","3":"Aras Kargo", "4":"7788991122","button_0_1":"xCpXi4v"}}' # 5 — sonucu webhook ile bekleyin; webhook yoksa durumu sorun curl https://app.flowoy.com/api/v1/messages/96a67fd4-… -H "X-API-Key: $KEY"
Dördüncü adımın döndürdüğü messageId değerini kendi sisteminizde siparişin yanına saklayın. Bir sorun olduğunda destekle konuşurken tek referans odur.
Kimlik doğrulama
Her istek X-API-Key başlığı ister. Anahtarsız istek 401 döner — API ile hesabınız olmadan hiçbir işlem yapılamaz.
curl https://app.flowoy.com/api/v1/me \ -H "X-API-Key: ptf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Anahtar nereden alınır
Panel → Ayarlar → API Anahtarları → Yeni Anahtar. Anahtar yalnız bir kez gösterilir; kaybederseniz yenisini üretip eskisini silin.
Anahtarı sorgu dizgisinde göndermeyin. ?apiKey=… biçimi kabul edilmez. URL'ler sunucu erişim kayıtlarına, referrer başlıklarına ve tarayıcı geçmişine düşer; anahtar oralardan sızar. Aynı sebeple anahtarı tarayıcıda çalışan koda gömmeyin — çağrıyı kendi sunucunuzdan yapın.
- Anahtar hesabınıza bağlıdır; başka hesabın verisine erişemez.
- API erişimi Profesyonel planında açıktır. Plan düşerse mevcut anahtarlar da
403 plan_requireddöner. - Anahtar biçimi
ptf_+ 32 karakter. İlk 12 karakter arama ön eki olarak saklanır, gerisi yalnız hash hâlinde durur — bizde bile düz metin karşılığı yoktur.
Hız sınırı
Anahtar başına dakikada 120 istek. Her yanıt kalan hakkı X-RateLimit-Remaining başlığında taşır. Sınır aşılınca 429 ve Retry-After döner — o kadar saniye bekleyip devam edin.
Bunun dışında gönderim, WhatsApp numaranızın kendi saatlik ve günlük kotasına da tabidir. Toplu iş yaparken iki sınırı da gözetin; 25'lik partiler için /messages/bulk ucunu kullanın.
Yanıt ve hatalar
Başarılı her yanıt ok: true, her hata ok: false ve makine tarafından okunabilir bir error kodu taşır.
| HTTP | error | Anlamı |
|---|---|---|
| 400 | bad_request | İstek eksik ya da bozuk |
| 400 | variables | Şablon değişkenleri uyuşmuyor — ayrıntı missing / unexpected alanlarında |
| 400 | template | Şablon Meta'da onaylı değil |
| 401 | unauthorized | Anahtar yok ya da geçersiz |
| 403 | plan_required | Planınızda API erişimi yok |
| 403 | not_sendable | Gönderim izin kapısından geçmedi — reason sebebi yazar |
| 404 | not_found | Kayıt bulunamadı |
| 413 | too_many | Toplu gönderimde alıcı sayısı sınırı aşıldı |
| 429 | rate_limited | Hız sınırı |
| 500 | internal | Bizdeki hata — tekrar denenebilir |
403 not_sendable bir hata değil, kuraldır. Müşteri pazarlama iznini kapatmış, kara listeye düşmüş ya da sıklık sınırı dolmuş olabilir. Tekrar denemeyin — gönderim kapıları bölümü hangi kapıya takıldığınızı anlatır.
Değişken hatası neye benzer
Eksik parametreyi Meta'nın 132000 ailesinden değil, bizden ve adıyla öğrenirsiniz:
{
"ok": false,
"error": "variables",
"missing": ["button_0_1"],
"unexpected": [],
"expected": {
"bodyVariableCount": 4,
"dynamicButtons": [{ "anahtar": "button_0_1", "metin": "Kargomu takip et" }]
}
}
Uç noktalar
Anahtarın hangi hesaba ait olduğunu doğrular. Entegrasyonu kurarken ilk burayı çağırın.
{ "ok": true, "data": {
"company": "Örnek Market",
"plan": "proexpert",
"phone": "+90 5xx xxx xx xx",
"rateLimitPerMinute": 120
} }
Yalnız Meta'da onaylı şablonlarınız. Gönderim yapmadan önce buradan hangi şablonun kaç değişken ve hangi buton anahtarını istediğini öğrenin. Tek şablon için /api/v1/templates/{ad}.
{ "ok": true, "total": 1, "data": [{
"name": "kargo_takip_butonlu",
"category": "UTILITY",
"language": "tr",
"body": "Merhaba {{1}}, {{2}} numaralı siparişiniz {{3}} ile kargoya verildi. Takip numaranız {{4}}.",
"headerType": null,
"bodyVariableCount": 4,
"dynamicButtons": [{ "anahtar": "button_0_1", "metin": "Kargomu takip et" }]
}] }
bodyVariableCount kadar "1", "2"… anahtarı ve her dynamicButtons[].anahtar değerini göndermeniz gerekir.
Müşteri listeniz. limit en çok 1000, varsayılan 100; offset ile sayfalayın. Tek kişi için /api/v1/customers/{telefon}.
Telefona göre ekler ya da günceller. Aynı numarayı tekrar göndermek mükerrer kayıt açmaz — dış sistemler aynı müşteriyi sürekli ittiği için bu davranış kasıtlıdır. Yeni kayıt 201, güncelleme 200 döner; created alanı hangisi olduğunu söyler.
| Alan | Zorunlu | Not |
|---|---|---|
phone | evet | 05xx…, +905xx…, 905xx… kabul edilir |
firstName | hayır | Göndermediğiniz alana dokunulmaz |
lastName | hayır | |
email | hayır | |
marketingConsent | hayır | Yalnız açıkça true/false gönderirseniz değişir. Göndermezseniz mevcut izin korunur — izin kaydı yasal belgedir, kazara silinmez |
Pazarlama çıkışı. Kendi iptal akışınızı (sitenizdeki "listeden çık" bağlantısı, çağrı merkezi) buraya bağlayın — aksi hâlde izin iki ayrı yerde tutulur ve biri yanlış olur. 6563 sayılı kanun gereği izin geri alındığında iznin kendisi de kapanır, yalnız bayrak değil. Aynı anda customer.opted_out webhook'u yayınlanır.
Onaylı şablon gönderir. 202 döner: mesaj kuyruğa alındı, henüz teslim edilmedi.
curl -X POST https://app.flowoy.com/api/v1/messages/template \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{ "phone": "05551112233", "template": "kargo_takip_butonlu", "createIfMissing": true, "variables": { "1":"Ayşe", "2":"SP-1042", "3":"Aras Kargo", "4":"7788991122", "button_0_1":"xCpXi4v" } }' # → 202 { "ok": true, "messageId": "96a67fd4-…", "status": "queued" }
| Alan | Zorunlu | Not |
|---|---|---|
phone | evet | Alıcı numarası |
template | evet | Şablon adı — uuid değil |
variables | şablona göre | Gövde ve buton anahtarları |
createIfMissing | hayır | Varsayılan false → numara defterde yoksa 404. Yazım hatasıyla sahte müşteri açılmasın diye böyle |
firstName, lastName | hayır | Yalnız createIfMissing ile yeni kayıt açılırken kullanılır |
SMS yerine WhatsApp'tan tek kullanımlık kod. Aynı uç, farklı kategoride bir şablon: AUTHENTICATION. Bu kategoride gövde metnini Meta kendisi yazar ve dile göre çevirir — siz yalnız kodu gönderirsiniz.
curl -X POST https://app.flowoy.com/api/v1/messages/template \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{ "phone": "05551112233", "template": "dogrulama_kodu", "createIfMissing": true, "variables": { "kod": "482913" } }' # → 202 { "ok": true, "messageId": "wamid.HBgMOTA1…", "status": "queued" }
Kullanıcıya düşen mesaj şöyle görünür — metin Meta'nındır, değiştirilemez:
Doğrulama kodunuz: 482913. Güvenliğiniz için bu kodu paylaşmayın. Süresi 10 dakika sonra doluyor. [ Kodu kopyala ]
| Alan | Zorunlu | Not |
|---|---|---|
variables.kod | evet | Ürettiğiniz kod. code, otp veya 1 adı da kabul edilir — kodu iki kez yazmanız gerekmez, buton parametresini biz dolduruyoruz |
template | evet | Hesabınızda onaylı doğrulama şablonunun adı |
Bilinmesi gerekenler
- İzin aranmaz. Doğrulama mesajı hizmet mesajıdır; pazarlama onayı, opt-out ve sıklık sınırı uygulanmaz. Kayıtlı olmayan kullanıcıya da gider.
- 24 saat kuralı etkilemez. Şablon mesajı pencereden bağımsız gider.
- Kodu sunucunuzda üretin ve hash'leyerek saklayın. Şablonun alt bilgisi 10 dakika diyor; sunucudaki geçerlilik süresi de 10 dakika olmalı, yoksa kullanıcıya yanlış bilgi vermiş olursunuz.
- API anahtarını mobil uygulamaya gömmeyin. Anahtar tüm hesabın yetkisini taşır; APK veya IPA içinden çıkarılabilir. İstek kendi sunucunuzdan gitmeli.
- Kopyala butonu otomatik doldurma değildir. Kullanıcı basar, kod panoya kopyalanır. Uygulamanın kodu kendiliğinden okuması için Android'e özel
ZERO_TAPbuton tipi ve uygulama tarafında broadcast receiver gerekir.
Doğrulama şablonu normal şablonlardan farklı oluşturulur (gövde metni kabul edilmez; güvenlik uyarısı, kod geçerlilik süresi ve OTP butonu verilir). Panelden oluşturabilir ya da bizden isteyebilirsiniz.
Tek şablon, çok alıcı — istek başına en çok 25 kişi. Her alıcı kendi kapılarından ayrı geçer: biri izinsizse diğerleri gider. Yanıt alıcı başına sonuç taşır, çünkü toplu işte "kısmen başarılı" normal durumdur.
curl -X POST https://app.flowoy.com/api/v1/messages/bulk \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{ "template": "kargo_takip_butonlu", "recipients": [ { "phone":"05551112233", "variables":{"1":"Ayşe","2":"SP-1042","3":"Aras","4":"778899","button_0_1":"xCpXi4v"} }, { "phone":"05449998877", "variables":{"1":"Mert","2":"SP-1043","3":"Aras","4":"778900","button_0_1":"Kd82Lp1"} } ] }' # → 202 — kısmi başarı normaldir { "ok": true, "queued": 1, "failed": 1, "results": [ { "phone": "05551112233", "ok": true, "messageId": "96a67fd4-…" }, { "phone": "05449998877", "ok": false, "error": "not_sendable", "reason": "Müşteri pazarlama iznini kapattı" } ] }
Tüm alıcılar aynı değişkenleri alacaksa variables alanını gövdenin köküne bir kez yazabilirsiniz; alıcıdaki değer kökü ezer.
Serbest metin. Yalnız müşteri son 24 saat içinde size yazdıysa gönderilebilir — bu WhatsApp'ın kuralı. Pencere kapalıysa 131047 alırsınız ve şablona düşmeniz gerekir. Bu uç müşteri açmaz; numara defterde yoksa 404.
curl -X POST https://app.flowoy.com/api/v1/messages/text \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"phone":"05551112233","text":"Siparişiniz hazır, bugün çıkıyor."}'
Gönderim geçmişi — "son X saatte ne oldu" diye sormanın yolu. Mutabakat için her kimliği tek tek yoklamaktan iyidir. Filtreler: status, direction, phone, since (ISO 8601), limit (en çok 200), offset.
curl "https://app.flowoy.com/api/v1/messages?status=failed&since=2026-10-30T00:00:00Z" \ -H "X-API-Key: $KEY"
Tek gönderimin sonucu.
| status | Anlamı |
|---|---|
pending | Kuyrukta, henüz Meta'ya gitmedi |
sent | Meta kabul etti |
delivered | Telefona ulaştı |
read | Okundu |
failed | Düştü — sebep error alanında |
Webhook aboneliği
Durumu yoklamak yerine biz size haber verelim. Her olay HTTPS adresinize imzalı olarak POST edilir.
curl -X POST https://app.flowoy.com/api/v1/webhooks \ -H "X-API-Key: $KEY" -H "Content-Type: application/json" \ -d '{"url":"https://sizin-siteniz.com/flowoy/webhook", "events":["message.delivered","message.failed","message.received"]}' # → 201 — secret YALNIZ burada gösterilir, saklayın { "ok": true, "data": { "id": "8b3c8f5d-…", "url": "https://sizin-siteniz.com/flowoy/webhook", "events": ["message.delivered", "message.failed", "message.received"], "secret": "whsec_4Kc…" } }
events boş bırakılırsa tüm olaylara abone olursunuz. Adres https olmalı ve genel bir alan adına işaret etmelidir; yerel ve iç ağ adresleri (localhost, 10.x, 169.254.x) reddedilir.
Aboneliklerinizi, üst üste kaçıncı başarısızlıkta olduklarını ve son hatayı listeler. secret burada dönmez.
Aboneliği siler.
Olaylar
| Olay | Ne zaman | veri içinde |
|---|---|---|
message.sent | Meta mesajı kabul etti | messageId, to, status, error, errorCode, at |
message.delivered | Telefona ulaştı | |
message.read | Okundu | |
message.failed | Düştü | |
message.received | Müşteri size yazdı | messageId, from, type, text, at |
customer.opted_out | Pazarlama izni kapandı | phone, source, at |
// Size gelen gövde { "olayId": "c1f0a0f2-…", "olay": "message.delivered", "zaman": "2026-10-31T09:14:22.418Z", "veri": { "messageId": "96a67fd4-…", "to": "905551112233", "status": "delivered", "error": null, "errorCode": null, "at": "2026-10-31T09:14:21.000Z" } }
Teslim garantisi en az bir kezdir. Geçici bir hatada aynı olay tekrar gönderilebilir — olayId ile tekilleştirin. Yanıt olarak 2xx döndürün; 4xx dönerseniz olay kalıcı reddedilmiş sayılır ve tekrar denenmez.
Üst üste 20 başarısız teslimden sonra abonelik otomatik kapatılır — ölü bir adrese sonsuza kadar vurmayız. Kapanan aboneliği GET /api/v1/webhooks ile görür, yenisini oluşturursunuz.
İmza doğrulama
Her iletim üç başlık taşır. Gövdeyi işlemeden önce imzayı doğrulayın; aksi hâlde adresinize herkes sahte olay gönderebilir.
| Başlık | İçerik |
|---|---|
X-Flowoy-Event | Olay adı — gövdeyi okumadan yönlendirmek için |
X-Flowoy-Timestamp | Unix saniye. 5 dakikadan eskiyse reddedin |
X-Flowoy-Signature | sha256= + HMAC-SHA256(secret, "timestamp.ham_gövde") |
Zaman damgası imzaya dahildir: aksi hâlde eski bir gövde aynı imzayla sonsuza kadar tekrar oynatılabilirdi.
PHP
$govde = file_get_contents('php://input'); $zaman = $_SERVER['HTTP_X_FLOWOY_TIMESTAMP'] ?? ''; $imza = $_SERVER['HTTP_X_FLOWOY_SIGNATURE'] ?? ''; // 1) Tazelik — tekrar oynatmayı (replay) engeller if (abs(time() - (int)$zaman) > 300) { http_response_code(400); exit; } // 2) İmza — sabit zamanlı karşılaştırma $beklenen = 'sha256=' . hash_hmac('sha256', $zaman . '.' . $govde, FLOWOY_SECRET); if (!hash_equals($beklenen, $imza)) { http_response_code(401); exit; } $olay = json_decode($govde, true); // … olayId ile tekilleştirin, işinizi yapın http_response_code(200);
Node.js
const crypto = require('crypto'); // DİKKAT: ham gövde gerekir — express.json() sonrası hesaplama tutmaz. app.post('/flowoy/webhook', express.raw({ type: 'application/json' }), (req, res) => { const zaman = req.get('X-Flowoy-Timestamp') || ''; const imza = req.get('X-Flowoy-Signature') || ''; if (Math.abs(Date.now() / 1000 - Number(zaman)) > 300) return res.sendStatus(400); const beklenen = 'sha256=' + crypto .createHmac('sha256', process.env.FLOWOY_SECRET) .update(`${zaman}.${req.body}`) .digest('hex'); const a = Buffer.from(beklenen), b = Buffer.from(imza); if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.sendStatus(401); const olay = JSON.parse(req.body); res.sendStatus(200); // önce 200 dön, işi arka planda yap });
Teslim günlüğü
"Webhook'um neden gelmedi" sorusunun cevabı. Her denemenin olay adı, HTTP durum kodu, hata metni, kaçıncı deneme olduğu ve süresi kayıtlıdır.
{ "ok": true, "count": 2, "data": [
{ "event": "message.delivered", "statusCode": 200, "ok": true,
"attempt": 1, "durationMs": 184, "at": "2026-10-31T09:14:22Z" },
{ "event": "message.failed", "statusCode": 502, "ok": false,
"error": "HTTP 502", "attempt": 3, "durationMs": 10004, "at": "2026-10-31T08:02:10Z" }
] }
Gönderim kapıları
API panelin kısa yolu değil. Her gönderim, panelden yapılan gönderimle aynı kapılardan sırayla geçer; hangisine takıldığınızı yanıttaki kod söyler.
-
1
Anahtar401 unauthorized
Hesap bağlamı yalnız anahtardan gelir. Anahtarsız hiçbir uç iş yapmaz.
-
2
Plan403 plan_required
API erişimi plandan kaldırılırsa daha önce üretilmiş anahtarlar da çalışmayı bırakır.
-
3
Hız sınırı429 rate_limited
Dakikada 120 istek.
Retry-Afterkadar bekleyin. -
4
Şablon onayı400 template
Yalnız Meta'da onaylı şablon gönderilir. Taslak ya da reddedilmiş şablon listede bile görünmez.
-
5
Değişken denetimi400 variables
Eksik ya da fazla anahtar burada yakalanır. Bu denetim izin kapısından önce çalışır ki hatalı bir istek müşterinin sıklık sayacını tüketmesin.
-
6
İzin kapısı403 not_sendable
Pazarlama izni, opt-out, kara liste ve sıklık sınırı — dördü de veritabanı sorgusunda uygulanır.
UTILITYveAUTHENTICATIONşablonları hizmet mesajı sayılır; izin ve sıklık sınırı onlara uygulanmaz. -
7
Kuyruk202 queued
Buraya gelen mesaj kuyruğa girer; numaranızın saatlik ve günlük kotası burada uygulanır. Sonuç webhook ile size döner.
Teslim hataları
failed olayındaki errorCode Meta'nın kodunu taşır. Hangisi kalıcı, hangisi beklemeli:
| Kod | Anlamı | Ne yapmalı |
|---|---|---|
| 131026 | Numara WhatsApp'ta yok, kullanıcı yeni koşulları kabul etmemiş ya da uygulaması çok eski | Tekrar denemeyin. Üst üste iki kez alan numara otomatik kara listeye alınır; panelden geri açabilirsiniz |
| 131047 | 24 saatlik pencere kapalı | Serbest metin yerine şablon gönderin |
| 131049 | WhatsApp, kullanıcı etkileşimi düşük olduğu için pazarlama mesajını teslim etmedi | Tekrar denemeyin; bu numaraya bir süre pazarlama göndermeyin |
| 130472 | Kullanıcı Meta'nın deneme grubunda | İşlem gerekmez |
| 132000+ | Şablon parametreleri şablonla uyuşmuyor | GET /templates ile beklenen değişkenleri karşılaştırın |
Henüz olmayanlar
Yol haritasında olup bugün bulunmayanlar. Entegrasyonunuzu bunlara dayandırmayın.
- Gelen mesaj geçmişi.
message.receivedolayı anlık gelir, ama geçmiş yazışmalar API'den okunamaz; panelden görülür. - Şablon oluşturma. Şablonlar panelden yaratılıp Meta onayına gönderilir.
- Kampanya tetikleme. Kampanyalar panelden başlatılır; API'den tek tek ya da 25'lik partiler hâlinde gönderim yapılır.
- Test (sandbox) ortamı. Ayrı bir test hesabı yoktur; denemelerinizi kendi numaranızla yapın.
- Yetki kapsamları. Anahtar hesabın tüm API uçlarına erişir; uç bazlı kısıtlama yoktur.
Eksik bulduğunuz bir uç mu var? Bize yazın — yol haritasını kullanan ekipler belirliyor.