Webhook ile değişiklik takibi
YolcuGo, hesabınıza ait transfer değişikliklerini sizin HTTPS adresinize POST
ile gönderebilir. Webhook alıcısı sizin sunucunuzda çalışır; portaldaki
istek deneme ekranı bir webhook alıcısı değildir.
Bağlantıyı hazırlama
- İnternetten erişilebilir HTTPS alıcı adresinizi hazırlayın.
- Hesabınız için webhook adresi, güçlü rastgele bir secret ve olay filtresi tanımlanmasını support@yolcugo.com üzerinden isteyin.
- Secret'ı yalnız sunucunuzda saklayın; API erişim token'ıyla aynı değer değildir.
- Test bildirimi isteyerek imza, kalıcı kayıt ve
2xxyanıtını doğrulayın.
Filtre virgülle ayrılmış tam olay adları veya transfer.* gibi joker ifade
olabilir. Boş filtre bütün olayları kapsar. Yeni olaylar eklenebileceğinden
alıcınızı tanımadığı alanlara toleranslı tasarlayın.
İstek başlıkları ve imzalar
| Başlık | İçeriği |
|---|---|
Content-Type | application/json; charset=utf-8 |
X-Webhook-Event | Olay adı; örneğin transfer.created. |
X-Webhook-Delivery-Id | Teslimat kimliği; tekrar gönderimde aynı kimlik. |
X-Company-Id | Bildirimin hedef hesap kimliği; tek başına kimlik doğrulama kanıtı değildir. |
X-Webhook-Signature | Ham JSON gövdesinin secret ile HMAC-SHA256 özeti, küçük harf hex. |
X-Webhook-Timestamp | Gönderim anı, Unix saniyesi. |
X-Webhook-Signature-V2 | t=TIMESTAMP,v1=HEX_SIGNATURE biçiminde imza. |
Secret tanımlanmış webhook'larda iki imza birlikte gönderilir. Yeni alıcılar
V2 kullanmalıdır. Doğrulama girdisi, araya nokta konularak birleştirilen
timestamp.deliveryId.rawBody dizisidir. rawBody JSON parse edilmeden önce
gelen özgün UTF-8 baytlarıdır. Parse edip yeniden JSON üretmek imzayı bozar.
Alıcı tarafında bir zaman toleransı seçin, sistem saatinizi eşitleyin ve tolerans dışındaki istekleri reddedin. Bu tolerans sizin güvenlik ayarınızdır; teslimat taahhüdü değildir. Tekrar denemeler güncel gönderim timestamp'iyle imzalanır.
Node.js doğrulama örneği
Aşağıdaki bağımsız fonksiyona HTTP gövdesini Buffer olarak, başlıkları ise
tekil metin değerleri olarak verin. maxAgeSeconds değerini kendi alıcı
yapılandırmanızdan sağlayın. Secret'ı önceden tanımladığınız webhook bağlantısına
göre seçin; doğrulanmamış X-Company-Id başlığını yetki kanıtı olarak kullanmayın.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyWebhookV2({
rawBody, secret, signatureV2, timestamp, deliveryId,
maxAgeSeconds, nowSeconds = Math.floor(Date.now() / 1000)
}) {
if (!Buffer.isBuffer(rawBody) || typeof secret !== 'string' || !secret) return false;
if (!Number.isFinite(maxAgeSeconds) || maxAgeSeconds <= 0) return false;
if (!Number.isFinite(nowSeconds)) return false;
if (typeof signatureV2 !== 'string' || typeof timestamp !== 'string'
|| typeof deliveryId !== 'string') return false;
const match = /^t=([0-9]+),v1=([0-9a-fA-F]{64})$/.exec(signatureV2);
if (!match || match[1] !== timestamp || !/^[0-9]+$/.test(deliveryId)) return false;
const sentAt = Number(timestamp);
if (!Number.isSafeInteger(sentAt) || Math.abs(nowSeconds - sentAt) > maxAgeSeconds) return false;
const expected = createHmac('sha256', secret)
.update(`${timestamp}.${deliveryId}.`, 'utf8')
.update(rawBody)
.digest();
const received = Buffer.from(match[2], 'hex');
return received.length === expected.length && timingSafeEqual(received, expected);
}
Bu fonksiyon yalnız imzayı doğrular. HTTP alıcınızda doğrulama başarılı olduktan
sonra JSON'u ayrıştırın; eventType ve eventId alanlarını kontrol edin.
İmzalı gövdedeki eventType değerini esas alın. V2 doğrulaması başarısız olduğunda
otomatik olarak eski imzaya geçmeyin. Eski alıcılar için V1 girdisi yalnız ham
gövde, algoritma yine HMAC-SHA256'dır; V1 kendi başına zaman kontrolü sağlamaz.
Olay kataloğu
| Olay | İçerik / kullanım |
|---|---|
transfer.created | Oluşan transferin transferId, pnr, pickUpTime, vehicleTypeId alanları ve data anlık görüntüsü. |
transfer.updated | transferId, changedFields, changes (field, oldValue, newValue) ve güncel data. |
transfer.status_changed | transferId, pnr, oldStatus, oldStatusName, newStatus, newStatusName, data. |
transfer.driver_assigned | transferId, pnr, driverId, sürücü adı/telefonu, vehiclePlate, data. |
transfer.driver_unassigned | transferId, pnr, driverId, vehicleId, transferGroupId, reassignedToTransferId, reason, data. |
transfer.merged | Grup oluşturuldu; transferGroupId, tetikleyen transferId, partnerSubAccountId, anchorPickupTime, groupedTotalFee. |
transfer.merge_added | Gruba rezervasyon eklendi veya tedarikçi atandı; changeType varsa buna göre ayrıştırın. |
transfer.merge_removed | Rezervasyon gruptan çıkarıldı; transferId, transferGroupId, removalReason gibi olay alanları. |
transfer.merge_updated | Grup değişti; örneğin changeType: "anchor_reassigned", oldAnchorId, newAnchorId, driverTransferred. |
transfer.unmerged | Rezervasyon ayrıldı veya grup çözüldü; transferId, transferGroupId, parentDissolved, removalReason. |
Olayların ortak alanları eventId, eventType, occurredAt, apiVersion.
Payload API başarı zarfına sarılmaz; success veya statusCode beklemeyin.
apiVersion olay tipine göre farklı olabilir; güncel ailelerde 2026-03 ve
2026-05 kullanılır. Grup olaylarının tamamında transfer data görüntüsü yoktur.
Transfer oluşturuldu örneği
Aşağıdaki sentetik gövde biçimi gösterir; imza örneği değildir.
{
"eventId": "564a8328-70ec-4548-9b6c-25514238b087",
"eventType": "transfer.created",
"occurredAt": "2030-06-01T09:00:00Z",
"apiVersion": "2026-03",
"transferId": "9d16b8fb-d15e-43a5-bcb8-76f6a72c1343",
"pnr": "YG-DEMO01",
"pickUpTime": "2030-06-15T09:00:00Z",
"vehicleTypeId": 1,
"data": {
"transferId": "9d16b8fb-d15e-43a5-bcb8-76f6a72c1343",
"pnr": "YG-DEMO01",
"status": 1,
"statusName": "Yeni Transfer",
"companyId": 100,
"companyName": "Örnek Partner",
"pickUpTime": "2030-06-15T09:00:00Z",
"dropOffTime": null,
"vehicleTypeId": 1,
"vehicleTypeName": "Sedan",
"pickUpLocation": "İstanbul Havalimanı",
"dropOffLocation": "Taksim",
"driverFullName": null,
"driverPhone": null,
"vehiclePlate": null,
"passengerCount": 1,
"flightNo": "TK1234",
"note": null,
"totalNetAmount": 1000,
"supplierFee": 0,
"currency": "TRY",
"totalVatAmount": 200,
"totalGrossAmount": 1200,
"linkedReservationId": null,
"linkedPnr": null,
"legOrder": null,
"passengers": [{
"firstName": "Demo",
"lastName": "Yolcu",
"email": "demo@example.com",
"isLead": true
}],
"vehiclePhotos": [],
"extras": [],
"mergeStatus": 0
}
}
data.passengers içindeki lider alanı isLead adını taşır; booking isteğinin
isLeadPassenger alanıyla karıştırmayın. Ekstra görüntülerinde id, type,
typeName, name, description, netAmount, vatRate, approvalStatus ve
approvalStatusName; araç fotoğraflarında url, isShowcase bulunur.
Sürücü, plaka, bağlı bacak veya koleksiyon alanlarının null/boş olmasını karşılayın.
Grup değişikliği örneği
{
"eventId": "fa50c12f-c3ef-487e-8a57-b4939420c45b",
"eventType": "transfer.merge_updated",
"occurredAt": "2030-06-01T09:10:00Z",
"apiVersion": "2026-05",
"transferGroupId": "f16c0755-c05e-43e7-af14-e0ac8312899e",
"changeType": "anchor_reassigned",
"oldAnchorId": "9d16b8fb-d15e-43a5-bcb8-76f6a72c1343",
"newAnchorId": "e0d3c532-2376-43f4-b8b8-b935f57a29a3",
"driverTransferred": true
}
Grubun ana rezervasyonu değiştiğinde eski/yeni kimlikleri güncelleyin.
Sürücü taşındıysa transfer.driver_unassigned ve transfer.driver_assigned
olayları da gelebilir. transfer.merge_added için changeType: "supplier_assigned"
tedarikçi atamasını ifade eder; rezervasyon eklenmesi olayında changeType
bulunmayabilir. transfer.unmerged.parentDissolved tek kayıt ayrılması ile grubun
çözülmesini ayırır. Her anchor iptalinde grup mutlaka dağılır diye varsaymayın.
Birleştirilmiş kayıtlarda bazı klasik oluşturma/güncelleme/durum bildirimleri
yerine grup olayları gelir. Her booking için mutlaka transfer.created bekleyen
bir akış kurmayın; HTTP oluşturma yanıtını kaydedin ve grup olaylarını işleyin.
Teslimat, tekrarlar ve sıralama
- HTTP
2xxteslimat başarısıdır.4xx,5xx, bağlantı hatası veya zaman aşımı başarısız teslimat sayılır ve tekrar denenebilir. - Teknik teslimat zaman aşımı 45 saniyedir. Yeniden deneme sayısı ve aralıkları ortam yapılandırmasına bağlıdır; alıcınızın işleyişini sabit teslim süresine bağlamayın.
- Aynı olay birden fazla kez gelebilir. Olayı, hedef bağlantı kapsamında
eventIdile; teslimatıX-Webhook-Delivery-Idile tekilleştirin. - Teslim sırasına güvenmeyin.
occurredAtve yerel işleme kaydıyla eski olayın yeni durumu ezmesini önleyin; belirsiz durumda rezervasyon detayını yeniden okuyun.
Önerilen alıcı akışı: İmzayı doğrula → olayı ve tekilleştirme anahtarını
kalıcı olarak kaydet/kuyruğa al → 2xx dön → işi arka planda işle.
Tekrar gelen ve daha önce kalıcı olarak kabul edilmiş olay için yeniden iş
üretmeden 2xx dönün. Kalıcı kayıt başarısızsa başarı yanıtı vermeyin.
İmza başarısızsa isteği reddedin ve gövdeyi işleme almayın.
İmza zaman toleransı, olay tekilleştirme kaydının saklama süresi değildir. Tekilleştirmeyi yalnız bellekte tutmayın ve yeni teslimat kimliğiyle gelen aynı iş olayının etkisini tekrar uygulamayın. Gizli anahtarı ve kişisel verileri loglara yazmayın.
Bildirim gelmezse
Listeleme ve detay ile düzenli mutabakat yapın. Webhook'un
henüz gelmemesi rezervasyonun oluşmadığını göstermez; yeni booking göndererek
telafi etmeyin. Destek kaydına webhook bağlantınız, eventId, teslimat kimliği,
zaman ve yanıt kodunu ekleyin. Secret, token veya ham yolcu verisi paylaşmayın.