Verifikasi signature webhook

Signature membuktikan bahwa pengirim memiliki secret endpoint webhook Anda dan bahwa body tidak berubah. Verifikasi ini harus berhasil sebelum body dipakai untuk memperbarui pesanan.

Data yang dibutuhkan

InputSumber
Raw bodyBytes request HTTP asli, sebelum JSON parsing atau normalisasi.
TimestampHeader x-valcentra-timestamp, berupa Unix timestamp dalam detik.
SignatureHeader x-valcentra-signature, format v1= diikuti 64 digit hex huruf kecil.
SecretSecret endpoint yang disimpan di backend, bukan API key.

Jangan menghitung signature dari JSON.stringify(request.body). Spasi, urutan properti, dan encoding yang berubah menghasilkan bytes berbeda meskipun JSON-nya bermakna sama.

Cara signature dihitung

Pesan yang ditandatangani
UTF-8(timestamp + ".") + raw_body_bytes

Valcentra menghitung HMAC-SHA256 atas pesan tersebut dengan secret endpoint. Hasil digest diubah ke lowercase hex, lalu diberi awalan v1=.

Receiver membandingkan digest secara constant-time, bukan dengan perbandingan string biasa. Receiver juga menolak timestamp yang berbeda lebih dari 300 detik (5 menit) dari waktu saat ini, baik terlalu lama maupun terlalu jauh di masa depan.

Timestamp header adalah waktu penandatanganan delivery, bukan event.created_at. Event lama yang dikirim ulang dapat memiliki timestamp pengiriman baru dan tetap valid.

Contoh TypeScript untuk backend Node.js/Bun

Fungsi berikut hanya memverifikasi signature. Integrasikan dengan mekanisme raw body framework backend Anda; atur batas ukuran request sebelum membaca body. Contoh ini tidak menggantikan validasi schema, deduplikasi, atau transaksi pesanan.

verify-webhook.ts
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyWebhook({
	rawBody,
	timestampHeader,
	signatureHeader,
	secret,
	nowSeconds = Math.floor(Date.now() / 1000),
}: {
	rawBody: Uint8Array;
	timestampHeader: string | null;
	signatureHeader: string | null;
	secret: string;
	nowSeconds?: number;
}): boolean {
	if (!secret || !timestampHeader || !signatureHeader) return false;
	if (!/^[0-9]+$/.test(timestampHeader)) return false;

	const timestamp = Number(timestampHeader);
	if (!Number.isSafeInteger(timestamp) || !Number.isSafeInteger(nowSeconds)) {
		return false;
	}
	if (Math.abs(nowSeconds - timestamp) > 300) return false;
	if (!/^v1=[0-9a-f]{64}$/.test(signatureHeader)) return false;

	const expected = createHmac("sha256", secret)
		.update(timestampHeader + ".", "utf8")
		.update(rawBody)
		.digest();
	const received = Buffer.from(signatureHeader.slice(3), "hex");

	return received.length === expected.length && timingSafeEqual(received, expected);
}

Pada receiver Anda:

  1. Baca kedua header dan raw body dengan batas ukuran yang sesuai.
  2. Panggil verifyWebhook menggunakan secret endpoint dari secret manager.
  3. Jika hasilnya false, tolak request tanpa perubahan pesanan.
  4. Jika hasilnya true, parse JSON dan validasi field sesuai schema event.
  5. Simpan event secara persisten dan deduplikasi sebelum mengakui sukses, seperti pada panduan webhook.

Signature valid belum berarti event sudah aman diproses

Anda masih harus memeriksa schema, livemode, payment ID, jumlah, mata uang, referensi pesanan, dan apakah event sudah diproses. Signature tidak mencegah efek bisnis ganda tanpa deduplikasi.

Jika verifikasi gagal

Penyebab yang umumPemeriksaan
Secret salahCocokkan endpoint, project, dan environment tanpa mencetak secret.
Body berubahNonaktifkan JSON parsing sebelum verifikasi pada route receiver; jangan stringify ulang body.
Timestamp di luar toleransiSinkronkan jam server dan periksa bahwa timestamp diperlakukan sebagai detik, bukan milidetik.
Header hilang atau berubahPeriksa proxy dan middleware yang meneruskan request.
Secret baru belum dipasangPerbarui konfigurasi receiver setelah pergantian secret.

Jangan menonaktifkan verifikasi untuk membuat delivery terlihat sukses. Catat kategori kegagalan saja, tanpa signature lengkap, secret, raw body, atau credential.

Saat mengganti secret

Jika pergantian secret melibatkan masa transisi, receiver dapat memverifikasi dengan secret aktif dan secret sebelumnya yang secara eksplisit diizinkan. Setiap kandidat tetap harus melewati pemeriksaan timestamp dan constant-time comparison. Hapus secret lama setelah transisi selesai.

Ini adalah strategi receiver Anda, bukan jaminan bahwa Valcentra mengirim beberapa signature atau menyediakan masa berlaku secret lama tertentu. Untuk secret yang bocor, jangan mempertahankan secret lama sebagai fallback.

Lanjutkan ke: webhook untuk pemrosesan event, atau retry webhook untuk diagnosis delivery gagal.

On this page