> ## Documentation Index
> Fetch the complete documentation index at: https://docs.simplus.id/llms.txt
> Use this file to discover all available pages before exploring further.

# Verifikasi Signature

> Cara kerja signature webhook Simplus dan cara memverifikasinya, dengan atau tanpa library Svix.

Verifikasi signature di setiap pesan webhook sebelum memprosesnya, supaya pesan palsu tidak bisa masuk ke sistem kamu.

## Header webhook

Setiap pesan webhook membawa tiga header:

| Header | Isi |
| - | - |
| `svix-id` | ID unik pesan. Nilainya sama walaupun pesan dikirim ulang (retry). |
| `svix-timestamp` | Waktu pengiriman dalam Unix timestamp (detik). |
| `svix-signature` | Daftar signature pesan, dibuat dari Signing Secret endpoint kamu. |

## Cara termudah: pakai library Svix

Library resmi Svix (Node.js, PHP, Python, dan bahasa lain) sudah mengurus semua langkah di halaman ini, termasuk menolak pesan lama. Contoh endpoint lengkap untuk Express, Laravel, PHP native, FastAPI, dan Flask ada di [Menerima Webhook](/integration/webhook/menerima-webhook#membuat-endpoint-penerima).

<Tip>
  Pakai library kalau bisa. Verifikasi manual di bawah ini berguna kalau bahasa atau framework kamu belum didukung, atau kamu ingin paham cara kerjanya.
</Tip>

## Verifikasi manual (tanpa library)

<Steps>
  <Step title="Susun signed content">
    Gabungkan `svix-id`, `svix-timestamp`, dan raw body dengan tanda titik:

    ```text theme={null}
    {svix-id}.{svix-timestamp}.{raw body}
    ```

    Pakai raw body persis seperti yang diterima, jangan hasil parse JSON.
  </Step>

  <Step title="Siapkan key dari Signing Secret">
    Signing Secret berbentuk `whsec_<base64>`. Buang prefix `whsec_`, lalu decode sisanya dari base64. Hasilnya (bytes) adalah key HMAC.
  </Step>

  <Step title="Hitung signature">
    Hitung HMAC-SHA256 dari signed content memakai key tadi, lalu encode hasilnya ke base64.
  </Step>

  <Step title="Bandingkan dengan header svix-signature">
    Header `svix-signature` bisa berisi lebih dari satu signature, dipisahkan spasi, misalnya `v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= v1,bm9ldHUjKzFob2l2...`. Buang prefix versi `v1,`, lalu bandingkan setiap signature dengan hasil hitunganmu memakai **constant-time compare**. Pesan valid kalau salah satunya cocok.
  </Step>

  <Step title="Cek timestamp">
    Tolak pesan kalau `svix-timestamp` berbeda lebih dari sekitar 5 menit dari jam server kamu. Ini mencegah pesan lama dikirim ulang oleh pihak lain (replay attack).
  </Step>
</Steps>

Contoh dengan modul `crypto` bawaan Node.js:

```javascript verify-simplus-webhook.js theme={null}
import crypto from "node:crypto";

const TOLERANCE_SECONDS = 5 * 60;

export function verifySimplusWebhook(rawBody, headers, secret) {
  const id = headers["svix-id"];
  const timestamp = headers["svix-timestamp"];
  const signatureHeader = headers["svix-signature"];
  if (!id || !timestamp || !signatureHeader) {
    throw new Error("Header webhook tidak lengkap");
  }

  // Tolak pesan yang terlalu lama atau dari masa depan
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - Number(timestamp)) > TOLERANCE_SECONDS) {
    throw new Error("Timestamp di luar toleransi");
  }

  // Key = bagian base64 setelah "whsec_"
  const key = Buffer.from(secret.split("_")[1], "base64");
  const expected = crypto
    .createHmac("sha256", key)
    .update(`${id}.${timestamp}.${rawBody}`)
    .digest();

  // Header bisa berisi beberapa "v1,<signature>" dipisahkan spasi
  const valid = signatureHeader.split(" ").some((entry) => {
    const [version, signature] = entry.split(",");
    if (version !== "v1" || !signature) return false;
    const received = Buffer.from(signature, "base64");
    return received.length === expected.length && crypto.timingSafeEqual(received, expected);
  });

  if (!valid) throw new Error("Signature tidak valid");
  return JSON.parse(rawBody);
}
```

Kalau fungsi ini melempar error, balas dengan status `400` dan jangan proses pesannya.

<Warning>
  Jangan bandingkan signature dengan `===` atau `==`. Perbandingan biasa bisa membocorkan informasi lewat perbedaan waktu (timing attack). Pakai `crypto.timingSafeEqual` di Node.js, `hash_equals` di PHP, atau `hmac.compare_digest` di Python.
</Warning>

## Langkah berikutnya

<CardGroup cols={2}>
  <Card title="Menerima Webhook" icon="inbox" href="/integration/webhook/menerima-webhook">
    Contoh endpoint lengkap, cara mencoba di lokal, dan checklist production.
  </Card>

  <Card title="Event sales.persisted" icon="receipt" href="https://registry.scalar.com/@simplus/apis/public#tag/webhook-overview">
    Lihat struktur payload transaksi penjualan.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.