İçeriğe geç
GlobalTrustIDdocs

Webhooks

Doğrulama sonucunu sunucunuza anında iletmek ve imzayı doğrulamak.

Webhook, doğrulama sonucu hazır olduğunda sizin sunucunuza yaptığımız bir POST isteğidir. Kullanıcı tarayıcıyı kapatsa bile sonuç size ulaşır.

Kurulum#

İki yol var ve ikisi bir arada kullanılabilir.

Kalıcı abonelik (önerilen)#

Panelde Entegrasyonlar → Webhooks bölümünden bir adres kaydedin ve hangi olaylara abone olacağınızı seçin. Her oturumda ayrıca webhook_url göndermenize gerek kalmaz; order.paid gibi bir oturuma bağlı olmayan olaylar da yalnızca bu yolla ulaşır.

Oturuma özel adres#

Oturumu açarken webhook_url verin:

Terminal
curl -X POST https://api.globaltrust.id/v1/verification/session \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "scope": ["identity.first_name", "identity.last_name"],
    "webhook_url": "https://sizin-siteniz.com/gti/webhook"
  }'

Adresiniz https olmalı ve herkese açık şekilde erişilebilir olmalıdır.

Gelen istek#

Gövde, oturumun sonucudur:

JSON
{
  "event": "verification.approved",
  "session_id": "01J8ZQ4X7K9M2N3P4Q5R6S7T8U",
  "status": "approved",
  "identity": {
    "identity.first_name": "Ayşe",
    "identity.last_name": "Yılmaz"
  },
  "created_at": "2026-09-14T21:30:00Z"
}

identity yalnızca oturum onaylandığında gelir. Reddedilen ya da süresi dolan bir oturumda alan hiç bulunmaz — status ile ayırt edin.

Başlıklar:

BaşlıkAçıklama
X-GTI-EventOlayın türü (aşağıdaki tabloya bakın).
X-GTI-Timestampİsteğin gönderildiği an (Unix saniye).
X-GTI-SignatureGövdenin HMAC imzası.
X-GTI-DeliveryTeslimatın tekil kimliği. Aynı olay tekrar gönderilirse bu değer aynı kalır — yineleme ayıklamak için kullanın.

Olaylar#

Panelden hangi olaylara abone olacağınızı seçersiniz:

OlayNe zaman
verification.approvedKullanıcı paylaşımı onayladı, veri hazır.
verification.declinedKullanıcı reddetti.
verification.expiredOturumun süresi doldu, kimse okutmadı.
login.completedOAuth girişi tamamlandı.
order.paidPaket ödemesi alındı.

İmzayı doğrulayın#

Bu adımı atlamayın. Webhook adresiniz herkese açıktır; imzayı doğrulamadan gelen gövdeye güvenirseniz, size sahte bir "onaylandı" gönderen herkes kimlik doğrulamasını atlatmış olur.

İmza şu biçimdedir:

Çıktı
t=1756713600,v1=5f3a9c...

v1, şu değerin HMAC-SHA256 özetidir:

Çıktı
{timestamp}.{ham gövde}

İmzalama sırrını panelden kendiniz alırsınız: Entegrasyonlar → Webhooks.

Node.js#

JavaScript
const crypto = require("crypto");

function dogrula(imzaBasligi, timestamp, hamGovde, sir) {
  const beklenen = crypto
    .createHmac("sha256", sir)
    .update(timestamp + "." + hamGovde)
    .digest("hex");

  const gelen = (imzaBasligi.match(/v1=([a-f0-9]+)/) || [])[1] || "";

  // Sabit zamanlı karşılaştırma: baytları tek tek sızdıran bir karşılaştırma
  // imzanın tahmin edilmesine yarar.
  const a = Buffer.from(beklenen, "hex");
  const b = Buffer.from(gelen, "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Go#

Go
func dogrula(imzaBasligi, timestamp string, hamGovde, sir []byte) bool {
    mac := hmac.New(sha256.New, sir)
    mac.Write([]byte(timestamp))
    mac.Write([]byte("."))
    mac.Write(hamGovde)
    beklenen := hex.EncodeToString(mac.Sum(nil))

    _, gelen, _ := strings.Cut(imzaBasligi, "v1=")
    return hmac.Equal([]byte(beklenen), []byte(gelen))
}

Python#

Python
import hmac, hashlib, re

def dogrula(imza_basligi: str, timestamp: str, ham_govde: bytes, sir: bytes) -> bool:
    beklenen = hmac.new(sir, timestamp.encode() + b"." + ham_govde, hashlib.sha256).hexdigest()
    eslesme = re.search(r"v1=([a-f0-9]+)", imza_basligi)
    return bool(eslesme) and hmac.compare_digest(beklenen, eslesme.group(1))

PHP#

PHP
<?php
function gtiWebhookDogrula(
    string $imzaBasligi,
    string $timestamp,
    string $hamGovde,
    string $sir
): bool {
    $beklenen = hash_hmac('sha256', $timestamp . '.' . $hamGovde, $sir);

    if (!preg_match('/v1=([a-f0-9]+)/', $imzaBasligi, $eslesme)) {
        return false;
    }

    // hash_equals sabit zamanlıdır. Normal === karşılaştırması ilk farklı
    // baytta durur; bu süre farkı imzanın adım adım tahmin edilmesine yarar.
    return hash_equals($beklenen, $eslesme[1]);
}

Tam bir uç nokta:

PHP
<?php
// Ham gövdeyi ÖNCE oku: $_POST'a bakmak gövdeyi ayrıştırır ve imza artık
// tutmaz. php://input tek seferlik olduğu için de en başta okunmalı.
$hamGovde  = file_get_contents('php://input');
$imza      = $_SERVER['HTTP_X_GTI_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_GTI_TIMESTAMP'] ?? '';

$sir = getenv('GTI_WEBHOOK_SECRET');

if (!gtiWebhookDogrula($imza, $timestamp, $hamGovde, $sir)) {
    http_response_code(401);
    exit;
}

// İmza doğru olsa bile eski bir isteği kabul etme (replay).
if (abs(time() - (int) $timestamp) > 300) {
    http_response_code(401);
    exit;
}

$olay = json_decode($hamGovde, true);

// İdempotan ol: aynı session_id ikinci kez gelirse hiçbir şey yapma.
if (!zatenIslendi($olay['session_id'])) {
    islemeAl($olay);
}

http_response_code(200);

Zaman damgasını da kontrol edin#

İmza doğru olsa bile çok eski bir isteği kabul etmeyin. Saldırgan geçerli bir isteği kaydedip sonra tekrar gönderebilir (replay). 5 dakikadan eski istekleri reddetmek makul bir sınırdır.

Yanıt vermeniz gerekenler#

  • Başarılıysa 2xx dönün.
  • Gövdeye ihtiyacımız yok, boş yanıt yeterlidir.
  • Hızlı yanıt verin. Ağır işleri (e-posta gönderme, rapor üretme) kuyruğa alın; webhook isteğini bekletmeyin.

2xx dışında bir yanıt alırsak veya bağlantı kurulamazsa yeniden deneriz.

Aynı olayın iki kez gelmesi#

Yeniden deneme yüzünden aynı sonuç size iki kez ulaşabilir. Bu yüzden işleyicinizi idempotent yazın: X-GTI-Delivery başlığını kaydedin ve daha önce işlediğiniz bir teslimat geldiğinde 2xx dönüp hiçbir şey yapmayın.

session_id yerine X-GTI-Delivery kullanın: bir oturum birden fazla olay üretebilir (verification.approved ardından login.completed gibi) ve session_id ile ayıklamak bunların ikincisini yutardı.

Yeniden deneme takvimi#

2xx dışında bir yanıt (ya da zaman aşımı) yeniden denemeyi tetikler:

DenemeBekleme
1hemen
21 dk
32 dk
44 dk
58 dk
616 dk

Beklemelere küçük bir rastgelelik eklenir. Bu olmasaydı, bir kesinti sırasında biriken bütün teslimatlar adres geri geldiği anda aynı saniyede yeniden denenir ve sunucunuzu ikinci kez devirirdi.

Toplam pencere yaklaşık yarım saattir. Altı denemeden sonra teslimat başarısız işaretlenir ve bırakılır: sürekli bozuk bir adresi günlerce denemek, hem sizin hem bizim tarafta gürültüden başka bir şey üretmez.

Test etme#

Geliştirme sırasında yerel sunucunuz internete kapalıdır. ngrok gibi bir tünel aracıyla geçici bir genel adres alıp webhook_url olarak verebilirsiniz.