Givoti SMS API dokümantasyonu
Kendi yazılımından WhatsApp mesajı ve doğrulama kodu gönder, rehberini yönet, olayları imzalı webhook ile al. Tüm paketlerde (denemede de) açıktır.
Temel bilgiler
Taban adres:
- İstek ve yanıt gövdeleri JSON'dur (
Content-Type: application/json, UTF-8). Medya yüklememultipart/form-datakullanır. - Telefon numaraları ülke koduyla, yalnızca rakam olarak döner:
905321234567. Gönderirken0532 123 45 67,+90 532…gibi biçimler de kabul edilir; Türkiye dışı numaraları+ile yaz. - Tarihler ISO 8601 biçimindedir (
2026-10-23T09:30:00.000Z).scheduled_atalanında ISO metni veya milisaniye cinsinden zaman damgası gönderebilirsin. - Gönderilen her mesaj önce kuyruğa alınır (
status: "queued") ve hattın günlük limitine, bekleme süresine ve mesai saatlerine uyularak gönderilir. Sonucu webhook ile ya daGET /messages/:idile takip et.
Kimlik doğrulama
Panelde API & Webhook sayfasından bir API anahtarı oluştur. Anahtar ulk_live_ ile başlar ve yalnızca bir kez gösterilir. Her istekte şu başlıklardan birini gönder:
Authorization: Bearer ulk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# ya da
X-API-Key: ulk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Anahtarlara yetki (kapsam) verilir; yetkisi olmayan uca yapılan istek 403 döner. İsteğe bağlı olarak anahtarı belirli IP adresleriyle ve bir bitiş tarihiyle sınırlayabilirsin.
| Kapsam | İzin verdiği |
|---|---|
messages:send | Mesaj, toplu mesaj, OTP gönderme; medya yükleme; sıradaki mesajı iptal |
messages:read | Mesajları listeleme ve okuma |
contacts:read / contacts:write | Kişileri okuma / ekleme, güncelleme, silme |
lines:read | Hatları listeleme |
templates:read | Şablonları listeleme |
* | Tüm yetkiler |
Hız sınırları ve kotalar
- API anahtarı başına dakikada 120 istek. Ayrıca IP başına dakikada 600 istek.
- Aynı IP'den 15 dakikada 30 geçersiz anahtar denemesinden sonra istekler 15 dakika
429alır. - Yanıtlarda
X-RateLimit-LimitveX-RateLimit-Remainingbaşlıkları bulunur. - Tek istekte en fazla 500 alıcı; toplu uçta en fazla 1.000 mesaj.
- Gönderimler paketinin aylık mesaj hakkından düşer; hak yetmezse istek
402döner ve hiçbir mesaj kuyruğa alınmaz. - Kuyruğa alınan mesajlar hattın günlük limiti ve ısınma kurallarına göre gönderilir; bu yüzden büyük listeler saatlere yayılabilir.
Hatalar
Hatalı isteklerde HTTP durum kodu ve şu gövde döner:
{
"error": {
"code": "validation_error",
"message": "Alıcı listesi zorunludur",
"details": { "to": "Alıcı listesi zorunludur" }
}
}
| HTTP | code | Anlamı |
|---|---|---|
| 400 | bad_request | Gövde geçerli JSON değil |
| 401 | unauthorized | Anahtar yok, geçersiz, iptal edilmiş ya da süresi dolmuş |
| 402 | quota_exceeded, trial_expired | Aylık mesaj hakkı yetersiz / deneme süresi bitti |
| 403 | forbidden | Anahtarın bu yetkisi yok, IP izinli değil ya da hesap askıda |
| 404 | not_found | Kayıt ya da yol bulunamadı |
| 409 | conflict | Numara rehberde zaten var; ya da mesaj artık iptal edilemez |
| 413 | error | Dosya çok büyük (en fazla 16 MB) |
| 422 | validation_error, no_line | Eksik / hatalı alan; kullanılabilir hat yok |
| 429 | rate_limited | Hız sınırı aşıldı; bir süre sonra tekrar dene |
| 500 | error | Sunucu hatası; isteği daha sonra tekrarla |
/messages
messages:sendBir ya da birden fazla kişiye mesaj gönderir.
| Alan | Tür | Açıklama |
|---|---|---|
to * | metin / dizi | Numara ya da numara listesi (en fazla 500). Rehberdeki bir kişi için "c:42". |
body | metin | Mesaj metni (en fazla 4.096 karakter). Değişkenler ({ad}) ve metin seçenekleri ({Merhaba|Selam}; her mesajda biri rastgele seçilir) desteklenir. media yoksa zorunlu. |
template_id | sayı | body yerine kayıtlı şablonu kullanır. |
variables | nesne | Ek değişkenler: {"siparis_no": "10482"}. Rehberdeki kişinin bilgileriyle birleşir. |
media | nesne | {"url": "https://…/fatura.pdf", "type": "document", "caption": "…", "filename": "…"} ya da yüklenen dosya için {"file": "…"}. Türler: image, video, audio, document. En fazla 16 MB. |
line_id / line_ids | sayı / dizi | Gönderecek hat(lar). Boşsa varsayılan / uygun hatlar arasında dağıtılır. |
scheduled_at | tarih | İleri tarihli gönderim. |
client_ref | metin | Kendi referansın (ör. sipariş no, en fazla 100 karakter); listede filtrelenebilir ve webhook'ta döner. |
curl -X POST https://givotisms.com/v1/messages \ -H "Authorization: Bearer $ULAK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "905321234567", "body": "Merhaba {ad}, #{siparis_no} numaralı siparişiniz kargoya verildi.", "variables": { "ad": "Ayşe", "siparis_no": "10482" }, "client_ref": "order-10482" }'
Yanıt 201 Created. Tek alıcıda data bir mesaj nesnesidir; birden fazla alıcıda data dizi olur ve count eklenir.
{
"data": {
"id": 1842, "line_id": 3, "to": "905321234567", "direction": "out",
"type": "text", "body": "Merhaba Ayşe, #10482 numaralı siparişiniz kargoya verildi.",
"media": null, "status": "queued", "error": null, "source": "api",
"campaign_id": null, "client_ref": "order-10482", "wa_id": null,
"scheduled_at": null, "sent_at": null, "delivered_at": null, "read_at": null,
"created_at": "2026-10-23T09:30:00.000Z"
}
}
Mesaj durumları: queued (sırada) → sending → sent (WhatsApp'a iletildi) → delivered (alıcının telefonuna ulaştı) → read (okundu). Hata durumunda failed ve error alanı; iptal edilirse cancelled. Gelen mesajlarda direction: "in" ve from alanı bulunur.
/messages/bulk
messages:sendFarklı içerikli en fazla 1.000 mesajı tek istekte kuyruğa alır. Her öğe /messages gövdesiyle aynıdır. Toplam mesaj hakkı önceden kontrol edilir; yetmezse hiçbiri kuyruğa alınmaz (402).
{ "messages": [
{ "to": "905321234567", "body": "Siparişiniz hazır.", "client_ref": "A-1" },
{ "to": "905441112233", "template_id": 4, "variables": { "siparis_no": "A-2" } }
] }
Yanıt: en az biri kabul edildiyse 201, hiçbiri kabul edilmediyse 422. Gövde: {"data": [{"index": 0, "ok": true, "data": [ … ]}, {"index": 1, "ok": false, "error": {"code": "…", "message": "…"}}], "accepted": 1, "rejected": 1}.
/messages
messages:readMesajları en yeniden eskiye listeler. Sorgu parametreleri: status (virgülle birden fazla: failed,cancelled), direction (in / out), phone, client_ref, since (ISO tarih), page, limit (varsayılan 50, en fazla 200). Yanıt: {"data": [ … ], "total": 120, "page": 1, "limit": 50}.
/messages/:id
messages:readTek mesajı döner: {"data": { … }}.
/messages/:id
messages:sendHenüz sırada (queued) bekleyen mesajı iptal eder. Gönderilmeye başlamış mesaj için 409 döner.
/otp
messages:sendDoğrulama kodu gönderir. Kodu sen verebilir ya da Givoti SMS'e ürettirebilirsin; üretilen kod yanıtta döner, doğrulamayı kendi sisteminde yaparsın.
| Alan | Açıklama |
|---|---|
to * | Telefon numarası |
code | Kendi kodun (3–12 harf/rakam). Boşsa rastgele rakamlardan üretilir. |
length | Üretilecek kodun uzunluğu, 4–8 (varsayılan 6) |
template | Mesaj metni; {kod} içermeli. Varsayılan: "Doğrulama kodunuz: *{kod}*" ve yeni satırda "Bu kodu kimseyle paylaşmayın." |
line_id, client_ref | İsteğe bağlı |
curl -X POST https://givotisms.com/v1/otp -H "Authorization: Bearer $ULAK_API_KEY" \ -H "Content-Type: application/json" -d '{"to": "905321234567", "length": 6}' # 201 → { "data": { "id": 1843, "status": "queued", … }, "code": "482913" }
/media
messages:sendDosyayı önceden yükler (multipart/form-data, alan adı file, en fazla 16 MB). Yanıttaki file değerini mesajda "media": {"file": "…"} olarak kullan.
curl -X POST https://givotisms.com/v1/media -H "Authorization: Bearer $ULAK_API_KEY" -F "file=@katalog.pdf" # 201 → { "data": { "type": "document", "file": "1729675800000-a1b2c3.pdf", "url": "…", "mimetype": "application/pdf", "filename": "katalog.pdf", "size": 182044 } }
/contacts
contacts:readParametreler: q (ad, soyad, telefon, e-posta içinde arar), phone, tag, page, limit.
/contacts/:id
contacts:read:id yerine telefon numarası da yazılabilir (/contacts/905321234567).
/contacts
contacts:writeKişi ekler. Numara zaten varsa 409 döner; güncellemek için ?upsert=true ekle.
{
"phone": "05321234567", "name": "Ayşe", "surname": "Yılmaz",
"email": "ayse@ornek.com", "company": "Yılmaz Tekstil", "birthday": "1990-05-17",
"tags": ["vip"], "custom": { "sehir": "İzmir" },
"consent": true, "groups": [2]
}
consent: true, kişinin ticari ileti onayı verdiğini kaydeder (kaynak "API" ve o anki tarih). Bu alanı yalnızca gerçekten onay aldıysan gönder; bkz. İzin ve İYS. Yanıttaki kişi nesnesi opted_out (STOP yazdı mı) ve has_whatsapp alanlarını da içerir.
/contacts/:id
contacts:writeGönderilen alanları günceller; custom mevcut alanlarla birleştirilir.
/contacts/:id
contacts:writeKişiyi siler.
/lines
lines:readHatları döner: id, name, status (connected, disconnected, pending…), phone, is_default, daily_limit.
/templates
templates:readŞablonları döner: id, name, category, body, media.
Webhook olayları
Panelde API & Webhook → Webhook'lar → Ekle ile bir HTTPS adresi ve dinlenecek olayları seç (boş bırakırsan tüm olaylar gelir). Givoti SMS her olayda bu adrese POST gönderir:
POST /senin-adresin
Content-Type: application/json
User-Agent: Ulak-Webhook/1.0
X-Ulak-Event: message.delivered
X-Ulak-Delivery: 5531
X-Ulak-Signature: sha256=3f6c…e9a1
{
"event": "message.delivered",
"created_at": "2026-10-23T09:31:12.000Z",
"data": { "id": 1842, "to": "905321234567", "status": "delivered", "client_ref": "order-10482", … }
}
| Olay | Ne zaman | data |
|---|---|---|
message.queued | Mesaj kuyruğa alındı | Mesaj nesnesi |
message.sent | WhatsApp'a iletildi | Mesaj nesnesi |
message.delivered | Alıcının telefonuna ulaştı | Mesaj nesnesi |
message.read | Alıcı okudu (okundu bilgisi açıksa) | Mesaj nesnesi |
message.failed | Gönderilemedi | Mesaj nesnesi (error dolu) |
message.received | Sana mesaj geldi | Mesaj nesnesi + contact (id, name, surname) |
line.connected / line.disconnected | Hat bağlandı / koptu | line_id, phone / reason |
line.qr | Ayrılmış; şu an gönderilmiyor | — |
campaign.started / campaign.completed | Kampanya başladı / bitti | campaign_id, name, sayılar |
contact.opted_out / contact.opted_in | Kişi STOP yazdı ya da yeniden katıldı | contact_id, phone, varsa keyword / source |
Yanıt ve yeniden deneme: 10 saniye içinde 2xx döndürmelisin. Aksi hâlde Givoti SMS 10 sn, 1 dk, 5 dk, 30 dk ve 2 saat sonra tekrar dener (toplam 6 deneme), sonra bu gönderimi "başarısız" olarak işaretler. Aynı olay birden fazla gelebilir; X-Ulak-Delivery numarasıyla tekrarları ayıkla. Ağır işleri yanıtı döndükten sonra yap.
İmza doğrulama
X-Ulak-Signature, istek gövdesinin (ham bayt hâlinin) webhook imza anahtarınla (secret) hesaplanmış HMAC-SHA256 özetidir, onaltılık (hex) biçimde ve sha256= önekiyle. Gövdeyi JSON olarak ayrıştırmadan önce ham metin üzerinden doğrula.
import crypto from "node:crypto"; import express from "express"; const app = express(); app.post("/ulak-webhook", express.raw({ type: "application/json" }), (req, res) => { const expected = "sha256=" + crypto.createHmac("sha256", process.env.ULAK_WEBHOOK_SECRET).update(req.body).digest("hex"); const got = String(req.get("x-ulak-signature") || ""); const ok = got.length === expected.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected)); if (!ok) return res.sendStatus(401); const { event, data } = JSON.parse(req.body.toString("utf8")); res.sendStatus(200); // önce yanıt ver if (event === "contact.opted_out") { /* kendi listenden çıkar */ } });
Mağazadan Givoti SMS'e gelen webhook
Mağazan ya da yazılımın sipariş olaylarını Givoti SMS'e gönderip hazır şablonlarla bildirim yollatabilir. Bu uç API anahtarı değil, entegrasyona özel gizli bir adres (/hooks/…) kullanır. Gövde biçimi, olay adları ve imza (X-Ulak-Signature, HMAC-SHA256, base64) için Diğer platformlar kılavuzuna bak. WooCommerce ve Shopify kendi imza başlıklarıyla çalışır.