Dokumentasi Lyxvo Security

Hubungkan website apa pun ke Lyxvo WAF dan Lyxvo Captcha. Kamu butuh satu situs dari dasbor: site key (publik) dan secret key (simpan di server).

Ambil kuncimu

Panduan pemula

Ikuti langkah ini dari atas ke bawah. Tidak perlu paham kode: setiap klik dijelaskan, dengan gambar dasbor yang asli. Angka bulat di gambar sama dengan nomor langkahnya.

Waktu: sekitar 10 menit. Yang kamu butuhkan: alamat websitemu, dan akses ke hosting (untuk WordPress) atau ke Cloudflare.

Istilah yang dipakai

WAF (firewall)
Penjaga di depan websitemu. Setiap kali ada yang membuka situsmu, servermu bertanya ke Lyxvo: "aman?". Kalau isinya serangan (pembobolan database, skrip jahat, alat peretas), permintaan itu ditolak.
Captcha
Kotak "Saya manusia" di formulir (kontak, daftar, login). Browser pengunjung mengerjakan hitungan kecil sekitar 1 detik, jadi bot spam tidak bisa mengirim formulir. Pengunjung tidak perlu memilih gambar.
Site key
Kunci publik, diawali lxs_pk_. Boleh dilihat orang; dipasang di halaman bersama captcha.
Secret key
Kunci rahasia, diawali lxs_sk_. Seperti kata sandi: hanya dipasang di server atau pengaturan Cloudflare. Jangan dikirim di chat umum atau ditaruh di halaman.
IP
Alamat internet pengunjung, misalnya 203.0.113.7. Penyerang dikenali dan diblokir dari IP-nya.
Mode
Blokir serangan = menolak serangan (normal). Pantau saja = hanya mencatat, tidak menolak (untuk uji coba). Mati = tidak memeriksa apa pun.

Langkah 1–3: buat situs dan ambil kunci

1. Buka dasbor dan masuk

Buka security.lyxvo.ai/id/dashboard/. Masuk dengan akun Lyxvo (sama dengan akun lyxvo.ai). Belum punya? Klik "Belum punya akun? Buat sekarang", isi nama, email dan kata sandi (minimal 8 huruf), lalu klik Buat akun.

Halaman masuk dasbor Lyxvo Security

2. Tambahkan situsmu

  1. ① Nama situs: bebas, untuk kamu sendiri. Contoh: Toko Saya.
  2. ② Domain: alamat websitemu tanpa https:// dan tanpa garis miring. Contoh: tokosaya.com. Subdomain (shop.tokosaya.com) otomatis ikut. Lebih dari satu? Pisahkan dengan koma.
  3. ③ Klik Tambah situs.
Formulir tambah situs di dasbor

3. Salin kunci

  1. ① Site key: klik Salin. Dipakai untuk captcha (langkah 5).
  2. ② Secret key: klik Lihat, lalu Salin. Dipakai untuk menyambungkan situs (langkah 4).
  3. ③ Secret baru: hanya bila secret key bocor. Yang lama langsung tidak berlaku, jadi pasang yang baru di situsmu.
Kotak kunci: site key dan secret key

Simpan secret key di tempat aman (misalnya catatan pribadi). Kamu bisa melihatnya lagi kapan saja di dasbor.

Buka dasbor

Langkah 4: sambungkan websitemu

Pilih satu cara yang cocok. Setelah tersambung, setiap pengunjung situsmu diperiksa dulu oleh Lyxvo dalam waktu sangat singkat. Kalau Lyxvo sedang tidak bisa dihubungi, situsmu tetap terbuka seperti biasa.

A. WordPress (plugin, tanpa kode)

Untuk situs WordPress di hosting apa pun. Firewall + captcha di login, daftar, komentar dan Contact Form 7.

Unduh plugin Lyxvo Security (.zip)

  1. Unduh plugin dengan tombol di bawah (file lyxvo-security-wordpress.zip). Jangan dibuka/diekstrak.
  2. Masuk ke wp-admin → ① Plugin → Tambahkan Plugin → ② tombol Unggah Plugin di atas.
  3. ③ Pilih file lyxvo-security-wordpress.zip → ④ Instal Sekarang → Aktifkan Plugin.
  4. Buka Pengaturan → Lyxvo Security. Tempel ① secret key dan ② site key dari langkah 3, lalu tekan ③ Simpan.
  5. Tekan ④ Tes koneksi. Harus muncul "Tersambung. Lyxvo Security sedang memeriksa situs ini." Selesai.
Halaman Tambahkan Plugin → Unggah Plugin di WordPress
Halaman Pengaturan → Lyxvo Security di WordPress

Admin yang sedang login tidak pernah diperiksa, jadi kamu tidak akan terkunci dari wp-admin. Kalau security.lyxvo.ai tidak bisa dihubungi, situs dan formulir tetap jalan seperti biasa. Situsmu di belakang Cloudflare? Centang "Situsku di belakang Cloudflare" supaya IP pengunjung terbaca benar.

B. Cloudflare (website apa pun)

Untuk situs apa pun yang domainnya sudah memakai Cloudflare (Shopify dan Wix tidak bisa dengan cara ini).

  1. Masuk ke dash.cloudflare.com → menu Workers & Pages → Create → Create Worker (Hello World) → beri nama lyxvo-security → Deploy.
  2. Klik Edit code, hapus semua isinya, tempel kode di bawah, lalu klik Deploy.
  3. Kembali ke Worker itu → Settings → Variables and Secrets → Add → tipe Secret, nama LYXVO_SECRET, isi secret key-mu → Deploy.
  4. Settings → Domains & Routes → Add → Route → isi tokosaya.com/* (pakai domainmu), pilih zona domainmu → Add route. Ulangi untuk www.tokosaya.com/* bila situsmu memakai www.
export default {
  async fetch(request, env, ctx) {
    const u = new URL(request.url);
    try {
      // Form and JSON bodies are scanned too (uploads are skipped); the clone leaves the request intact.
      const type = request.headers.get("content-type") || "";
      const body = request.method !== "GET" && request.method !== "HEAD" && !type.startsWith("multipart/") && Number(request.headers.get("content-length")) > 0 && Number(request.headers.get("content-length")) < 65536
        ? (await request.clone().text()).slice(0, 8000) : "";
      const r = await fetch("https://security.lyxvo.ai/api/security/check", {
        method: "POST",
        signal: AbortSignal.timeout(1500),
        headers: { "content-type": "application/json", authorization: "Bearer " + env.LYXVO_SECRET },
        body: JSON.stringify({
          ip: request.headers.get("cf-connecting-ip") || "", country: request.cf?.country,
          method: request.method, url: u.pathname + u.search, headers: Object.fromEntries(request.headers), body,
        }),
      });
      const v = await r.json();
      if (v.action === "block") return new Response("Blocked by Lyxvo Security", { status: v.status });
    } catch {}
    return fetch(request);
  },
};

Dengan cara ini Lyxvo menjaga seluruh situsmu, apa pun teknologinya (WordPress, Laravel, situs statis…).

C. Punya developer atau programmer

Untuk situs buatan sendiri (Laravel, Node.js, Next.js, Python, Go…).

  1. Kirim tautan bagian "Untuk developer" di bawah halaman ini ke developer-mu.
  2. Berikan secret key lewat jalur pribadi (bukan grup chat). Mereka menaruhnya di pengaturan server sebagai LYXVO_SECRET.
  3. Pemasangan biasanya selesai dalam 15 menit. Lalu lakukan langkah 6 untuk mengecek.

5. Captcha untuk formulir booking, kontak, daftar & login

Captcha menahan bot yang mengirim booking palsu, spam di formulir kontak, atau mencoba-coba kata sandi. Pengunjung asli tidak perlu memilih gambar: kotaknya berubah jadi Terverifikasi sendiri dalam sekitar 1 detik.

  1. Kotak captcha di formulir (satu baris HTML + satu script).
  2. Saat formulir dikirim, token captcha ikut terkirim otomatis (kolom tersembunyi bernama lyxvo-captcha).
  3. Server yang menerima formulir menanyakan token itu ke Lyxvo dengan secret key, dan hanya menyimpan booking bila jawabannya success: true.

Bagian 3 wajib. Kotak captcha tanpa pemeriksaan di server hanya hiasan, bot tetap bisa mengirim formulir.

Pilih yang sesuai dengan situsmu:

A. WordPress (Contact Form 7, login, daftar, komentar)

Paling mudah: tidak perlu kode.

  1. Pasang plugin Lyxvo Security (langkah 4A di atas) dan tempel kedua kunci.
  2. Di Pengaturan → Lyxvo Security, pastikan bagian "Captcha di" tercentang: Halaman login, Pendaftaran, Lupa kata sandi, Komentar, Formulir Contact Form 7. Tekan Simpan.
  3. Selesai. Setiap formulir Contact Form 7 (booking, kontak, reservasi…) otomatis mendapat kotak captcha tepat di atas tombol Kirim (①). Booking tanpa captcha ditolak dengan pesan yang jelas.
  4. Formulir booking belum ada? Plugin → Tambahkan Plugin → cari "Contact Form 7" → Instal → Aktifkan, lalu Kontak → Tambah Baru, buat kolom (nama, email, tanggal, jumlah tamu), salin kode pendeknya ke halaman Booking.
Formulir booking Contact Form 7 dengan kotak Lyxvo Captcha

Saat ini plugin memasang captcha otomatis di formulir bawaan WordPress dan Contact Form 7. WPForms, Elementor Forms, Gravity Forms dan checkout WooCommerce belum didukung; untuk formulir booking, pakai Contact Form 7.

B. Formulir HTML + PHP (hosting biasa, tanpa WordPress)

Untuk situs HTML biasa di hosting yang mendukung PHP (hampir semua hosting Indonesia).

  1. Unduh contoh lengkap (tombol di bawah), buka foldernya booking-php. Isinya dua file: booking.html (formulir) dan booking.php (pemeriksa).
  2. Buka booking.html dengan Notepad/editor, ganti lxs_pk_... dengan site key-mu. Simpan.
  3. Buka booking.php, ganti lxs_sk_... dengan secret key-mu. Simpan.
  4. Unggah kedua file ke hosting: File Manager → public_html (folder yang sama).
  5. Buka tokosaya.com/booking.html, isi formulir, tunggu Terverifikasi, tekan Kirim booking. Booking tersimpan di file bookings.csv (bisa dibuka di Excel) di folder yang sama.
Contoh formulir booking dengan Lyxvo Captcha terverifikasi

Unduh contoh lengkap (.zip)

booking.html

<!doctype html>
<html lang="id">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Booking</title>
  <style>
    body { font: 16px/1.5 system-ui, sans-serif; max-width: 480px; margin: 40px auto; padding: 0 16px; }
    label { display: block; margin: 12px 0 4px; font-weight: 600; }
    input, textarea { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 8px; font: inherit; box-sizing: border-box; }
    button { margin-top: 8px; padding: 12px 20px; border: 0; border-radius: 8px; background: #6d3dfb; color: #fff; font: inherit; font-weight: 700; cursor: pointer; }
  </style>
</head>
<body>
  <h1>Booking</h1>
  <form action="booking.php" method="POST">
    <label for="name">Nama lengkap</label>
    <input id="name" name="name" required>
    <label for="email">Email</label>
    <input id="email" name="email" type="email" required>
    <label for="date">Tanggal datang</label>
    <input id="date" name="date" type="date" required>
    <label for="guests">Jumlah tamu</label>
    <input id="guests" name="guests" type="number" min="1" max="20" value="2" required>

    <!-- Lyxvo Captcha: ganti lxs_pk_... dengan site key-mu -->
    <div class="lyxvo-captcha" data-sitekey="lxs_pk_..."></div>

    <button type="submit">Kirim booking</button>
  </form>
  <script src="https://security.lyxvo.ai/captcha.js" async defer></script>
</body>
</html>

booking.php

<?php
// Receives the booking form. Ganti lxs_sk_... dengan secret key-mu (atau isi variabel server LYXVO_SECRET).
$secret = getenv('LYXVO_SECRET') ?: 'lxs_sk_...';

// 1. Ask Lyxvo whether the captcha was solved.
$ch = curl_init('https://security.lyxvo.ai/api/security/captcha/verify');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 5,
    CURLOPT_POSTFIELDS => [
        'secret' => $secret,
        'token' => $_POST['lyxvo-captcha'] ?? '',
        'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
    ],
]);
$answer = json_decode((string) curl_exec($ch), true);
curl_close($ch);

if (empty($answer['success'])) {
    http_response_code(400);
    exit('<p>Captcha belum selesai. Kembali, tunggu tanda "Terverifikasi", lalu kirim lagi.</p><p><a href="javascript:history.back()">Kembali</a></p>');
}

// 2. The captcha is fine: save the booking (here: one line in bookings.csv) or email it to yourself.
$row = [date('c'), $_POST['name'] ?? '', $_POST['email'] ?? '', $_POST['date'] ?? '', $_POST['guests'] ?? ''];
$f = fopen(__DIR__ . '/bookings.csv', 'a');
fputcsv($f, $row);
fclose($f);

echo '<p>Terima kasih, booking kamu sudah kami terima.</p>';

Ingin booking dikirim ke email? Ganti bagian "save the booking" di booking.php dengan mail('emailmu@domain.com', 'Booking baru', print_r($_POST, true)); (hosting harus mengizinkan fungsi mail).

C. Formulir yang dikirim dengan JavaScript (tanpa pindah halaman)

Untuk developer: React, Vue, jQuery, fetch… Contoh server memakai Node.js (Express).

  1. Taruh kotak captcha di dalam <form> dan muat captcha.js.
  2. Saat mengirim, sertakan nilai kolom lyxvo-captcha (FormData sudah otomatis menyertakannya).
  3. Setelah setiap pengiriman, panggil lyxvoCaptcha.reset(...) karena token hanya berlaku sekali.
  4. Di server, kirim token + secret key ke /api/security/captcha/verify dan tolak bila success bukan true.
  5. Jalankan: npm install express, lalu LYXVO_SECRET=lxs_sk_... node server.js.

Unduh contoh lengkap (.zip)

public/index.html

<!doctype html>
<html lang="id">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Booking</title>
</head>
<body>
  <h1>Booking</h1>
  <form id="booking">
    <input name="name" placeholder="Nama lengkap" required>
    <input name="email" type="email" placeholder="Email" required>
    <input name="date" type="date" required>
    <div class="lyxvo-captcha" data-sitekey="lxs_pk_..."></div>
    <button type="submit">Kirim booking</button>
  </form>
  <p id="result" role="status"></p>
  <script src="https://security.lyxvo.ai/captcha.js" async defer></script>
  <script>
    const form = document.getElementById('booking');
    form.addEventListener('submit', async (e) => {
      e.preventDefault();
      const res = await fetch('/booking', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify(Object.fromEntries(new FormData(form))), // includes the lyxvo-captcha token
      });
      const data = await res.json();
      document.getElementById('result').textContent = data.message;
      if (data.ok) form.reset();
      // A token works once: get a new one for the next send.
      window.lyxvoCaptcha.reset(form.querySelector('.lyxvo-captcha'));
    });
  </script>
</body>
</html>

server.js

// npm install express  ·  LYXVO_SECRET=lxs_sk_... node server.js
const express = require('express');
const app = express();
app.use(express.json());
app.use(express.static('public'));

app.post('/booking', async (req, res) => {
  const r = await fetch('https://security.lyxvo.ai/api/security/captcha/verify', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ secret: process.env.LYXVO_SECRET, token: req.body['lyxvo-captcha'], ip: req.ip }),
  }).catch(() => null);
  const answer = r ? await r.json() : { success: false };
  if (!answer.success) return res.status(400).json({ ok: false, message: 'Captcha belum selesai. Tunggu tanda Terverifikasi lalu kirim lagi.' });

  // The captcha is fine: save the booking or send it to yourself here.
  console.log('booking', req.body.name, req.body.email, req.body.date);
  res.json({ ok: true, message: 'Terima kasih, booking kamu sudah kami terima.' });
});

app.listen(process.env.PORT || 3000);

D. Python (Flask)

Untuk situs Python. Tanpa pustaka tambahan selain Flask.

  1. pip install flask
  2. Ganti lxs_pk_... di FORM dengan site key-mu.
  3. Jalankan: LYXVO_SECRET=lxs_sk_... python app.py

app.py

# pip install flask  ·  LYXVO_SECRET=lxs_sk_... python app.py
import json
import os
import urllib.request

from flask import Flask, request

app = Flask(__name__)

FORM = """<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<h1>Booking</h1>
<form method="post">
  <input name="name" placeholder="Nama lengkap" required>
  <input name="email" type="email" placeholder="Email" required>
  <input name="date" type="date" required>
  <div class="lyxvo-captcha" data-sitekey="lxs_pk_..."></div>
  <button type="submit">Kirim booking</button>
</form>
<script src="https://security.lyxvo.ai/captcha.js" async defer></script>"""


def captcha_ok(token, ip):
    body = json.dumps({"secret": os.environ["LYXVO_SECRET"], "token": token, "ip": ip}).encode()
    req = urllib.request.Request("https://security.lyxvo.ai/api/security/captcha/verify", data=body,
                                 headers={"Content-Type": "application/json"})
    try:
        with urllib.request.urlopen(req, timeout=5) as r:
            return json.load(r).get("success") is True
    except Exception:
        return False


@app.route("/", methods=["GET", "POST"])
def booking():
    if request.method == "GET":
        return FORM
    if not captcha_ok(request.form.get("lyxvo-captcha", ""), request.remote_addr):
        return "Captcha belum selesai. Kembali, tunggu tanda Terverifikasi, lalu kirim lagi.", 400
    # The captcha is fine: save the booking or send it to yourself here.
    return "Terima kasih, booking kamu sudah kami terima."


if __name__ == "__main__":
    app.run(port=int(os.environ.get("PORT", 5000)))

E. Formulir ke WhatsApp, Google Form, Formspree atau Typeform

Captcha tidak bisa melindungi formulir seperti ini, karena tidak ada server milikmu yang memeriksa tokennya (layanan itu tidak mengenal Lyxvo). Pilihannya: pindahkan formulir booking ke WordPress + Contact Form 7 (A) atau ke contoh HTML + PHP (B) di hostingmu. Tombol "Booking via WhatsApp" biasa tidak perlu captcha, karena tidak ada formulir yang bisa dibanjiri bot.

Yang perlu diketahui tentang kotak captcha

  • Berubah jadi Terverifikasi sendiri dalam sekitar 1 detik. Tidak ada gambar yang harus dipilih.
  • Kalau pengunjung menekan Kirim sebelum Terverifikasi, formulir menunggu lalu terkirim sendiri. Tidak ada booking yang hilang.
  • Token berlaku 5 menit dan sekali pakai. Kotak memperbarui token sendiri sebelum habis, jadi pengunjung boleh lama mengisi formulir.
  • Hanya jalan di domain yang terdaftar di situsmu (subdomain ikut). Di domain lain kotaknya bertuliskan "Gagal memeriksa".
  • Bahasa mengikuti halaman (<html lang="id"> → Bahasa Indonesia). Paksa dengan data-lang="id" atau data-lang="en". Tema gelap: data-theme="dark".

Arti jawaban pemeriksaan (untuk developer)

success: true
Captcha benar. Simpan booking-nya.
reason: expired
Token lebih dari 5 menit. Minta pengunjung mengirim ulang.
reason: already-used
Token sudah dipakai. Formulir JavaScript lupa memanggil lyxvoCaptcha.reset().
reason: wrong-answer / bad-signature / malformed
Token palsu atau rusak (biasanya bot).
error: missing-token
Formulir dikirim tanpa token: kotak captcha tidak ada di dalam <form>, atau belum selesai.
error: invalid-secret
Secret key salah. Salin lagi dari dasbor.

Langkah 6: cek hasilnya

6. Cek apakah sudah tersambung

  1. Di dasbor, Mode & aturan → Mode → pilih "Pantau saja" → Simpan (supaya kamu sendiri tidak terblokir saat menguji).
  2. Buka di browser: https://tokosaya.com/?q=<script>alert(1)</script> (pakai domainmu). Situsmu tetap terbuka seperti biasa.
  3. Tunggu sampai 15 detik di dasbor: ① muncul batang di grafik serangan dan ② satu baris di Catatan serangan dengan tindakan monitor. Artinya sudah tersambung.
  4. Ganti Mode kembali ke "Blokir serangan" → Simpan. Selesai, situsmu terlindungi.
Grafik serangan dan catatan serangan di dasbor

Kalau kamu menguji dalam mode Blokir, IP-mu sendiri ikut diblokir. Buka dasbor → IP diblokir → klik Buka blokir.

Langkah 7: atur sesuai kebutuhan

Mode & aturan

  1. ① Mode: Blokir serangan (normal), Pantau saja (uji coba), atau Mati.
  2. ② Jebak pencarian berkas rahasia: biarkan menyala, kecuali situsmu WordPress.
  3. ③ Permintaan per menit: berapa kali satu IP boleh membuka situsmu per menit sebelum diblokir 1 jam. 600 sudah aman untuk kebanyakan situs; 0 = tanpa batas.
  4. ④ Selalu izinkan: IP kantormu atau server pembayaran, satu per baris. IP ini tidak pernah diblokir.
  5. ⑤ Negara diblokir: kode dua huruf, misalnya RU, KP. Kosongkan bila pengunjungmu dari mana saja.
  6. ⑥ Klik Simpan. Perubahan berlaku dalam beberapa detik.
Formulir mode dan aturan

Masalah yang sering terjadi

Catatan serangan tetap kosong

Belum ada serangan, atau situs belum tersambung. Cek: secret key sudah benar (tanpa spasi), file lyxvo-security.php ada di wp-content/mu-plugins, atau route Cloudflare sudah ditambahkan. Lalu ulangi langkah 6.

Captcha bertuliskan "Gagal memeriksa"

Domain halaman itu belum ada di daftar domain situsmu. Tambahkan di Mode & aturan → Domain → Simpan, lalu muat ulang halaman.

Pengunjung asli terblokir

Buka IP diblokir → Buka blokir. Agar tidak terulang, masukkan IP itu ke Selalu izinkan. Kalau sering terjadi, naikkan angka Permintaan per menit.

Tidak bisa masuk /wp-admin

Dengan plugin WordPress ini tidak terjadi (admin yang login tidak diperiksa). Kalau kamu menyambungkan WordPress lewat Cloudflare Worker, hilangkan centang "Jebak pencarian berkas rahasia" di Mode & aturan, lalu buka blokir IP-mu.

Tes koneksi di plugin gagal

"Secret key salah": salin lagi dari dasbor (tanpa spasi). "Tidak bisa menghubungi": hosting memblokir koneksi keluar; minta hosting mengizinkan HTTPS ke security.lyxvo.ai.

Booking tanpa captcha masih masuk

Pemeriksaan di server belum terpasang (bagian 3 di langkah 5). Di WordPress: centang Formulir Contact Form 7 di pengaturan plugin. Situs lain: pastikan booking.php (atau servermu) memanggil verify sebelum menyimpan.

Muncul peringatan batas bulan ini

Jatah pemeriksaan paketmu habis. Situs tetap jalan tetapi tidak diperiksa sampai bulan depan. Naikkan paket di bagian Paket pada dasbor.

Untuk developer

Referensi teknis API dan contoh kode untuk setiap bahasa.

Lyxvo WAF

Sebelum aplikasimu memproses permintaan, kirim detailnya ke API check. Bila jawabannya block, tolak permintaan dengan status yang diberikan. Pakai batas waktu singkat dan loloskan permintaan bila ada error, supaya situsmu tidak bergantung pada kami.

Permintaan

POST https://security.lyxvo.ai/api/security/check
Authorization: Bearer lxs_sk_...
Content-Type: application/json

{ "ip": "203.0.113.7", "method": "POST", "url": "/login?next=/",
  "headers": { "user-agent": "Mozilla/5.0 ..." }, "body": "email=a%40b.com", "country": "ID" }
ip
IP pengunjung (di belakang proxy: X-Forwarded-For atau CF-Connecting-IP).
method, url
Metode HTTP dan path beserta query string.
headers
Header permintaan (User-Agent dan header pemindai diperiksa).
body
Opsional. Body formulir atau JSON, maks 64 KB, dipindai muatan serangan.
country
Opsional, kode negara dua huruf untuk negara diblokir (dari Cloudflare: CF-IPCountry).

Jawaban

{ "action": "block", "status": 403, "kind": "sqli", "reason": "union select" }
{ "action": "allow", "status": 200 }
action
allow atau block.
status
403 untuk serangan, 429 untuk banjir dan blokir sementara.
kind
Serangan yang ditemukan: sqli, xss, traversal, rce, scanner, trap, flood…
monitor
true bila situs dalam mode pantau: dicatat, tidak diblokir.
quota
true bila jatah pemeriksaan bulan ini habis (permintaan diloloskan).

Contoh

Node.js / Express

// npm i express  ·  set LYXVO_SECRET=lxs_sk_... in your environment
app.set("trust proxy", true); // so req.ip is the visitor, behind a proxy or load balancer
app.use(async (req, res, next) => {
  try {
    const r = await fetch("https://security.lyxvo.ai/api/security/check", {
      method: "POST",
      signal: AbortSignal.timeout(1500),
      headers: { "content-type": "application/json", authorization: "Bearer " + process.env.LYXVO_SECRET },
      body: JSON.stringify({ ip: req.ip, method: req.method, url: req.originalUrl, headers: req.headers,
        body: typeof req.body === "string" ? req.body : JSON.stringify(req.body || "") }),
    });
    const v = await r.json();
    if (v.action === "block") return res.status(v.status).send("Blocked by Lyxvo Security");
  } catch {} // Lyxvo Security unreachable: let the request through
  next();
});

Next.js (proxy.ts / middleware.ts)

// proxy.ts (Next.js 16) or middleware.ts (older), at the project root
import { NextResponse, type NextRequest } from "next/server";

export async function proxy(req: NextRequest) {
  try {
    // Form and JSON bodies are scanned too (uploads are skipped); the clone leaves the request intact.
    const type = req.headers.get("content-type") || "";
    const body = req.method !== "GET" && req.method !== "HEAD" && !type.startsWith("multipart/") && Number(req.headers.get("content-length")) > 0 && Number(req.headers.get("content-length")) < 65536
      ? (await req.clone().text()).slice(0, 8000) : "";
    const headers: Record<string, string> = {};
    req.headers.forEach((v, k) => { headers[k] = v; });
    const r = await fetch("https://security.lyxvo.ai/api/security/check", {
      method: "POST",
      signal: AbortSignal.timeout(1500),
      headers: { "content-type": "application/json", authorization: "Bearer " + process.env.LYXVO_SECRET },
      body: JSON.stringify({
        ip: req.headers.get("x-forwarded-for")?.split(",")[0].trim() || "",
        method: req.method, url: req.nextUrl.pathname + req.nextUrl.search,
        headers, body,
      }),
    });
    const v: { action?: string; status?: number } = await r.json();
    if (v.action === "block") return new NextResponse("Blocked by Lyxvo Security", { status: v.status });
  } catch {}
  return NextResponse.next();
}
export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"] };

PHP / Laravel

<?php
// Plain PHP: save as lyxvo-security.php and add require __DIR__ . '/lyxvo-security.php'; at the top of index.php.
// Laravel: put the body in a middleware. WordPress: use the Lyxvo Security plugin instead (beginner guide, step 4A).
function lyxvo_security_check(): void {
    $secret = getenv('LYXVO_SECRET') ?: 'lxs_sk_...';
    $headers = function_exists('getallheaders') ? getallheaders() : [];
    $payload = json_encode([
        'ip' => $_SERVER['REMOTE_ADDR'] ?? '', // behind Cloudflare: $_SERVER['HTTP_CF_CONNECTING_IP']
        'method' => $_SERVER['REQUEST_METHOD'] ?? 'GET',
        'url' => $_SERVER['REQUEST_URI'] ?? '/',
        'headers' => $headers,
        'body' => substr(file_get_contents('php://input') ?: '', 0, 8000),
    ]);
    $ch = curl_init('https://security.lyxvo.ai/api/security/check');
    curl_setopt_array($ch, [
        CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT_MS => 1500,
        CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . $secret],
        CURLOPT_POSTFIELDS => $payload,
    ]);
    $v = json_decode((string) curl_exec($ch), true);
    curl_close($ch);
    if (($v['action'] ?? '') === 'block') { // anything else, including a timeout, lets the request through
        http_response_code((int) ($v['status'] ?? 403));
        exit('Blocked by Lyxvo Security');
    }
}
lyxvo_security_check();

Python (Flask / Django)

# pip install requests  ·  Flask shown; in Django do the same in a middleware's __call__
import os, requests
from flask import Flask, request, abort

app = Flask(__name__)

@app.before_request
def lyxvo_security():
    try:
        v = requests.post("https://security.lyxvo.ai/api/security/check", timeout=1.5,
            headers={"Authorization": "Bearer " + os.environ["LYXVO_SECRET"]},
            json={"ip": request.headers.get("X-Forwarded-For", request.remote_addr).split(",")[0].strip(),
                  "method": request.method, "url": request.full_path, "headers": dict(request.headers),
                  "body": request.get_data(as_text=True)[:8000]}).json()
        if v.get("action") == "block":
            abort(v.get("status", 403))
    except requests.RequestException:
        pass  # fail open

Go (net/http)

// Imports: bytes, encoding/json, io, net, net/http, os, strings, time. Wrap your handler: LyxvoSecurity(mux).
func LyxvoSecurity(next http.Handler) http.Handler {
	client := &http.Client{Timeout: 1500 * time.Millisecond}
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		h := map[string]string{}
		for k, v := range r.Header { h[k] = strings.Join(v, ", ") }
		ip, _, _ := net.SplitHostPort(r.RemoteAddr)
		// Form and JSON bodies are scanned too: read up to 64 KB, then put it back for your handlers.
		var form []byte
		if r.ContentLength > 0 && r.ContentLength < 64<<10 && !strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/") {
			form, _ = io.ReadAll(io.LimitReader(r.Body, 64<<10))
			r.Body = io.NopCloser(bytes.NewReader(form))
		}
		body, _ := json.Marshal(map[string]any{"ip": ip, "method": r.Method, "url": r.URL.RequestURI(), "headers": h, "body": string(form[:min(len(form), 8000)])})
		req, _ := http.NewRequest("POST", "https://security.lyxvo.ai/api/security/check", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", "Bearer "+os.Getenv("LYXVO_SECRET"))
		if res, err := client.Do(req); err == nil {
			var v struct{ Action string; Status int }
			json.NewDecoder(res.Body).Decode(&v)
			res.Body.Close()
			if v.Action == "block" && v.Status >= 400 { http.Error(w, "Blocked by Lyxvo Security", v.Status); return }
		}
		next.ServeHTTP(w, r) // Lyxvo Security unreachable or slow: let the request through
	})
}

Cloudflare Workers (in front of any site)

// A Worker on your domain's route screens every request, then passes it to your origin.
// Secret: wrangler secret put LYXVO_SECRET
export default {
  async fetch(request, env, ctx) {
    const u = new URL(request.url);
    try {
      // Form and JSON bodies are scanned too (uploads are skipped); the clone leaves the request intact.
      const type = request.headers.get("content-type") || "";
      const body = request.method !== "GET" && request.method !== "HEAD" && !type.startsWith("multipart/") && Number(request.headers.get("content-length")) > 0 && Number(request.headers.get("content-length")) < 65536
        ? (await request.clone().text()).slice(0, 8000) : "";
      const r = await fetch("https://security.lyxvo.ai/api/security/check", {
        method: "POST",
        signal: AbortSignal.timeout(1500),
        headers: { "content-type": "application/json", authorization: "Bearer " + env.LYXVO_SECRET },
        body: JSON.stringify({
          ip: request.headers.get("cf-connecting-ip") || "", country: request.cf?.country,
          method: request.method, url: u.pathname + u.search, headers: Object.fromEntries(request.headers), body,
        }),
      });
      const v = await r.json();
      if (v.action === "block") return new Response("Blocked by Lyxvo Security", { status: v.status });
    } catch {}
    return fetch(request);
  },
};

Lyxvo Captcha

Widget mengambil tantangan, browser pengunjung menyelesaikan proof-of-work kecil (sekitar satu detik, tanpa teka-teki), lalu token masuk ke field tersembunyi lyxvo-captcha. Servermu mengirim token itu bersama secret key ke API verify. Token hanya berlaku sekali dan selama 5 menit; widget memperbaruinya sendiri. Formulir yang dikirim dengan JavaScript wajib memanggil lyxvoCaptcha.reset(el) setelah setiap pengiriman. Tombol kirim yang ditekan sebelum pemeriksaan selesai akan menunggu lalu mengirim sendiri. Widget hanya jalan di domain milik situs itu. Formulir booking lengkap yang sudah diuji: panduan pemula, langkah 5.

1. In your form (any page)

<form action="/contact" method="POST">
  <input name="email" type="email" required>
  <div class="lyxvo-captcha" data-sitekey="lxs_pk_..."></div>
  <button>Send</button>
</form>
<script src="https://security.lyxvo.ai/captcha.js" async defer></script>
<!-- Optional: data-theme="dark", data-callback="onVerified" (called with the token) -->

2. On your server: Node

const token = req.body["lyxvo-captcha"];
const r = await fetch("https://security.lyxvo.ai/api/security/captcha/verify", {
  method: "POST", headers: { "content-type": "application/json" },
  body: JSON.stringify({ secret: process.env.LYXVO_SECRET, token, ip: req.ip }),
});
const { success } = await r.json();
if (!success) return res.status(400).send("Please complete the captcha.");

2. On your server: PHP

$ch = curl_init('https://security.lyxvo.ai/api/security/captcha/verify');
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5,
    CURLOPT_POSTFIELDS => ['secret' => getenv('LYXVO_SECRET'), 'token' => $_POST['lyxvo-captcha'] ?? '', 'ip' => $_SERVER['REMOTE_ADDR']]]);
$ok = json_decode((string) curl_exec($ch), true)['success'] ?? false;
if (!$ok) { http_response_code(400); exit('Please complete the captcha.'); }

2. On your server: Python

ok = requests.post("https://security.lyxvo.ai/api/security/captcha/verify", timeout=5, json={
    "secret": os.environ["LYXVO_SECRET"], "token": request.form.get("lyxvo-captcha", ""), "ip": request.remote_addr,
}).json().get("success")
if not ok:
    abort(400)

Jawaban verify

{ "success": true } atau { "success": false, "reason": "expired" | "already-used" | "wrong-answer" | "bad-signature" | "malformed" }

Mode dan aturan

Di dasbor: Blokir (bawaan), Pantau (hanya mencatat, pakai dulu di situs ramai), Mati. Aturan: jebakan path, permintaan per menit, daftar izinkan dan blokir, negara diblokir, kesulitan captcha. Serangan dengan muatan nyata memblokir IP selamanya; buka blokirnya di dasbor.

Batas

Setiap paket punya jatah pemeriksaan WAF dan captcha per bulan. Lewat batas, pemeriksaan diloloskan dan dasbor memberi tahu. Catatan serangan disimpan 7, 30 atau 90 hari sesuai paket.