Lewati ke konten

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.

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);
});
import hashlib
import hmac
import 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