İçeriğe geç
KARGO GO

Geliştirici dokümanı

Kurumsal API ile e-ticaret sitenizden ya da ERP’nizden gönderi oluşturun, etiket alın, takip edin; webhook ile durum değişikliklerini anında alın.

Bu sayfa entegrasyon için gereken her şeyi özetler. Uç noktaların tam listesi ve şemaları için makine tarafından okunabilir OpenAPI belgesini kullanın: openapi.json · etkileşimli doküman (Swagger). API anahtarınızı ve webhook adreslerinizi kurumsal portal › API ve webhook sayfasından yönetirsiniz (portal yöneticisi rolü).

1. Başlangıç

  • Temel adres: http://api:4000/api/public/v1
  • Kimlik doğrulama: her isteğe Authorization: Bearer <anahtar> başlığı. Anahtar portalde üretilir ve yalnız bir kez gösterilir; sistemde yalnız özeti saklanır. Kaybederseniz iptal edip yenisini oluşturun.
  • İki ortam: kg_test_… anahtarları deneme ortamında çalışır: aynı uçlar, aynı yanıt biçimi; ama gönderiler ayrı tutulur, operasyona, raporlara ve faturaya hiç girmez. kg_live_… anahtarları gerçek gönderi açar.
  • İzinler (kapsam): anahtar oluştururken seçilir. shipments.create (Gönderi oluştur ve iptal et), shipments.read (Gönderi ve takip bilgisi oku, fiyat sorgula), labels.print (Etiket al (PDF / ZPL)), pickups.manage (Alım talebi oluştur, listele, iptal et), returns.manage (İade kodu üret), webhooks.manage (Webhook adreslerini yönet). Kapsam dışı istek 403 SCOPE_REQUIRED alır.
  • IP izin listesi: isteğe bağlı; tanımlıysa yalnız o adreslerden gelen istekler kabul edilir (403 IP_NOT_ALLOWED).
  • İstek sınırı: anahtar başına dakikada 600 istek (değiştirilebilir). Yanıt başlıkları X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset; aşımda 429 ve Retry-After.
  • İstek kimliği: her yanıtta X-Request-Id döner (kendi değerinizi gönderirseniz aynen geri gelir). Destek taleplerinde bu değeri iletin.
Bağlantı testi
curl http://api:4000/api/public/v1/me \
  -H "Authorization: Bearer kg_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

2. İstek ve yanıt kuralları

  • Gövde ve yanıt JSON (UTF-8). Bilinmeyen alanlar reddedilir. Tarihler ISO 8601, tutarlar kuruş cinsinden tam sayı (codAmountKurus: 45000 = 450,00 ₺).
  • Sayfalama imleç tabanlıdır: yanıttaki nextCursor boş değilse aynı isteği cursor=<değer> ile tekrarlayın. limit en çok 200.
  • Idempotency-Key: POST isteklerine 8–128 karakterlik benzersiz bir anahtar (ör. sipariş numaranız + deneme sayısı) ekleyin. Ağ hatasında aynı anahtarla tekrar gönderirseniz ilk yanıt aynen döner (Idempotent-Replayed: true); ikinci gönderi açılmaz. Aynı anahtar farklı gövdeyle gelirse 422 IDEMPOTENCY_KEY_REUSED. Kayıt 24 saat tutulur.
  • Sipariş numarası (reference): müşteri bazında benzersizdir; aynı referansla ikinci istek yeni gönderi açmaz, mevcut gönderiyi created: false ile döner.
  • Mağaza siparişi (store): e-ticaret sipariş kaynağını verirseniz ({ "platform": "WOOCOMMERCE", "url": "https://magaza.com", "orderId": "1042" }) aynı sipariş için portaldaki mağaza bağlantısı ya da eklenti ikinci gönderi açmaz; var olan gönderi created: false ile döner: açıksa matchedBy: "STORE_ORDER", teslim edilmiş / kapanmışsa matchedBy: "STORE_ORDER_CLOSED". Kapanmış siparişe değişim / yeniden gönderim için yeni gönderi yalnız bilinçli istekle açılır: "store": { …, "resend": true }. Önceki gönderi hâlâ açıksa bu bayrak da yeni gönderi açmaz (var olan döner); iptal edilmiş gönderi bayrak gerektirmez. Aynı reference verilmişse yeni gönderinin numarası -Y2, -Y3… eki alır.

Hata gövdesi her zaman aynı biçimdedir:

Hata yanıtı
HTTP/1.1 422 Unprocessable Entity
X-Request-Id: 3f9c…

{
  "code": "ADDRESS_NOT_FOUND",
  "message": "Adres eşleşmedi: Mahalle kesin eşleşmedi. GET /coverage ile il, ilçe ve mahalle adlarını kontrol edin.",
  "requestId": "3f9c…",
  "errors": [{ "field": "receiver.address.neighborhood", "message": "Mahalle kesin eşleşmedi" }],
  "details": { "candidates": [{ "id": 4821, "name": "Kızılay Mahallesi" }] }
}
HTTPcodeAnlamı
400VALIDATION_ERRORAlan doğrulama hatası; errors listesine bakın
401UNAUTHORIZEDAnahtar yok, geçersiz, iptal edilmiş ya da süresi dolmuş
403SCOPE_REQUIRED / IP_NOT_ALLOWED / SANDBOX_ONLYKapsam, IP ya da ortam uygun değil
404SHIPMENT_NOT_FOUND …Kayıt yok ya da başka müşteriye ait (ayrım yapılmaz)
409NOT_CANCELLABLE / INVALID_STEP …İşlem mevcut durumda yapılamaz
422ADDRESS_NOT_FOUND / NO_COVERAGE / SERVICE_UNAVAILABLE …İş kuralı hatası
429RATE_LIMITEDİstek sınırı; Retry-After saniye sonra tekrar deneyin

3. Uç noktalar

YöntemYolKapsamAçıklama
GET/me—Anahtar bilgisi, durum kodları (bağlantı testi)
GET/coverage—Hizmet bölgesi: il + ilçe (+ mahalle) adı ya da neighborhoodId
GET/content-categories—İçerik türü kodları
GET/sender-addressesshipments.readPortalde tanımlı gönderici adresleriniz
POST/quotesshipments.readFiyat sorgusu (sözleşme tarifesiyle)
POST/shipmentsshipments.createGönderi oluştur (Idempotency-Key)
POST/shipments/batchshipments.createToplu oluştur (500’e kadar, satır bazlı sonuç)
GET/shipmentsshipments.readListele: reference, trackingNumber, status, from, to, cursor, limit
GET/shipments/{id}shipments.readGönderi ayrıntısı
GET/shipments/{id}/eventsshipments.readTakip olayları
GET/shipments/{id}/labellabels.printEtiket: format=pdf|zpl, layout=100x100|100x150|A4
POST/shipments/{id}/cancelshipments.createİptal (yalnız kabulden önce)
POST/pickupspickups.manageAlım talebi oluştur
GET/pickups, /pickups/{id}pickups.manageAlım talepleri
POST/pickups/{id}/cancelpickups.manageAlım talebini iptal et
POST/returnsreturns.manageİade kodu üret
POST/sandbox/shipments/{id}/advanceshipments.createDeneme: durumu ilerlet (yalnız kg_test_)
POST/sandbox/pickups/{id}/completepickups.manageDeneme: alımı tamamla (yalnız kg_test_)
GET/POST/PUT/DELETE/webhookswebhooks.manageWebhook adresleri; /webhooks/{id}/test, /webhooks/{id}/rotate-secret
GET/webhook-deliverieswebhooks.manageTeslim günlüğü; /webhook-deliveries/{id}/resend

4. Örnek: gönderi oluşturma

Alıcı adresi il / ilçe / mahalle adıyla verilebilir; sistem eşleştirir, eşleşmezse adayları döner. Ölçü vermezseniz ücret ağırlıktan hesaplanır ve şubede kesinleşir. İçerik türü verilmezse portal ayarlarındaki varsayılan kullanılır.

POST /shipments
curl -X POST http://api:4000/api/public/v1/shipments \
  -H "Authorization: Bearer kg_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: siparis-10231-1" \
  -d '{
    "reference": "SIP-10231",
    "receiver": {
      "name": "Ayşe Yılmaz",
      "phone": "0532 111 22 33",
      "email": "ayse@example.com",
      "address": { "province": "Ankara", "district": "Çankaya", "neighborhood": "Kızılay", "line": "Atatürk Blv. No:10 D:5" }
    },
    "pieces": [{ "weightKg": 2.5, "lengthCm": 30, "widthCm": 20, "heightCm": 15 }],
    "serviceType": "STANDARD",
    "codAmountKurus": 45000,
    "codMethod": "ANY",
    "contentDescription": "Tişört"
  }'
Yanıt (201)
{
  "created": true,
  "shipment": {
    "id": "019a…",
    "environment": "test",
    "trackingNumber": "012345678901",
    "reference": "SIP-10231",
    "status": "PRE_ADVISED",
    "statusLabel": "Ön kayıt",
    "price": { "final": false, "totalKurus": 17594, "lines": [ … ] },
    "pieces": [{ "seq": 1, "barcode": "0123456789010013", … }],
    "trackingUrl": "…/t/012345678901",
    …
  }
}

Etiket: GET /shipments/{id}/label?format=pdf (ZPL için format=zpl). Deneme gönderileri gerçek numaralardan ayırt edilsin diye 0 ile başlayan takip numarası alır; şube ve kurye cihazları bu numaraları kabul etmez.

5. Deneme ortamı

Deneme anahtarıyla oluşturduğunuz gönderi gerçek operasyona girmez; bu yüzden durumu siz ilerletirsiniz. Her adım webhook olaylarını da tetikler, böylece entegrasyonunuzu baştan sona deneyebilirsiniz.

POST /sandbox/shipments/{id}/advance
# to verilmezse sıradaki adım: ACCEPTED → IN_TRANSIT → OUT_FOR_DELIVERY → DELIVERED
curl -X POST http://api:4000/api/public/v1/sandbox/shipments/019a…/advance \
  -H "Authorization: Bearer kg_test_…" -H "Content-Type: application/json" \
  -d '{ "to": "DELIVERY_FAILED", "reason": "Alıcı adreste yok" }'
# Diğer adımlar: RETURNED (iade), COD_PAID_OUT (teslim edilmiş tahsilatlı gönderide ödeme)

6. Webhook bildirimleri

Portalde (ya da POST /webhooks ile) bir adres tanımlayıp hangi olayları istediğinizi seçersiniz. Adres https:// ile başlamalı, herkese açık bir sunucuya gitmeli ve isteğe 2xx dönmelidir (yönlendirme izlenmez, zaman aşımı 10 sn).

OlayNe zaman
shipment.status_changed — Gönderi durumu değiştiGönderinin genel durumu her değiştiğinde (kabul, yolda, dağıtımda, teslim, iade, iptal). Diğer olaylardan bağımsız olarak ayrıca gönderilir.
shipment.delivered — Gönderi teslim edildiAlıcıya teslim edildiğinde; teslim alan (maskeli), saat ve kapıda tahsilat bilgisi taşır.
shipment.delivery_failed — Teslim edilemediKurye teslim edemediğinde; sebep ve varsa yeni deneme tarihi taşır.
shipment.returned — İade süreci başladıGönderi göndericiye iade sürecine alındığında.
pickup.completed — Adresten alım tamamlandıKurye adresinizden paketleri aldığında; alınan gönderilerin takip numaralarını taşır.
cod.paid_out — Kapıda tahsilat ödemesi yapıldıKapıda tahsilat ödeme listesi bankadan ödendi olarak işaretlendiğinde.
Örnek gövde (shipment.delivered)
POST /kargogo/webhook HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: KargoGo-Webhook/1.0
X-KargoGo-Event: shipment.delivered
X-KargoGo-Event-Id: 6f1a…
X-KargoGo-Delivery-Id: 7c22…
X-KargoGo-Environment: live
X-KargoGo-Signature: t=1790000000,v1=5c3d…e1

{
  "id": "6f1a…",
  "type": "shipment.delivered",
  "environment": "live",
  "createdAt": "2026-10-01T09:12:44.000Z",
  "data": {
    "shipment": { "id": "…", "trackingNumber": "…", "reference": "SIP-10231", "status": "DELIVERED", "statusLabel": "Teslim edildi", … },
    "proof": { "receiverName": "A*** Y***", "relation": "Kendisi", "deliveredAt": "…", "otpVerified": false },
    "cod": { "amountKurus": 45000, "method": "CASH", "collectedAt": "…" }
  }
}
  • Teslim garantisi “en az bir kez”dir: aynı olay nadiren iki kez gelebilir; id alanını kaydedip tekrarları atlayın.
  • Yeniden deneme: 2xx dışı yanıt ya da zaman aşımında 1 dk → 5 dk → 15 dk → 30 dk → 2 sa → 6 sa → 24 sa aralıklarla toplam 8 deneme. Art arda 5 olay tüm denemelere rağmen iletilemezse adres kendiliğinden kapatılır ve firmanızın e-postasına bildirim gider; portalden yeniden açıp iletilemeyen olayları elle yeniden gönderebilirsiniz.
  • Sıra garanti değildir: olaylar paralel iletildiği ve yeniden denendiği için geliş sırası oluşma sırasından farklı olabilir (ör. shipment.delivered, dağıtıma çıkış bildiren shipment.status_changed olayından önce gelebilir). Her olaydacreatedAt alanını saklayın; elinizdeki kayıttan daha eski createdAt’lı bir olay gelirse durumu geri almayın. Güncel durumu her zaman GET /shipments/{id} ile de okuyabilirsiniz.
  • Hızlı yanıt verin: gövdeyi kuyruğa alıp hemen 200 dönün; ağır işlemleri sonra yapın.

İmza doğrulama

X-KargoGo-Signature başlığı t=<unix saniye>,v1=<hex> biçimindedir. İmza, adresin gizli anahtarı (whsec_…) ile `${t}.${hamGövde}` metninin HMAC-SHA256’sıdır. Ham gövdeyi (JSON’a çevirmeden önce, bayt bayt) kullanın; zaman damgası 5 dakikadan eskiyse reddedin (tekrar oynatma koruması). Karşılaştırmayı zamanlama güvenli fonksiyonla yapın.

Node.js (Express)
import express from 'express';
import { createHmac, timingSafeEqual } from 'node:crypto';

const SECRET = process.env.KARGOGO_WEBHOOK_SECRET; // whsec_…
const app = express();

// Ham gövde gerekir: JSON ayrıştırıcıdan ÖNCE express.raw kullanın
app.post('/kargogo/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8');
  if (!verify(req.header('x-kargogo-signature'), raw)) return res.status(400).send('imza gecersiz');
  const event = JSON.parse(raw);
  // event.id ile tekrarları atlayın, sonra işleyin (kuyruğa alıp hemen 200 dönün)
  console.log(event.type, event.data.shipment?.trackingNumber);
  res.status(200).json({ ok: true });
});

function verify(header, raw) {
  if (!header) return false;
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = createHmac('sha256', SECRET).update(`${t}.${raw}`).digest('hex');
  const given = String(parts.v1 ?? '');
  return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}

app.listen(3000);
PHP
<?php
// Ham gövde: php://input (json_decode etmeden önce)
$secret = getenv('KARGOGO_WEBHOOK_SECRET'); // whsec_…
$raw    = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_KARGOGO_SIGNATURE'] ?? '';

$parts = [];
foreach (explode(',', $header) as $p) {
    [$k, $v] = array_pad(explode('=', $p, 2), 2, '');
    $parts[$k] = $v;
}
$t = (int) ($parts['t'] ?? 0);
if ($t === 0 || abs(time() - $t) > 300) {
    http_response_code(400);
    exit('zaman damgasi gecersiz');
}
$expected = hash_hmac('sha256', $t . '.' . $raw, $secret);
if (!hash_equals($expected, $parts['v1'] ?? '')) {
    http_response_code(400);
    exit('imza gecersiz');
}

$event = json_decode($raw, true);
// $event['id'] ile tekrarları atlayın; $event['type'], $event['data']['shipment']['trackingNumber'] …
http_response_code(200);
echo json_encode(['ok' => true]);

Portaldeki “Deneme olayı gönder” düğmesi adresinize test.ping olayı yollar ve yanıt kodunu, süreyi ve yanıtınızın kısa halini gösterir; imza doğrulamanızı canlıya geçmeden burada deneyin.

7. Sürüm ve destek

Geriye uyumsuz değişiklikler yeni sürüm yolunda (/v2) yapılır; /v1 en az 12 ay desteklenir. Yeni alanlar mevcut yanıtlara eklenebilir; istemciniz bilinmeyen alanları yok saymalıdır. Sorularınız için portaldeki müşteri temsilcinizle iletişime geçin ve ilgili X-Request-Id değerini paylaşın.