Dokümanlar/API ve Webhook'lar/Webhook'lar

Webhook'lar

Kendi tarafınızda bir HTTPS uç noktası açın; ilgilendiğiniz olaylar gerçekleştiğinde SecureCodingHub buraya imzalı bir JSON zarfı POST eder. Her teslimat HMAC-SHA256 ile imzalanır; böylece sahte bir kaynaktan değil bizden geldiğini doğrulayabilirsiniz.

Webhook'ları ne zaman kullanmalısınız

  • Bir geliştirici zorunlu bir atamayı tamamladığında (veya başarısız olduğunda) Jira ticket'ı açın.
  • Bir kategori tamamlama sertifikası verildiğinde İK kaydını güncelleyin.
  • CI'dan gelen bir SARIF çalıştırması eğitim atamalarını otomatik oluşturduğunda Slack'i bilgilendirin.
  • REST API'yi yoklamadan SCH durumunu kendi veri ambarınıza aynalayın.

Uç nokta oluşturma

1

Kurum admin'i olarak oturum açın ve Organization → Webhooks sayfasını açın.

2

+ Add endpoint düğmesine tıklayın. Teslimatları alacak public HTTPS URL'sini girin. Lokal geliştirme için ngrok / cloudflared tünelleri uygundur.

3

Abone olmak istediğiniz olayları işaretleyin (bunu daha sonra değiştirebilirsiniz).

4

Create endpoint düğmesine tıklayın. Bir whsec_… imzalama secret'ı yalnızca bir kez gösterilir. Secrets manager'ınıza kopyalayın — bu her teslimatı doğrulamak için kullanacağınız anahtardır.

Olay kataloğu

v1, dört olay türü yayınlar. Katalog ayrıca GET /api/public/v1/webhook-endpoints/events adresinde programatik olarak da mevcuttur.

OlayTetiklenme zamanıPayload data yapısı
assignment.createdBir atama oluşturulduğunda — admin tarafından manuel, public API üzerinden veya SARIF içeri alımının yan etkisi olarak.POST /assignments yanıtı ile aynı yapı.
assignment.completedBir kullanıcı kendisine verilen bir atamada %100 tamamlanmaya ulaştığında.Atama özetine ek olarak userId, userName, userEmail, completedAt.
sarif.ingestedBir SARIF çalıştırması işlendiğinde (üzerinden herhangi bir atama oluşturulup oluşturulmadığından bağımsız).Senkron POST /sarif yanıtı ile aynı yapı.
certificate.issuedBir kullanıcıya kategori tamamlama sertifikası otomatik verildiğinde.certificateId, certificateNumber, kullanıcı bilgileri, categoryId, categoryTitle, issuedAt.

İstek yapısı

Her teslimat şu header'lar ve bir JSON gövde ile bir POST isteğidir:

POST /your-endpoint HTTP/1.1
Host: example.com
Content-Type: application/json
User-Agent: SecureCodingHub-Webhooks/1.0
X-SecureCodingHub-Event: assignment.completed
X-SecureCodingHub-Event-Id: evt_8a3d…
X-SecureCodingHub-Delivery: 9c1b…
X-SecureCodingHub-Signature: t=1717003245,v1=ad8c…

{
  "id": "evt_8a3d…",
  "type": "assignment.completed",
  "createdAt": "2026-05-29T13:45:12.412Z",
  "orgId": "e188fc87-…",
  "data": { … }
}

Zarf tüm olay türlerinde aynıdır. Yalnızca type ve içerideki data değişir.

İmzayı doğrulama

X-SecureCodingHub-Signature header'ı t=<unix_seconds>,v1=<hex> formatına sahiptir. Doğrulamak için:

1

Header'dan t ve v1'i ayrıştırın.

2

HMAC-SHA256("<t>.<raw_body>", secret) değerini küçük harfli hex dizgisi olarak hesaplayın.

3

Sabit zamanda v1 ile karşılaştırın.

4

Zaman damgası sunucu saatinizden 5 dakikadan fazla saparsa reddedin (replay saldırılarına karşı korur).

Her zaman ham istek gövdesi üzerinde doğrulayın — JSON'u yeniden serileştirmek byte sırasını değiştirir ve imzayı geçersiz kılar.

Node.js örneği

import crypto from 'crypto'
import express from 'express'

const app = express()
const SECRET = process.env.SCH_WEBHOOK_SECRET

app.post('/webhook',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const header = req.header('X-SecureCodingHub-Signature') || ''
    const parts = Object.fromEntries(
      header.split(',').map(p => p.trim().split('='))
    )
    const ts = parseInt(parts.t, 10)
    const sig = parts.v1
    if (!ts || !sig) return res.status(400).send('bad signature header')
    if (Math.abs(Date.now() / 1000 - ts) > 300) {
      return res.status(400).send('stale')
    }
    const expected = crypto
      .createHmac('sha256', SECRET)
      .update(`${ts}.${req.body.toString('utf8')}`)
      .digest('hex')
    const ok = crypto.timingSafeEqual(
      Buffer.from(expected, 'hex'),
      Buffer.from(sig, 'hex')
    )
    if (!ok) return res.status(401).send('bad signature')

    const event = JSON.parse(req.body.toString('utf8'))
    console.log(`✓ ${event.type} ${event.id}`)
    res.json({ received: true })
  })

app.listen(7777)

Python (Flask) örneği

import hashlib, hmac, os, time
from flask import Flask, request, abort

SECRET = os.environ['SCH_WEBHOOK_SECRET'].encode()
app = Flask(__name__)

@app.post('/webhook')
def webhook():
    header = request.headers.get('X-SecureCodingHub-Signature', '')
    parts = dict(p.split('=') for p in header.split(','))
    try:
        ts = int(parts['t']); sig = parts['v1']
    except (KeyError, ValueError):
        abort(400, 'bad signature header')
    if abs(time.time() - ts) > 300:
        abort(400, 'stale')
    raw = request.get_data()  # raw bytes
    expected = hmac.new(SECRET, f"{ts}.".encode() + raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, sig):
        abort(401, 'bad signature')
    event = request.get_json()
    print('OK', event['type'], event['id'])
    return {'received': True}

Go örneği

package main

import (
  "crypto/hmac"; "crypto/sha256"; "encoding/hex"; "io"
  "net/http"; "os"; "strconv"; "strings"; "time"
)

var secret = []byte(os.Getenv("SCH_WEBHOOK_SECRET"))

func webhook(w http.ResponseWriter, r *http.Request) {
  header := r.Header.Get("X-SecureCodingHub-Signature")
  var ts int64; var sig string
  for _, p := range strings.Split(header, ",") {
    kv := strings.SplitN(strings.TrimSpace(p), "=", 2)
    if len(kv) != 2 { continue }
    if kv[0] == "t" { ts, _ = strconv.ParseInt(kv[1], 10, 64) }
    if kv[0] == "v1" { sig = kv[1] }
  }
  if ts == 0 || sig == "" { http.Error(w, "bad header", 400); return }
  if abs(time.Now().Unix() - ts) > 300 { http.Error(w, "stale", 400); return }
  body, _ := io.ReadAll(r.Body)
  mac := hmac.New(sha256.New, secret)
  mac.Write([]byte(strconv.FormatInt(ts, 10) + "."))
  mac.Write(body)
  if !hmac.Equal(mac.Sum(nil), mustHex(sig)) {
    http.Error(w, "bad signature", 401); return
  }
  w.Write([]byte(`{"received":true}`))
}

func mustHex(s string) []byte { b, _ := hex.DecodeString(s); return b }
func abs(x int64) int64 { if x < 0 { return -x }; return x }

func main() {
  http.HandleFunc("/webhook", webhook)
  http.ListenAndServe(":7777", nil)
}

Teslimat garantileri ve yeniden denemeler

Webhook teslimatları sıraya alınır ve bir arka plan worker'ı tarafından eşzamansız olarak gönderilir. en az bir kez teslim edilirler — nadir durumlarda (zaman aşımı, yanıt yarışı) aynı olay iki kez teslim edilebilir. Tekrarları ayıklamak için zarftaki id alanını (ve X-SecureCodingHub-Event-Id header'ını) kullanın.

Uç noktanız 15 saniye içinde herhangi bir 2xx durumu ile yanıt vermelidir. Bunun dışındaki her şey başarısızlık sayılır ve üstel geri çekilme ile yeniden denemeyi tetikler:

DenemeBir öncekinden sonraki süre
1 (ilk)kaynak olaydan hemen sonra sıraya alınır
2+1 dakika
3+5 dakika
4+30 dakika
5 (son)+2 saat

5. başarısızlıktan sonra teslimat exhausted olarak işaretlenir ve uç nokta otomatik olarak devre dışı bırakılır. Başarısızlıkları ve otomatik devre dışı bırakma zaman damgasını Organization → Webhooks → View deliveries altında görebilirsiniz.

En iyi uygulamalar

  • Her zaman imzayı doğrulayın. Aksi halde URL'nizi tahmin eden herhangi biri pipeline'ınıza sahte olaylar yağdırabilir.
  • Hızlı yanıt verin. İşi bir kuyruğa devredin ve milisaniyeler içinde 200 dönün — handler içinde senkron işleme yapmayın.
  • event.id ile tekrar ayıklayın. Görülmüş ID'leri en az 7 gün saklayın.
  • IP tabanlı filtrelemeye güvenmeyin. Cloudflare çıkış IP'leri önceden haber verilmeksizin değişebilir; imza tek kararlı güven çapasıdır.
  • Canlıya almadan önce test edin. Yerel bir alıcıyı dışarı açmak için cloudflared (cloudflared tunnel --url http://localhost:7777) veya ngrok kullanın, ardından uçtan uca bir teslimatı yakalamak için API üzerinden bir olay tetikleyin.

Son teslimatları inceleme

Admin konsolu, uç nokta başına en son 50 teslimatı durum, deneme sayısı, yanıt kodu ve varsa hata mesajı ile listeler. Programatik erişim şu adreste mevcuttur:

GET /api/public/v1/webhook-endpoints/{id}/deliveries

Bu işlem webhook:manage kapsamını gerektirir.