Webhook doğrulama
Bir alarm webhook'unun gerçekten Orbtrace örneğinizden geldiğini doğrulayın — HMAC-SHA256 imzası, zaman-damgası kayma kontrolü ve idempotency-key tekilleştirmesi. Node, Python, Go ve Java için alıcı kod parçaları.
Bir Webhook alarm kanalı eklediğinizde, Orbtrace her teslimi imzalar; böylece alıcınız bunun gerçekten sizin örneğinizden geldiğini kanıtlayabilir (genel uç noktanıza yapılan sahte bir istek değil). Şema, sektör standardı Stripe tarzı HMAC'tir — alıcınız bunu standart kütüphaneyle doğrular, Orbtrace SDK'sı gerekmez.
İmza
Her istek üç başlık taşır:
| Başlık | Anlamı |
|---|---|
X-Orbtrace-Signature | t=<unix-saniye>,v1=<hex> — zaman damgası ve HMAC. |
X-Orbtrace-Idempotency-Key | Alarm örneği başına sabit — yeniden denemeleri buna göre tekilleştirin. |
X-Orbtrace-Delivery-Attempt | Teslim deneme numarası (1 = ilk deneme). |
İmzalanan dize; zaman damgası, bir . karakteri, ardından ham istek gövdesidir:
imzalanan = "<unix-saniye>" + "." + <ham_gövde>
v1 = HMAC_SHA256(imzalama_sırrı, imzalanan) // küçük harf hexAlıcınız sırasıyla ŞUNLARI yapmalıdır:
t'yi ayrıştırın ve saatinizden 5 dakikadan fazla saparsa reddedin — yakalanmış bir gövdenin tekrarını (replay) bu öldürür.HMAC-SHA256(sır, t + "." + ham_gövde)değerini yeniden hesaplayın vev1ile sabit zamanlı karşılaştırın.X-Orbtrace-Idempotency-Keyüzerinden tekilleştirin ki yeniden denenen bir teslim iki kez işlenmesin.
Gövdenin ham baytlarına karşı doğrulayın, herhangi bir JSON ayrıştır/yeniden-serileştir işleminden önce — yeniden kodlama boşlukları değiştirir ve HMAC'ı bozar.
Alıcı kod parçaları
Node.js
import crypto from 'node:crypto';
function verify(rawBody, headers, secret) {
const [tPart, v1Part] = headers['x-orbtrace-signature'].split(',');
const t = tPart.slice(2); // "t=" kırp
const sig = v1Part.slice(3); // "v1=" kırp
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) throw new Error('stale');
const expected = crypto.createHmac('sha256', secret)
.update(`${t}.${rawBody}`).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!ok) throw new Error('bad signature');
}Python
import hashlib, hmac, time
def verify(raw_body: bytes, headers, secret: str):
t, v1 = (p.split("=", 1)[1] for p in headers["X-Orbtrace-Signature"].split(","))
if abs(time.time() - int(t)) > 300:
raise ValueError("stale")
signed = f"{t}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
if not hmac.compare_digest(v1, expected):
raise ValueError("bad signature")Go
func Verify(rawBody []byte, header, secret string) error {
var t, v1 string
for _, p := range strings.Split(header, ",") {
kv := strings.SplitN(p, "=", 2)
switch kv[0] {
case "t":
t = kv[1]
case "v1":
v1 = kv[1]
}
}
ts, _ := strconv.ParseInt(t, 10, 64)
if math.Abs(float64(time.Now().Unix()-ts)) > 300 {
return errors.New("stale")
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(t + "."))
mac.Write(rawBody)
expected := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(v1), []byte(expected)) {
return errors.New("bad signature")
}
return nil
}Java
String[] parts = header.split(",");
String t = parts[0].substring(2); // "t="
String v1 = parts[1].substring(3); // "v1="
if (Math.abs(Instant.now().getEpochSecond() - Long.parseLong(t)) > 300)
throw new SecurityException("stale");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(UTF_8), "HmacSHA256"));
mac.update((t + ".").getBytes(UTF_8));
byte[] expected = mac.doFinal(rawBody); // rawBody = ham baytlar
if (!MessageDigest.isEqual(fromHex(v1), expected))
throw new SecurityException("bad signature");Idempotency (tekillik)
Orbtrace başarısız teslimleri yeniden dener, dolayısıyla aynı alarm birden fazla gelebilir. X-Orbtrace-Idempotency-Key bir alarm örneği için yeniden denemeler boyunca sabittir — işlediğiniz anahtarları saklayın ve tekrarları atın. X-Orbtrace-Delivery-Attempt başlığı bilgilendiricidir (hangi yeniden denemenin sonunda ulaştığını loglamak için kullanışlı).