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_REQUIREDalı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şımda429veRetry-After. - İstek kimliği: her yanıtta
X-Request-Iddöner (kendi değerinizi gönderirseniz aynen geri gelir). Destek taleplerinde bu değeri iletin.
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
nextCursorboş değilse aynı isteğicursor=<değer>ile tekrarlayın.limiten ç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 gelirse422 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: falseile 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öndericreated: falseile döner: açıksamatchedBy: "STORE_ORDER", teslim edilmiş / kapanmışsamatchedBy: "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ıreferenceverilmişse yeni gönderinin numarası-Y2,-Y3… eki alır.
Hata gövdesi her zaman aynı biçimdedir:
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" }] }
}| HTTP | code | Anlamı |
|---|---|---|
| 400 | VALIDATION_ERROR | Alan doğrulama hatası; errors listesine bakın |
| 401 | UNAUTHORIZED | Anahtar yok, geçersiz, iptal edilmiş ya da süresi dolmuş |
| 403 | SCOPE_REQUIRED / IP_NOT_ALLOWED / SANDBOX_ONLY | Kapsam, IP ya da ortam uygun değil |
| 404 | SHIPMENT_NOT_FOUND … | Kayıt yok ya da başka müşteriye ait (ayrım yapılmaz) |
| 409 | NOT_CANCELLABLE / INVALID_STEP … | İşlem mevcut durumda yapılamaz |
| 422 | ADDRESS_NOT_FOUND / NO_COVERAGE / SERVICE_UNAVAILABLE … | İş kuralı hatası |
| 429 | RATE_LIMITED | İstek sınırı; Retry-After saniye sonra tekrar deneyin |
3. Uç noktalar
| Yöntem | Yol | Kapsam | Açı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-addresses | shipments.read | Portalde tanımlı gönderici adresleriniz |
POST | /quotes | shipments.read | Fiyat sorgusu (sözleşme tarifesiyle) |
POST | /shipments | shipments.create | Gönderi oluştur (Idempotency-Key) |
POST | /shipments/batch | shipments.create | Toplu oluştur (500’e kadar, satır bazlı sonuç) |
GET | /shipments | shipments.read | Listele: reference, trackingNumber, status, from, to, cursor, limit |
GET | /shipments/{id} | shipments.read | Gönderi ayrıntısı |
GET | /shipments/{id}/events | shipments.read | Takip olayları |
GET | /shipments/{id}/label | labels.print | Etiket: format=pdf|zpl, layout=100x100|100x150|A4 |
POST | /shipments/{id}/cancel | shipments.create | İptal (yalnız kabulden önce) |
POST | /pickups | pickups.manage | Alım talebi oluştur |
GET | /pickups, /pickups/{id} | pickups.manage | Alım talepleri |
POST | /pickups/{id}/cancel | pickups.manage | Alım talebini iptal et |
POST | /returns | returns.manage | İade kodu üret |
POST | /sandbox/shipments/{id}/advance | shipments.create | Deneme: durumu ilerlet (yalnız kg_test_) |
POST | /sandbox/pickups/{id}/complete | pickups.manage | Deneme: alımı tamamla (yalnız kg_test_) |
GET/POST/PUT/DELETE | /webhooks | webhooks.manage | Webhook adresleri; /webhooks/{id}/test, /webhooks/{id}/rotate-secret |
GET | /webhook-deliveries | webhooks.manage | Teslim 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.
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"
}'{
"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.
# 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).
| Olay | Ne zaman |
|---|---|
shipment.status_changed — Gönderi durumu değişti | Gö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 edildi | Alıcıya teslim edildiğinde; teslim alan (maskeli), saat ve kapıda tahsilat bilgisi taşır. |
shipment.delivery_failed — Teslim edilemedi | Kurye 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. |
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;
idalanı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ış bildirenshipment.status_changedolayından önce gelebilir). Her olaydacreatedAtalanını saklayın; elinizdeki kayıttan daha eskicreatedAt’lı bir olay gelirse durumu geri almayın. Güncel durumu her zamanGET /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.
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
// 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.