- Beranda
- Webhook
- Verifikasi tanda tangan
Verifikasi tanda tangan
Pastikan webhook benar-benar dari Baiyar: header tanda tangan, toleransi waktu, dan contoh di Node.js dan Python.
Setiap request membawa Baiyar-Signature: t=<detik unix>,v1=<hex>. Nilai v1 adalah HMAC-SHA256 atas string <t>.<raw body request>, dengan secret endpoint sebagai kunci. Saat secret sedang dirotasi, header berisi dua nilai v1; terima request jika salah satunya cocok. Tolak timestamp t yang berbeda lebih dari 5 menit dari waktu server untuk mencegah replay request lama.
Verifikasi terhadap raw body, byte demi byte, sebelum kamu mem-parse-nya.
Node.js
Section titled “Node.js”import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyBaiyarSignature(header, rawBody, secret, toleranceSeconds = 300) { const parts = header.split(",").map((part) => part.trim()); const timestamp = Number(parts.find((part) => part.startsWith("t="))?.slice(2)); if (!Number.isInteger(timestamp)) return false; if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest(); return parts .filter((part) => part.startsWith("v1=")) .some((part) => { const given = Buffer.from(part.slice(3), "hex"); return given.length === expected.length && timingSafeEqual(given, expected); });}
// Express: simpan raw body untuk rute ini.app.post("/baiyar/webhook", express.raw({ type: "application/json" }), (req, res) => { const ok = verifyBaiyarSignature( req.get("Baiyar-Signature") ?? "", req.body.toString("utf8"), process.env.BAIYAR_WEBHOOK_SECRET, ); if (!ok) return res.sendStatus(400); const event = JSON.parse(req.body.toString("utf8")); // Tangani event.type, lalu jawab dengan cepat. res.sendStatus(200);});Python
Section titled “Python”import hashlibimport hmacimport time
def verify_baiyar_signature(header: str, raw_body: bytes, secret: str, tolerance_seconds: int = 300) -> bool: parts = [part.strip() for part in header.split(",")] timestamps = [part[2:] for part in parts if part.startswith("t=")] if not timestamps or not timestamps[0].isdigit(): return False timestamp = int(timestamps[0]) if abs(time.time() - timestamp) > tolerance_seconds: return False
signed = f"{timestamp}.".encode() + raw_body expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() return any( hmac.compare_digest(part[3:], expected) for part in parts if part.startswith("v1=") )
# Flask@app.post("/baiyar/webhook")def baiyar_webhook(): raw = request.get_data() # raw bytes, sebelum di-parse if not verify_baiyar_signature(request.headers.get("Baiyar-Signature", ""), raw, os.environ["BAIYAR_WEBHOOK_SECRET"]): return "", 400 event = request.get_json() return "", 200