Langsung ke konten
Integration

Cara mendeteksi pengguna VPN di aplikasi Anda dengan satu panggilan API

| 7 min read

Tambahkan VPN, proxy, dan deteksi Tor ke alur pendaftaran, checkout, dan login. Contoh middleware ekspres, integrasi Next.js, dan penilaian risiko dengan kode yang berfungsi.

Lock icon on a digital network background representing VPN security
Photo by Towfiqu barbhuiya on Unsplash

Halaman checkout Anda mendapatkan 50 pesanan dari rentang IP yang sama dalam 12 jam, semuanya menggunakan kartu prabayar. Uji coba gratis Anda menunjukkan 200 pendaftaran dari IP pusat data dalam seminggu. Titik akhir login Anda melihatnya upaya pengisian kredensial dari merotasi IP proxy.

Anda perlu mengetahui kapan lalu lintas berasal dari VPN, proxy, atau simpul keluar Tor. Bukan untuk memblokirnya langsung, tetapi untuk menyesuaikan penilaian risiko Anda. Satu panggilan API memberi Anda sinyal itu.

Panggilan API

Kirim IP pengguna ke POST /v1/vpn-detect. Tidak ada ketergantungan, tidak diperlukan SDK.

curl -X POST https://api.botoi.com/v1/vpn-detect \\
  -H "Content-Type: application/json" \\
  -d '{"ip": "185.220.101.1"}'

Respons untuk simpul keluar Tor yang diketahui (skor risiko 90):

{
  "success": true,
  "data": {
    "ip": "185.220.101.1",
    "is_vpn": true,
    "is_proxy": false,
    "is_tor": true,
    "is_datacenter": false,
    "provider": null,
    "risk_score": 90,
    "checks": {
      "tor": true,
      "datacenter": false,
      "suspicious_hostname": false
    }
  }
}

Respons untuk IP perumahan yang bersih (skor risiko 0):

{
  "success": true,
  "data": {
    "ip": "73.162.45.118",
    "is_vpn": false,
    "is_proxy": false,
    "is_tor": false,
    "is_datacenter": false,
    "provider": null,
    "risk_score": 0,
    "checks": {
      "tor": false,
      "datacenter": false,
      "suspicious_hostname": false
    }
  }
}

Responsnya memberi Anda lima tanda boolean (is_vpn, is_proxy, is_tor, is_datacenter) ditambah angka risk_score dari 0 hingga 100. Koneksi Tor mendapat skor 90. IP pusat data mendapat skor 60. Nama host yang mencurigakan skor 40. IP perumahan bersih skor 0.

Integrasi 1: Middleware ekspres

Middleware ini memanggil API deteksi VPN dan melampirkan hasilnya req.vpnRisk. Setiap penangan rute hilir dapat memeriksanya req.vpnRisk.isVpn untuk membuat keputusan tanpa mengulangi panggilan API.

import type { Request, Response, NextFunction } from 'express';

const VPN_DETECT_URL = 'https://api.botoi.com/v1/vpn-detect';
const BOTOI_API_KEY = process.env.BOTOI_API_KEY;

interface VpnRisk {
  isVpn: boolean;
  isProxy: boolean;
  isTor: boolean;
  isDatacenter: boolean;
  riskScore: number;
}

declare global {
  namespace Express {
    interface Request {
      vpnRisk?: VpnRisk;
    }
  }
}

export async function vpnDetectMiddleware(
  req: Request,
  _res: Response,
  next: NextFunction
) {
  const ip = req.headers['x-forwarded-for']?.toString().split(',')[0]?.trim()
    || req.socket.remoteAddress
    || 'unknown';

  try {
    const res = await fetch(VPN_DETECT_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': \`Bearer \${BOTOI_API_KEY}\`,
      },
      body: JSON.stringify({ ip }),
      signal: AbortSignal.timeout(3000),
    });

    const { data } = await res.json();

    req.vpnRisk = {
      isVpn: data.is_vpn,
      isProxy: data.is_proxy,
      isTor: data.is_tor,
      isDatacenter: data.is_datacenter,
      riskScore: data.risk_score,
    };
  } catch {
    // Fail open: if detection fails, treat as clean
    req.vpnRisk = {
      isVpn: false,
      isProxy: false,
      isTor: false,
      isDatacenter: false,
      riskScore: 0,
    };
  }

  next();
}

// Usage in your route:
// app.use(vpnDetectMiddleware);
//
// app.post('/api/signup', (req, res) => {
//   if (req.vpnRisk?.isVpn) {
//     // require email verification or flag for review
//   }
// });

Middleware gagal dibuka. Jika API tidak dapat dijangkau atau waktu habis setelah 3 detik, API akan disetel semua bendera ke false dan membiarkan permintaan berlanjut. Pengguna Anda tidak pernah melihat kesalahan disebabkan oleh pemadaman pihak ketiga.

Integrasi 2: Perlindungan checkout Next.js

Penipuan pembayaran mengikuti pola: akun baru, kartu prabayar, koneksi VPN. Ini Berikutnya.js Pengendali rute App Router memeriksa ketiga sinyal dan merutekan perintah mencurigakan ke manual meninjaunya alih-alih menyetujuinya secara otomatis.

import { NextRequest, NextResponse } from 'next/server';

const VPN_DETECT_URL = 'https://api.botoi.com/v1/vpn-detect';
const BOTOI_API_KEY = process.env.BOTOI_API_KEY!;

interface CheckoutBody {
  cardType: 'credit' | 'debit' | 'prepaid';
  accountCreatedAt: string;
  amount: number;
  currency: string;
}

export async function POST(req: NextRequest) {
  const body: CheckoutBody = await req.json();

  const ip = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim()
    || '127.0.0.1';

  // Check VPN status
  const vpnRes = await fetch(VPN_DETECT_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': \`Bearer \${BOTOI_API_KEY}\`,
    },
    body: JSON.stringify({ ip }),
    signal: AbortSignal.timeout(3000),
  });

  const { data: vpnData } = await vpnRes.json();

  const accountAge = Date.now() - new Date(body.accountCreatedAt).getTime();
  const isNewAccount = accountAge < 24 * 60 * 60 * 1000; // less than 24 hours
  const isPrepaid = body.cardType === 'prepaid';
  const isVpn = vpnData.is_vpn || vpnData.is_tor || vpnData.is_proxy;

  // VPN + new account + prepaid card = manual review
  if (isVpn && isNewAccount && isPrepaid) {
    return NextResponse.json({
      status: 'review',
      orderId: crypto.randomUUID(),
      message: 'Order placed. We will confirm within 30 minutes.',
    }, { status: 202 });
  }

  // VPN + new account (no prepaid) = proceed with logging
  if (isVpn && isNewAccount) {
    console.log(JSON.stringify({
      event: 'checkout_vpn_new_account',
      ip,
      riskScore: vpnData.risk_score,
      amount: body.amount,
    }));
  }

  // Process the order normally
  return NextResponse.json({
    status: 'approved',
    orderId: crypto.randomUUID(),
  });
}

Pawang tidak menolak pesanan. Ia mengembalikan 202 dengan "kami akan mengonfirmasi dalam 30 menit" pesan. This buys your team time to review without tipping off a bad actor that they've been ditandai. Pelanggan VPN yang sah masih mendapatkan pesanan mereka diproses setelah beberapa saat.

Integrasi 3: Pembatasan tingkat login

Serangan isian kredensial sering kali berasal dari VPN atau IP proxy untuk menghindari pemblokiran berbasis IP. Terapkan batas kecepatan yang lebih ketat pada koneksi tersebut: 3 upaya login per 15 menit untuk pengguna VPN versus 10 untuk pengguna biasa.

import type { Request, Response, NextFunction } from 'express';

const VPN_DETECT_URL = 'https://api.botoi.com/v1/vpn-detect';
const BOTOI_API_KEY = process.env.BOTOI_API_KEY;

// VPN users: 3 attempts per 15 minutes
// Regular users: 10 attempts per 15 minutes
const VPN_LOGIN_LIMIT = 3;
const STANDARD_LOGIN_LIMIT = 10;
const WINDOW_MS = 15 * 60 * 1000;

const loginAttempts = new Map<string, { count: number; resetAt: number }>();

export async function loginRateLimit(
  req: Request,
  res: Response,
  next: NextFunction
) {
  const ip = req.headers['x-forwarded-for']?.toString().split(',')[0]?.trim()
    || req.socket.remoteAddress
    || 'unknown';

  // Check VPN status
  let isVpn = false;
  try {
    const vpnRes = await fetch(VPN_DETECT_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': \`Bearer \${BOTOI_API_KEY}\`,
      },
      body: JSON.stringify({ ip }),
      signal: AbortSignal.timeout(2000),
    });

    const { data } = await vpnRes.json();
    isVpn = data.is_vpn || data.is_tor || data.is_proxy;
  } catch {
    // Fail open: use standard limits
  }

  const limit = isVpn ? VPN_LOGIN_LIMIT : STANDARD_LOGIN_LIMIT;
  const now = Date.now();
  const entry = loginAttempts.get(ip);

  if (!entry || entry.resetAt < now) {
    loginAttempts.set(ip, { count: 1, resetAt: now + WINDOW_MS });
    return next();
  }

  entry.count++;

  if (entry.count > limit) {
    const retryAfter = Math.ceil((entry.resetAt - now) / 1000);
    return res.status(429).json({
      error: 'Too many login attempts. Try again later.',
      retryAfter,
    });
  }

  next();
}

// Mount on your login route:
// app.post('/api/auth/login', loginRateLimit, loginHandler);

Rasio 3:10 memperlambat serangan otomatis dari IP VPN tanpa mempengaruhi sebagian besar pengguna sebenarnya. Pengguna sah di VPN perusahaan yang salah mengetikkan kata sandinya sebanyak tiga kali masih mendapat a hapus pesan kesalahan dan jendela coba lagi, bukan larangan permanen.

Membangun skor risiko gabungan

Deteksi VPN saja merupakan sinyal penipuan yang lemah. Banyak pengguna sah yang menjalankan VPN. Sinyalnya didapat lebih kuat bila Anda menggabungkannya dengan cek lainnya. Panggil tiga titik akhir botoi secara paralel:

  • /v1/vpn-detect untuk jenis koneksi (VPN, proxy, Tor, pusat data)
  • /v1/disposable-email/check untuk kualitas email (sekali pakai vs permanen)
  • /v1/ip/lookup untuk ketidakcocokan geografis (negara IP vs negara penagihan)

Fungsi ini memanggil ketiganya dan menghasilkan skor risiko 0-100:

interface RiskResult {
  score: number;
  action: 'allow' | 'review' | 'block';
  signals: {
    vpn: boolean;
    tor: boolean;
    proxy: boolean;
    vpnRiskScore: number;
    disposableEmail: boolean;
    geoMismatch: boolean;
    ipCountry: string;
    billingCountry: string;
  };
}

async function calculateRisk(
  ip: string,
  email: string,
  billingCountry: string
): Promise<RiskResult> {
  const BOTOI_KEY = process.env.BOTOI_API_KEY!;
  const headers = {
    'Content-Type': 'application/json',
    'Authorization': \`Bearer \${BOTOI_KEY}\`,
  };

  // Run all three checks in parallel
  const [vpnRes, emailRes, geoRes] = await Promise.all([
    fetch('https://api.botoi.com/v1/vpn-detect', {
      method: 'POST',
      headers,
      body: JSON.stringify({ ip }),
    }),
    fetch('https://api.botoi.com/v1/disposable-email/check', {
      method: 'POST',
      headers,
      body: JSON.stringify({ email }),
    }),
    fetch('https://api.botoi.com/v1/ip/lookup', {
      method: 'POST',
      headers,
      body: JSON.stringify({ ip }),
    }),
  ]);

  const vpnData = (await vpnRes.json()).data;
  const emailData = (await emailRes.json()).data;
  const geoData = (await geoRes.json()).data;

  const ipCountry = geoData.country_code || '';
  const geoMismatch = ipCountry !== '' && billingCountry !== ''
    && ipCountry.toUpperCase() !== billingCountry.toUpperCase();

  // Weight each signal
  let score = 0;

  // VPN/proxy/Tor: up to 30 points
  if (vpnData.is_vpn || vpnData.is_tor || vpnData.is_proxy) {
    score += Math.round(vpnData.risk_score * 0.3);
  }

  // Disposable email: 25 points
  if (emailData.is_disposable) {
    score += 25;
  }

  // Geo mismatch (IP country != billing country): 20 points
  if (geoMismatch) {
    score += 20;
  }

  // VPN + disposable email combo: extra 15 points
  if ((vpnData.is_vpn || vpnData.is_tor) && emailData.is_disposable) {
    score += 15;
  }

  score = Math.min(score, 100);

  return {
    score,
    action: score > 70 ? 'block' : score > 30 ? 'review' : 'allow',
    signals: {
      vpn: vpnData.is_vpn,
      tor: vpnData.is_tor,
      proxy: vpnData.is_proxy,
      vpnRiskScore: vpnData.risk_score,
      disposableEmail: emailData.is_disposable,
      geoMismatch,
      ipCountry,
      billingCountry,
    },
  };
}

// Example usage:
// const risk = await calculateRisk('185.220.101.1', 'user@tempmail.com', 'US');
// if (risk.action === 'review') { queueForManualReview(orderId); }
// if (risk.action === 'block') { rejectTransaction(orderId); }

Ketiga panggilan API dijalankan secara paralel dengan Promise.all, jadi total latensi sama dengan panggilan paling lambat (biasanya di bawah 100 md). Bobot penilaian adalah titik awal. Sesuaikan mereka berdasarkan data penipuan Anda. Jika email sekali pakai adalah sumber tagihan balik terbesar Anda, tingkatkanlah berat itu. Jika ketidakcocokan geografis jarang terjadi dan biasanya tidak berbahaya bagi basis pengguna Anda, kurangi.

Kapan TIDAK memblokir pengguna VPN

Memblokir lalu lintas VPN dengan keras adalah kesalahan bagi sebagian besar aplikasi. Berikut alasan umumnya orang terhubung melalui VPN:

  • Kebijakan perusahaan. Perusahaan merutekan lalu lintas karyawan melalui VPN secara default. Memblokir koneksi ini berarti pelanggan B2B Anda tidak dapat menggunakan produk Anda selama jam kerja.
  • Pribadi. Pengguna yang sadar privasi menjalankan VPN di setiap koneksi sebagai dasar ukuran keamanan. Mereka adalah pelanggan yang membayar, bukan penipu.
  • Akses internet terbatas. Pengguna di negara-negara tertentu mengandalkan VPN untuk menjangkau produk Anda sama sekali. Memblokir VPN akan menguncinya sepenuhnya.
  • Wi-Fi publik. Siapa pun di kedai kopi atau jaringan bandara harus menggunakan a VPN. Menghukum mereka karena kebersihan keamanan yang baik akan menciptakan insentif yang salah.
  • Jurnalis dan peneliti. Orang-orang dengan peran ini menggunakan Tor dan VPN untuk perlindungan sumber dan keamanan operasional. Memblokirnya dapat menimbulkan konsekuensi yang sangat besar.

Pendekatan yang tepat: tandai dan skor, jangan melakukan blokir keras. Gunakan deteksi VPN sebagai salah satu masukan ke a fungsi risiko yang mempertimbangkan banyak sinyal. Arahkan transaksi berisiko tinggi ke antrian peninjauan. Biarkan manusia mengambil keputusan terakhir pada kasus-kasus ambigu.

Poin-poin penting

  • POST /v1/vpn-detect kembali is_vpn, is_proxy, is_tor, is_datacenter, Dan risk_score untuk IP apa pun.
  • Tidak diperlukan kunci API untuk pengujian (5 permintaan per menit). Kunci gratis membuka batas yang lebih tinggi untuk produksi.
  • Lampirkan pemeriksaan VPN ke middleware Express sehingga setiap rute memiliki akses ke data risiko tanpa mengulangi panggilan tersebut.
  • Gabungkan deteksi VPN dengan pemeriksaan email sekali pakai dan geolokasi IP untuk suatu koneksi skor risiko yang cukup kuat untuk ditindaklanjuti.
  • Tandai koneksi VPN untuk ditinjau. Jangan blokir mereka. Pengguna yang sah menjalankan VPN untuk privasi, kebijakan perusahaan, dan akses internet terbatas.

FAQ

Bagaimana cara mendeteksi pengguna VPN di aplikasi saya?
Kirim alamat IP pengguna dalam permintaan POST ke titik akhir botoi /v1/vpn-detect. Responsnya mencakup tanda boolean untuk is_vpn, is_proxy, is_tor, dan is_datacenter, ditambah risk_score 0-100. Panggil titik akhir ini saat mendaftar, masuk, atau checkout untuk menandai koneksi dari layanan anonim.
API deteksi VPN manakah yang terbaik untuk aplikasi produksi?
Carilah API yang mengembalikan tanda terpisah untuk koneksi VPN, proxy, Tor, dan pusat data, bukan boolean tunggal. Titik akhir botoi /v1/vpn-detect mengembalikan keempat tanda ditambah skor risiko numerik, dan berfungsi tanpa kunci API pada 5 permintaan per menit. Untuk beban kerja produksi, kunci API membuka batas kecepatan yang lebih tinggi mulai dari tingkat gratis.
Haruskah saya memblokir semua pengguna VPN dari aplikasi saya?
Tidak. Banyak pengguna sah yang menjalankan VPN demi privasi, kebijakan perusahaan, atau karena mereka tinggal di wilayah dengan akses internet terbatas. Memblokir semua lalu lintas VPN akan mengunci pelanggan yang membayar. Sebagai gantinya, gunakan deteksi VPN sebagai satu sinyal dalam skor risiko gabungan dan tandai koneksi yang mencurigakan untuk ditinjau.
Bisakah saya mendeteksi koneksi proxy dan Tor dengan panggilan API yang sama?
Ya. Titik akhir botoi /v1/vpn-detect mengembalikan flag boolean terpisah untuk is_vpn, is_proxy, dan is_tor dalam satu respons. Anda tidak memerlukan panggilan API terpisah untuk setiap jenis koneksi. Titik akhir juga mengembalikan is_datacenter untuk mengidentifikasi lalu lintas dari penyedia cloud seperti AWS atau Google Cloud.
Bagaimana cara menggabungkan deteksi VPN dengan sinyal penipuan lainnya?
Panggil beberapa titik akhir botoi secara paralel: /v1/vpn-detect untuk jenis koneksi, /v1/disposable-email/check untuk kualitas email, dan /v1/ip/lookup untuk ketidakcocokan geografis antara negara IP dan negara penagihan. Bobot setiap sinyal dan jumlahkan menjadi skor risiko 0-100. Scores above 70 go to manual review; skor di bawah 30 lolos.

Mulai membangun dengan botoi

150+ endpoint API untuk pencarian, pemrosesan teks, pembuatan gambar, dan utilitas developer. Paket gratis, tanpa kartu kredit.