Uygulamayı aç
Moonborn — Developers

Webhook signature verification

`X-Moonborn-Signature` HMAC'ini doğrula, replay'leri reddet ve secret rotation'dan sağ çık.

Her webhook delivery'i şu formatta bir X-Moonborn-Signature header taşır:

X-Moonborn-Signature: t=1747497600,v1=2c4f8a...

t imzanın oluşturulduğu Unix timestamp; v1 signing secret ile anahtarlanmış {t}.{rawBody}'nin hex-encoded HMAC-SHA256'sı.

Doğrula (Node.js)

import { createHmac, timingSafeEqual } from 'node:crypto';
 
const MAX_AGE_SECONDS = 300;
 
export function verify(
  rawBody: string,
  header: string,
  secret: string,
): boolean {
  const parts = Object.fromEntries(
    header.split(',').map((p) => p.split('=', 2) as [string, string]),
  );
  const t = Number(parts['t']);
  const v1 = parts['v1'];
  if (!Number.isFinite(t) || typeof v1 !== 'string') return false;
  // Replay window guard.
  if (Math.abs(Date.now() / 1000 - t) > MAX_AGE_SECONDS) return false;
  const signed = `${t}.${rawBody}`;
  const expected = createHmac('sha256', secret).update(signed).digest('hex');
  return timingSafeEqual(
    Buffer.from(v1, 'hex'),
    Buffer.from(expected, 'hex'),
  );
}

Not: yeniden serialize edilmiş bir JSON nesnesi değil, ham body byte'larını kullan — imza Moonborn'un gönderdiği tam byte'lar üzerinde.

Doğrula (Python)

import hmac, hashlib, time
 
MAX_AGE = 300
 
def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t = int(parts["t"])
    v1 = parts["v1"]
    if abs(time.time() - t) > MAX_AGE:
        return False
    signed = f"{t}.{raw_body.decode()}".encode()
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(v1, expected)

Secret rotation grace

Rotation sırasında, delivery'ler iki v1= entry'si (eski + yeni) taşıyabilir. Eşleşen herhangi bir aday'ı geçerli ele al:

const candidates = header.split(',').filter((p) => p.startsWith('v1='));
const ok = candidates.some((c) => {
  const v = c.slice(3);
  return timingSafeEqual(
    Buffer.from(v, 'hex'),
    Buffer.from(expected, 'hex'),
  );
});

Grace period default'u: api.webhooks.secret_rotation_grace_minutes = 60.

Timestamp guard neden

t-yaşı reddi olmadan, geçerli bir delivery'i yakalayan bir saldırgan onu sonsuza dek replay edebilir. Beş dakika cömert bir sınır; receiver'ın bölgeselse 60s'e sıkılaştır.

Tarif

Webhook'lar Team+; doğrulama mantığı her tier'da aynı.

İlgili