İçeriğe geç
Sursa Developers

Webhook’ları alma

Webhook, sisteminize Sursa’daki bir değişikliği gerçekleştiği anda bildirir: bir kart yayımlanır, doğrulanır ya da arşivlenir, bir soru yanıtsız kalır. Sursa, olayı içeren bir HTTPS POST isteğini kaydettiğiniz adrese gönderir. Bu kılavuz, yeniden denemelerde, sıranın değiştiği ve teslimlerin yinelendiği durumlarda doğru çalışan bir alıcı oluşturur.

Her olayın ne anlama geldiği ve neler içerdiği için Webhook’lara genel bakış sayfasına ve API başvurusundaki olay kataloğuna bakın.

1. Webhook kaydedin

Bir yönetici webhook’ları Sursa’da Administration → Integrations → Webhooks and API (yönetim → entegrasyonlar → webhook’lar ve API) bölümünde ya da admin rolüne ve webhooks:manage kapsamına sahip bir anahtarla POST /v1/webhooks çağrısıyla kaydeder:

curl -X POST https://api.sursa.com/v1/webhooks \
  -H "Authorization: Bearer $SURSA_ADMIN_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/sursa",
    "description": "Sync verified cards to the help center",
    "events": ["card.published", "card.updated", "card.verified", "card.archived"]
  }'

Yanıt, imzalama gizli değerini whsec_ ile başlayan secret alanında içerir. Bu değer bir kez gösterilir. Onu alıcınızla birlikte, örneğin SURSA_WEBHOOK_SECRET ortam değişkeninde saklayın.

Adres https kullanmalı ve herkese açık IP adreslerine çözümlenmelidir: özel, geri döngü (loopback) ve diğer ayrılmış ağlardaki adresler hem kayıt sırasında hem de her teslimde reddedilir. Kendi bilgisayarınızda geliştirme yapmak için yerel alıcınızı, ona herkese açık bir HTTPS adresi veren bir tünel hizmetiyle dışarıya açın.

İstediğiniz zaman POST /v1/webhooks/{webhook_id}/test ile bir test olayı gönderin. Yanıt, alıcınızın ne yanıt verdiğini gösterir.

2. Bir teslimin görünümü

POST /sursa HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
webhook-id: evt_0123456789abcdef0123456789abcdef
webhook-timestamp: 1791194400
webhook-signature: v1,l1LLl/aEl4dQq7Geb383KbyNeZgs7VZpiw6iuoJgunI=
X-KB-Signature: t=1791194400,v1=82aa6b27378d89d7d880d17c3fe96268598443ae0e64d059c0c41e94e5d3ffbe
X-KB-Event-Id: evt_0123456789abcdef0123456789abcdef
X-KB-Event: card.verified
X-KB-Delivery: 99812
X-KB-Attempt: 1
X-KB-Sequence: 1042

{"id":"evt_0123456789abcdef0123456789abcdef","type":"card.verified","created_at":"2026-10-05T10:00:00Z","org_id":1,"api_version":"1.60.0","sequence":1042,"object":{"type":"card","id":"c1"},"payload":"full","data":{"card":{"id":"c1"}},"test":false}
BaşlıkAnlamı
webhook-idOlayın kimliği. Her yeniden denemede aynıdır. Yinelenenleri ayıklamak için kullanın.
webhook-timestampBu denemenin imzalandığı zaman, Unix saniyesi olarak.
webhook-signatureStandard Webhooks biçimindeki imza.
X-KB-SignatureAynı teslimin eski Sursa biçimindeki imzası.
X-KB-EventOlay türü.
X-KB-Attempt1’den başlayan deneme numarası.
X-KB-SequenceBu teslimin, webhook’un teslim sırasındaki konumu.

Başlık adlarında büyük-küçük harf ayrımı yoktur; kullandığınız çatı (framework) bunları X-Kb-Signature ya da x-kb-signature olarak gösterebilir.

Gövde olayın kendisidir. data alanı türe bağlıdır. thin veri yüküne ayarlanmış bir webhook olayı data olmadan alır; bunun yerine nesneyi kendi anahtarınızla API üzerinden okuyun.

3. İmzayı doğrulayın

Güvenmeden önce her teslimin imzasını kontrol edin. İki biçim de gövdenin baytlarını olduğu gibi imzalar; bu yüzden herhangi bir JSON ayrıştırmasından önce ham gövdeyi okuyun: JSON’u yeniden serileştirmek baytları değiştirir ve denetimi bozar.

Biçimİmzalanan dizeAnahtarİmza
Standard Webhooks (webhook-signature)<webhook-id>.<webhook-timestamp>.<body>Gizli değerin whsec_ sonrasındaki kısmı, base64 ile çözülmüş hâliv1, + HMAC-SHA256 değerinin base64 kodlaması
Eski biçim (X-KB-Signature)<t>.<body>Gizli değerin tamamı, metin olarakv1= + HMAC-SHA256 değerinin onaltılık gösterimi

Herhangi birini doğrulamak yeterlidir. Standard Webhooks başlığını tercih edin: aynı denetim birçok hazır kütüphanede bulunur. Gizli değer yenilenirken her başlık, geçerli her gizli değer için bir imza taşır (webhook-signature içinde boşlukla ayrılmış, X-KB-Signature içinde yinelenen v1=). İmzalardan herhangi biri eşleşirse teslim geçerlidir.

Ayrıca saatinizden 5 dakikadan fazla sapan bir zaman damgasını reddedin. Bu, birinin kaydedilmiş bir teslimi daha sonra yeniden göndermesini engeller.

# Check a saved delivery by hand: body.json holds the raw body, the values come from its headers.
SECRET="whsec_…"; WEBHOOK_ID="evt_…"; TIMESTAMP="1791194400"

# Standard Webhooks: compare with the value after "v1," in webhook-signature
KEY=$(printf '%s' "${SECRET#whsec_}" | tr '_-' '/+')
while [ $(( ${#KEY} % 4 )) -ne 0 ]; do KEY="$KEY="; done
KEY_HEX=$(printf '%s' "$KEY" | openssl base64 -d -A | xxd -p | tr -d '\n')
{ printf '%s.%s.' "$WEBHOOK_ID" "$TIMESTAMP"; cat body.json; } \
  | openssl dgst -sha256 -mac HMAC -macopt "hexkey:$KEY_HEX" -binary | openssl base64 -A

# Earlier format: compare with the value after "v1=" in X-KB-Signature
{ printf '%s.' "$TIMESTAMP"; cat body.json; } | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1

İmza doğrulama kodunuzu test edin

Kodunuzu gerçek teslimlere bağlamadan önce bu değerlerle çalıştırın. Saat zaman damgasına ayarlandığında iki denetim de başarılı olmalı, gövdenin bir baytı değiştiğinde ise başarısız olmalıdır.

GirdiDeğer
Gizli değerwhsec_test_secret_0123456789abcdef
webhook-idevt_0123456789abcdef0123456789abcdef
webhook-timestamp ve t1791194400
Gövde{"id":"evt_0123456789abcdef0123456789abcdef","type":"card.verified","created_at":"2026-10-05T10:00:00Z","org_id":1,"data":{"card":{"id":"c1"}}}
webhook-signaturev1,+xxWSXH6weQJ/MVg4+N78a1Dj4y/AQ4p94+PRNzpECg=
X-KB-Signaturet=1791194400,v1=ce26a19cb63cd3ae620217bf7688e1df1044fa7d59d9ae148d215f0292b768dc

4. Hızlı yanıt verin, işi sonra yapın

Sursa yanıtınız için en fazla 10 saniye bekler. Herhangi bir 2xx durumu teslimin başarılı olduğu anlamına gelir; yönlendirme, zaman aşımı ya da kapanan bağlantı dahil diğer her şey teslimin başarısız olduğu ve yeniden deneneceği anlamına gelir.

Bu yüzden alıcı yalnızca doğrulamalı, olayı kaydetmeli ve yanıt vermelidir. Asıl işi daha sonra kendi kuyruğunuzdan yapın.

// Node.js 20+, no framework. verifyStandard and verifyLegacy come from step 3.
import http from "node:http";

const SECRET = process.env.SURSA_WEBHOOK_SECRET;

http
  .createServer(async (req, res) => {
    const parts = [];
    for await (const part of req) parts.push(part);
    const body = Buffer.concat(parts); // the raw bytes, before any JSON parsing

    const valid = req.headers["webhook-signature"]
      ? verifyStandard(SECRET, req.headers, body)
      : verifyLegacy(SECRET, req.headers["x-kb-signature"], body);
    if (!valid) {
      res.writeHead(400).end();
      return;
    }

    const event = JSON.parse(body);
    await saveEventOnce(event); // insert into your table or queue, keyed by event.id; ignore a duplicate
    res.writeHead(204).end();
  })
  .listen(3000);

2xx yanıtını yalnızca olay güvenle saklandıktan sonra verin. Önce yanıt verir ve ardından çökerseniz olay sizin için kaybolur, çünkü Sursa onu zaten teslim edilmiş saymıştır.

5. Yeniden denemeler

Başarısız bir teslim giderek uzayan aralarla yeniden denenir: yaklaşık 1 dakika, 5 dakika, 30 dakika, 2 saat, 5 saat, 10 saat, 24 saat ve 24 saat sonra; yaklaşık üç güne yayılan dokuz deneme. Birçok webhook’tan gelen yeniden denemeler aynı anda gelmesin diye her ara %20’ye kadar değişir.

  • Retry-After içeren bir 429 ya da 503 yanıtı, sonraki denemeyi en az o süre kadar, en fazla 24 saat sonrasına erteler. Sisteminiz aşırı yüklendiğinde ya da bakımdayken bunu kullanın.
  • 410 Gone yanıtı webhook’u devre dışı bırakır: bunu yalnızca o adrese artık teslim istemediğinizde gönderin.
  • Art arda 10 başarısız denemeden sonra webhook duraklatılır ve kuruluşun yöneticilerine bildirilir. Olaylar birikmeye devam eder ve webhook yönetim arayüzünde ya da POST /v1/webhooks/{webhook_id}/resume ile yeniden etkinleştirildiğinde sırayla teslim edilir.
  • Son denemeden sonra teslim exhausted olarak işaretlenir. Onu elle yeniden gönderebilirsiniz.

Bir webhook’un teslimleri; her deneme, yanıt kodunuz ve süresiyle birlikte GET /v1/webhooks/{webhook_id}/deliveries tarafından ve yönetim arayüzünde listelenir. POST /v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver bir teslimi yeniden gönderir. Yeniden teslimin webhook-id değeri aynıdır.

6. Sıra ve yinelenenler

  • Sıra. Bir webhook’a yapılan teslimler sequence sırasına göre kesin olarak birbiri ardına gider. Bir teslim yeniden denenirken sonrakiler bekler. Farklı webhook’lar birbirini beklemez.
  • Yinelenenler. Aynı olay birden fazla kez gelebilir; örneğin yanıtınız geri dönerken kaybolduğunda. İşlediğiniz her olayın webhook-id değerini (gövdedeki id de aynıdır) saklayın ve daha önce gördüğünüz bir olayı atlayın.
  • Durumlarla düşünme. Aynı nesnedeki değişiklikler için created_at ya da sequence değerini sakladığınız değerle karşılaştırın ve elinizdeki durumdan eski bir olayı yok sayın. Emin olmadığınızda nesnenin güncel hâlini API üzerinden okuyun: olay size bir şeyin değiştiğini söyler, API ise şu anki hâlini.

7. Olay beslemesiyle mutabakat yapın

Her olay, ona abone olan bir webhook olsun ya da olmasın, olay beslemesinde de 30 gün saklanır. Alıcınız yeniden denemelerin kapsadığından daha uzun süre çalışmadıysa ya da hiçbir şeyin kaçırılmadığını kontrol etmek istiyorsanız beslemeyi admin rolüne ve webhooks:manage kapsamına sahip bir anahtarla okuyun:

curl -G https://api.sursa.com/v1/webhook-events \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  --data-urlencode "type=card.*" \
  --data-urlencode "created_since=2026-10-20T00:00:00Z" \
  --data-urlencode "limit=200"

Beslemedeki bir olay, teslimdekiyle aynı id değerine ve biçime sahiptir; yalnızca sequence, payload ve test teslim alanları yoktur. Alıcınız daha önce gördüğü olayları atladığı için mutabakatı istediğiniz sıklıkta, örneğin saatte bir çalıştırabilirsiniz.

GET /v1/webhook-events/{event_id} tek bir olayı kimliğiyle okur.

8. Gizli değeri yenileyin

POST /v1/webhooks/{webhook_id}/rotate-secret yeni bir gizli değer oluşturur ve öncekini, varsayılan olarak 24 saat, en fazla 7 gün süren bir örtüşme süresi boyunca geçerli tutar. Örtüşme süresince her teslim iki gizli değerle de imzalanır.

  1. Gizli değeri yenileyin ve yeni gizli değeri alıcınızda eskisinin yanına kaydedin.
  2. Bir teslimi, iki gizli değerden biriyle doğrulanıyorsa kabul edin.
  3. Örtüşme süresi bittikten sonra eski gizli değeri kaldırın.

Eski gizli değer sızdıysa beklemek yerine örtüşme süresini hemen sonlandırın: POST /v1/webhooks/{webhook_id}/revoke-previous-secret ile ya da gizli değeri "overlap_hours": 0 ile yenileyerek.

Kontrol listesi

  • İmzalardan birini ham gövde üzerinde doğrulayın ve zaman damgasını kontrol edin.
  • Olayı webhook-id anahtarıyla saklayın, ardından 10 saniye içinde 2xx yanıtı verin.
  • Yinelenen ve eskimiş olayları atlayın; önemli olduğunda nesnenin güncel hâlini API üzerinden okuyun.
  • Sursa’nın teslimleri yavaşlatmasını istediğinizde Retry-After ile 429 ya da 503 yanıtı verin; hiçbir zaman yanlışlıkla 410 yanıtı vermeyin.
  • Bilmediğiniz olay türlerini ve alanları atlayın.
  • Olay beslemesiyle düzenli aralıklarla mutabakat yapın.