Ana içeriğe geç

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​

  1. İnternetten erişilebilir HTTPS alıcı adresinizi hazırlayın.
  2. Hesabınız için webhook adresi, güçlü rastgele bir secret ve olay filtresi tanımlanmasını support@yolcugo.com üzerinden isteyin.
  3. Secret'ı yalnız sunucunuzda saklayın; API erişim token'ıyla aynı değer değildir.
  4. Test bildirimi isteyerek imza, kalıcı kayıt ve 2xx yanı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-Typeapplication/json; charset=utf-8
X-Webhook-EventOlay adı; örneğin transfer.created.
X-Webhook-Delivery-IdTeslimat kimliği; tekrar gönderimde aynı kimlik.
X-Company-IdBildirimin hedef hesap kimliği; tek başına kimlik doğrulama kanıtı değildir.
X-Webhook-SignatureHam JSON gövdesinin secret ile HMAC-SHA256 özeti, küçük harf hex.
X-Webhook-TimestampGönderim anı, Unix saniyesi.
X-Webhook-Signature-V2t=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.createdOluşan transferin transferId, pnr, pickUpTime, vehicleTypeId alanları ve data anlık görüntüsü.
transfer.updatedtransferId, changedFields, changes (field, oldValue, newValue) ve güncel data.
transfer.status_changedtransferId, pnr, oldStatus, oldStatusName, newStatus, newStatusName, data.
transfer.driver_assignedtransferId, pnr, driverId, sürücü adı/telefonu, vehiclePlate, data.
transfer.driver_unassignedtransferId, pnr, driverId, vehicleId, transferGroupId, reassignedToTransferId, reason, data.
transfer.mergedGrup oluşturuldu; transferGroupId, tetikleyen transferId, partnerSubAccountId, anchorPickupTime, groupedTotalFee.
transfer.merge_addedGruba rezervasyon eklendi veya tedarikçi atandı; changeType varsa buna göre ayrıştırın.
transfer.merge_removedRezervasyon gruptan çıkarıldı; transferId, transferGroupId, removalReason gibi olay alanları.
transfer.merge_updatedGrup değişti; örneğin changeType: "anchor_reassigned", oldAnchorId, newAnchorId, driverTransferred.
transfer.unmergedRezervasyon 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 2xx teslimat 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 eventId ile; teslimatı X-Webhook-Delivery-Id ile tekilleştirin.
  • Teslim sırasına güvenmeyin. occurredAt ve 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.