واجهة API لكشف احتيال IP: قيّم عمليات التسجيل والدخول والدفع في Node وPython
أضف واجهة API لكشف احتيال IP إلى التسجيل والدخول والدفع: شيفرة Node وPython شغّلناها على ست عناوين IP عامة، مع المخرجات الحقيقية والتخزين المؤقت وحدود المعدّل.

يصل تسجيلان بفارق ثانية، وكلاهما يُخفي عنوان منزل صاحبه. أحدهما من عقدة خروج Tor في هامبورغ. والآخر من iCloud Private Relay، الذي تصفه Apple بأنه "جزء من اشتراك iCloud+" يحميك "عند تصفح الويب في Safari" (دعم Apple). القاعدة التي تحظر "كل ما هو مخفي" سترفض الاثنين، وأحدهما عميل دافع لدى Apple.
التمييز بين هذين هو المهمة. وهو شريحة واحدة من نطاق ما يغطيه ذكاء عناوين IP، ويتّسع له ملف واحد من أقل من 50 سطرًا. ستجد أدناه الطلب، واستجابة حقيقية واحدة، ونسختي Node وPython مع التخزين المؤقت ومعالجة الأعطال، وما طبعتاه لست عناوين، ومواضع قصور الفحص.

الطلب الواحد الذي تحتاجه واجهة API لكشف احتيال IP
التكامل كله طلب GET واحد مع عنوان IP في المسار ومفتاحك في ترويسة x-api-key. يوثّق مرجع API الشكل نفسه:
curl https://api.geoiphub.com/v1/lookup/45.83.220.7 \
-H "x-api-key: YOUR_API_KEY"
ثلاثة تفاصيل تُوقع الناس في المحاولة الأولى:
- عنوان IP يوضع في المسار.
/v1/lookup/{ip}هو الشكل الوحيد. نسخة سلسلة الاستعلام تُعيد التوجيه بدل إعادة البيانات. - لا ترسل العنصر النائب. أرسلنا النص الحرفي
YOUR_API_KEYفحصلنا على401مع{"message":"Invalid API key"}. اترك الترويسة إلى أن تحصل على مفتاح حقيقي؛ فالاستدعاءات المجهولة تعمل بمعدل 30 في الدقيقة. - عنوان IP واحد لكل استدعاء. يأخذ كل طلب عنوانًا واحدًا، فالسكربت الذي يفحص قائمة يرسل طلبًا لكل عنوان ويحتسب كل طلب ضمن حدّك.
أنشئ مفتاحًا مجانيًا في لوحة التحكم حين تتجاوز مرحلة الاختبار. تمنح الخطة المجانية ذلك المفتاح 1,500 استعلام يوميًا، مع تضمين جميع الحقول.
ما الذي عاد لعنوان Mullvad، وأي الحقول نتفرّع عليها
الاستجابة نحو 2.7 كيلوبايت من JSON المتداخل في عشر مجموعات، ويحتاج فحص الاحتيال إلى مجموعتين منها: detection وscoring. هذا هو الاستعلام عن 45.83.220.7، مقتطعًا لكن بقيم لم تُمسّ:
{
"ip": "45.83.220.7",
"asn": {
"asn": 39351,
"org": "ESAB-AS - 31173 Services AB",
"asn_type": "hosting",
"connection_type": "datacenter"
},
"geo": {
"country_code": "SE",
"city": "Malmö"
},
"detection": {
"is_vpn": true,
"is_proxy": false,
"is_tor": false,
"is_hosting": true,
"is_relay": false,
"vpn_provider": "Mullvad (31173 Services)",
"vpn_confidence": 85
},
"scoring": {
"fraud_score": 46,
"confidence": 0.56,
"recommended_action": "review",
"detection_methods": [
"known_vpn_server",
"datacenter_ip"
]
},
"meta": {
"last_classified_at": "2026-10-16T07:40:17.524Z"
}
}
هذا العنوان تابع لـ 31173 Services، الشبكة التي يشغّل عليها Mullvad VPN خوادمه، في مالمو. وحين فحصناه في 13 أكتوبر لم يكن في قائمة خوادم Mullvad المنشورة. انطلقت إشارتان. جاءت known_vpn_server من جدولنا لشبكات استضافة VPN المعروفة، ولهذا فإن vpn_confidence تساوي 85 لا 95 التي تعطيها مطابقة قائمة خوادم Mullvad نفسها. وتقول datacenter_ip إنه استضافة لا اتصال منزلي. معًا تعطيان درجة 46، تقع في review: فالـ VPN وحده ليس احتيالًا.
ثلاثة حقول تحمل القرار:
| الحقل | ما يحتويه | كيف تستخدمه الشيفرة |
|---|---|---|
scoring.recommended_action | allow (الدرجة حتى 25)، review (حتى 50)، step_up (حتى 75)، block (فوق 75) | التفرّع |
scoring.fraud_score | الدرجة المركّبة من 0 إلى 100 خلف ذلك الإجراء | التسجيل والضبط لاحقًا |
scoring.detection_methods | الإشارات التي انطلقت، مثل tor_exit أو openvpn_open | السبب الذي تخزّنه وتعرضه على المراجعين |
علامات detection (is_vpn وis_proxy وis_tor وis_hosting وis_relay) هي الحقائق الخام خلف الدرجة. استخدمها في القواعد القليلة التي يختلف فيها عملك عن النطاقات الافتراضية. وللدرجة أيضًا إشارات تبرئة: ISP سكني يخصم 20 نقطة وزاحف موثَّق يخصم 30، فلا تدفع علامة وحدها عنوانًا إلى block. يشرح دليل درجة احتيال IP ما الذي يحرّك الرقم.
نسخة Node: fetch وتخزين مؤقت لمدة ساعة وأربعة فروع
هذا هو الملف نفسه الذي شغّلناه، على Node 24 دون أي اعتماديات. يحتاج إلى Node 18 أو أحدث لوجود fetch المدمج:
// screen.mjs: score an IP before a signup, login or checkout. Node 18+, no dependencies.
const API = "https://api.geoiphub.com/v1/lookup/";
const KEY = process.env.GEOIPHUB_API_KEY; // without a key: 30 lookups a minute, for testing only
const TTL_MS = 60 * 60 * 1000; // reuse a verdict for an hour
const cache = new Map(); // ip -> { at, data }
async function lookup(ip) {
const hit = cache.get(ip);
if (hit && Date.now() - hit.at < TTL_MS) return hit.data;
const headers = KEY ? { "x-api-key": KEY } : {};
const res = await fetch(API + encodeURIComponent(ip), {
headers,
signal: AbortSignal.timeout(3000),
});
if (res.status === 429) throw new Error(`rate limited, retry after ${res.headers.get("retry-after")}s`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
cache.set(ip, { at: Date.now(), data });
return data;
}
export async function screen(ip, flow) {
let data;
try {
data = await lookup(ip);
} catch (err) {
return { action: "review", reason: `lookup failed: ${err.message}` }; // fail soft, never block on our outage
}
const { scoring, detection } = data;
let action = scoring.recommended_action; // "allow" | "review" | "step_up" | "block"
if (detection.is_tor) action = "block"; // no step-up through a Tor exit
if (flow === "checkout" && action === "review") action = "step_up"; // stricter where money moves
const flags = ["is_vpn", "is_proxy", "is_tor", "is_hosting", "is_relay"].filter((f) => detection[f]);
return { action, score: scoring.fraud_score, flags, methods: scoring.detection_methods };
}
// Try it: node screen.mjs signup 45.33.32.156 104.28.46.99
const [flow, ...ips] = process.argv.slice(2);
for (const ip of ips) {
const v = await screen(ip, flow);
console.log(ip.padEnd(15), v.action.padEnd(8), v.score ?? "-", v.flags?.join(",") || "-", v.methods?.join(",") || v.reason);
}
أربعة قرارات فيه مقصودة:
- يبدأ من
recommended_actionويتجاوزه في موضعين فقط. عقدة خروج Tor تُحظر دائمًا، وعند الدفع يصبحreviewهوstep_up، لأن هناك تخرج الأموال. - يفشل بليونة. المهلة أو خطأ 5xx أو 429 تعيد
reviewلاblock. إذا تعذّر الوصول إلى واجهتنا، تستمر عمليات التسجيل لديك وتُسجَّل لنظرة ثانية. - المهلة 3 ثوان. من جانبنا، يُجاب عن عنوان IP معروف من قاعدة بيانات معيَّنة في الذاكرة في أقل من ميلي ثانية، فيكون معظم الانتظار زمن ذهاب وإياب الشبكة. أما عنوان IP لم نره من قبل فيُصنَّف مباشرة في أقل من 2.5 ثانية، وتغطي 3 ثوان تلك الحالة.
- يبقى المفتاح على خادمك. استدعِ الواجهة من الخادم الخلفي، وليس من شيفرة المتصفح أبدًا، حيث يستطيع أي شخص قراءة المفتاح.
نسخة Python تحتاج إلى User-Agent وإلا حصلت على 403
ترد واجهتنا على قيمة User-Agent الافتراضية لمكتبة Python urllib، وهي Python-urllib/3.x، بالرد 403 Forbidden. أعدنا إنتاج ذلك في 16 أكتوبر الساعة 05:36 UTC باستدعاء urllib.request.urlopen مجرد: HTTP Error 403: Forbidden. لا يواجه curl ولا requests هذه المشكلة لأنهما يرسلان قيم User-Agent مختلفة. لذلك يعيّن ملف Python قيمته الخاصة، مع تعليق يوضح السبب:
# screen.py: score an IP before a signup, login or checkout. Python 3.8+, standard library only.
import json, os, sys, time, urllib.error, urllib.request
API = "https://api.geoiphub.com/v1/lookup/"
KEY = os.environ.get("GEOIPHUB_API_KEY") # without a key: 30 lookups a minute, for testing only
TTL = 3600 # reuse a verdict for an hour
_cache = {} # ip -> (time, data)
def lookup(ip):
hit = _cache.get(ip)
if hit and time.time() - hit[0] < TTL:
return hit[1]
# Set a User-Agent: our API answers urllib's default "Python-urllib/3.x" with 403 Forbidden.
headers = {"User-Agent": "signup-screen/1.0"}
if KEY:
headers["x-api-key"] = KEY
req = urllib.request.Request(API + ip, headers=headers)
with urllib.request.urlopen(req, timeout=3) as resp:
data = json.load(resp)
_cache[ip] = (time.time(), data)
return data
def screen(ip, flow):
try:
data = lookup(ip)
except urllib.error.HTTPError as e:
wait = e.headers.get("Retry-After") if e.code == 429 else None
return {"action": "review", "reason": f"HTTP {e.code}" + (f", retry after {wait}s" if wait else "")}
except (OSError, ValueError) as e: # network error, timeout, bad JSON
return {"action": "review", "reason": f"lookup failed: {e}"} # fail soft
scoring, detection = data["scoring"], data["detection"]
action = scoring["recommended_action"] # "allow" | "review" | "step_up" | "block"
if detection["is_tor"]:
action = "block" # no step-up through a Tor exit
if flow == "checkout" and action == "review":
action = "step_up" # stricter where money moves
flags = [f for f in ("is_vpn", "is_proxy", "is_tor", "is_hosting", "is_relay") if detection[f]]
return {"action": action, "score": scoring["fraud_score"], "flags": flags,
"methods": scoring["detection_methods"]}
if __name__ == "__main__":
flow, ips = sys.argv[1], sys.argv[2:]
for ip in ips:
v = screen(ip, flow)
print(f"{ip:<15} {v['action']:<8}", v.get("score", "-"), ",".join(v.get("flags", [])) or "-",
",".join(v.get("methods", [])) or v.get("reason", ""))
هو المنطق نفسه في ملف Node، باستخدام المكتبة القياسية فقط، وقد اختُبر على Python 3.9.6. تُطلق urllib الاستثناء HTTPError لأي حالة غير 2xx، ولذلك تُقرأ 429 وترويسة Retry-After من الاستثناء.
ما طبعه السكربتان لست عناوين عامة
أعطى السكربتان الحكم نفسه لكل عنوان. شغّلنا ملف Node في الساعة 07:39 UTC وملف Python في الساعة 07:40 UTC من 16 أكتوبر 2026، دون مفتاح، على ست عناوين عامة. الأمر الأخير يعيد تشغيل عنوان Mullvad بصفته عملية دفع:
node screen.mjs signup 45.33.32.156 104.28.46.99 45.83.220.7 91.90.122.25 102.129.235.17 185.220.101.1
node screen.mjs checkout 45.83.220.7
45.33.32.156 allow 10 is_hosting datacenter_ip
104.28.46.99 allow 9 is_relay relay_ip
45.83.220.7 review 46 is_vpn,is_hosting known_vpn_server,datacenter_ip
91.90.122.25 step_up 64 is_hosting datacenter_blocklisted,no_ptr_datacenter,known_spammer
102.129.235.17 block 92 is_vpn known_vpn_server,openvpn_open,ipsec_open
185.220.101.1 block 100 is_tor,is_hosting tor_exit,datacenter_blocklisted,no_ptr_datacenter,known_spammer
45.83.220.7 step_up 46 is_vpn,is_hosting known_vpn_server,datacenter_ip
أما python3 screen.py بالوسائط نفسها فطبع الأسطر السبعة نفسها حرفًا بحرف. وهذا ما هو كل عنوان ولماذا استقر حيث استقر:
| العنوان | ما هو | الإجراء | الإشارات التي انطلقت |
|---|---|---|---|
45.33.32.156 | scanme.nmap.org، مضيف الاختبار العام لـ Nmap على Linode | allow، 10 | datacenter_ip فقط. الاستضافة وحدها ليست احتيالًا |
104.28.46.99 | مخرج iCloud Private Relay على Cloudflare | allow، 9 | relay_ip. تضع الدرجة سقفًا قدره 60 لعناوين الترحيل، فلا تصل أبدًا إلى block |
45.83.220.7 | شبكة Mullvad في مالمو | review، 46؛ وstep_up عند الدفع | known_vpn_server وdatacenter_ip |
91.90.122.25 | خادم PIA VPN في شان، ليختنشتاين، على M247 | step_up، 64 | datacenter_blocklisted وno_ptr_datacenter وknown_spammer. لم يُوسم كـ VPN |
102.129.235.17 | خادم VPN تجاري في واشنطن العاصمة | block، 92 | known_vpn_server، إضافةً إلى openvpn_open وipsec_open: أكملت فحوصاتنا مصافحات OpenVPN وIPsec |
185.220.101.1 | عقدة خروج Tor في هامبورغ | block، 100 | tor_exit وdatacenter_blocklisted وno_ptr_datacenter وknown_spammer |
استقر التسجيلان المذكوران في المقدمة على طرفي نقيض: عقدة خروج Tor عند 100 ومستخدم Private Relay عند 9. هذا التباين هو سبب التقييم بدل حظر كل عنوان مخفي.
تتحرك النتائج مع تغيّر بياناتنا. حتى إعادة الكتابة هذه، كان هذا المقال يعرض 45.83.220.7 كاستضافة عادية بدرجة 10 وallow. وبحلول 8 أكتوبر كان ماسحنا قد ربطه بـ Mullvad، وهو الآن يسجّل 46. سجّل الإجراء والطرق مع كل قرار، كي يظهر تغيّر كهذا في بياناتك بدل أن يفاجئك.
أين تستدعيه: كل تسجيل ودخول ودفع
قيّم كل تسجيل وكل دخول وكل دفع، على الخادم، قبل أن تُثبّت الإجراء. هذه هي اللحظات الثلاث التي يربح فيها المحتال شيئًا، وهي نادرة بما يكفي لكل مستخدم لتتسع لها ميزانية يومية. لا تستدعه عند كل عرض صفحة.
ما يعنيه كل إجراء عمليًا:
| الإجراء | التسجيل | الدخول | الدفع |
|---|---|---|---|
allow | أنشئ الحساب | سجّل الدخول | اقبل الدفعة |
review | أنشئه وعلّمه للمراجعة | سجّل الدخول وسجّل السبب | تحوّله شيفرتنا إلى step_up |
step_up | تحقق من البريد أو الهاتف أولًا | اطلب العامل الثاني | شغّل 3-D Secure |
block | ارفض برسالة محايدة | ارفض واحتسبه ضمن القفل | ارفض الطلب |
النطاقات الافتراضية نقطة انطلاق، وينبغي أن تحرّكها بيانات الاحتيال الخاصة بك. يعرض دليلنا حدود درجة الاحتيال للدخول والتسجيل والدفع حدودًا ابتدائية لكل إجراء مع مبرراتها.
وقيّم العنوان الصحيح أيضًا. خلف موازن حمل أو CDN، يكون عنوان المقبس هو الوكيل لديك، ويوجد عنوان IP الخاص بالعميل في ترويسة إعادة توجيه يستطيع أي عميل تزويرها كذلك. خذه من القفزة التي أضافها وكيلك الموثوق، لا من القيمة الواقعة أقصى اليسار التي أرسلها العميل.
البقاء ضمن 1,500 استعلام يوميًا: التخزين المؤقت وحد المعدّل
تسمح الخطة المجانية بـ 1,500 استعلام يوميًا. خزّن مؤقتًا حسب IP في شيفرتك وستمتد الميزانية أبعد بكثير، لأن الشخص الواحد يسجّل ويدخل ويدفع عادةً من العنوان نفسه خلال ساعة.
لن تساعد الواجهة نفسها في التخزين المؤقت: كل استجابة تحمل Cache-Control: private, no-store، فلا تحتفظ بها أي ذاكرة HTTP مشتركة. يحتفظ السكربتان بالحكم ساعة في ذاكرة العملية. استخدم Redis أو ذاكرتك المؤقتة الحالية إن شغّلت أكثر من خادم. ولا تحتفظ بحكم أكثر من يوم، لأن مصادر التهديدات وقاعدة بيانات الاستعلام لدينا تتحدث يوميًا.
يختلف سلوك الحدّين، وقد اصطدمنا بأحدهما أثناء اختبار هذا المقال:
| الاستدعاء بصفة | الحد | ما يحدث بعده |
|---|---|---|
| بلا مفتاح | 30 استعلامًا في الدقيقة لكل عنوان IP للمتصل | الطلب الثلاثون خلال دقيقة يعيد 429 ويحظر ذلك العنوان ساعتين |
| مفتاح مجاني | 1,500 استعلام يوميًا | 429 مع ترويسة Retry-After حتى يُعاد ضبط العدّاد اليومي |
في الساعة 05:37 UTC من 16 أكتوبر أطلقنا دفعة من الطلبات المجهولة لرؤية 429. خلال عشرين ثانية أعادت الواجهة 429 مع Retry-After: 7199، وبقي كل استدعاء مجهول من عنواننا محظورًا ساعتين، بما في ذلك سكربتاتنا. مسار الفشل الليّن فيهما هو أول مخرجات حقيقية حصلنا عليها:
45.33.32.156 review - - lookup failed: rate limited, retry after 6971s
45.33.32.156 review - - HTTP 429, retry after 6970s
لذا تعامل مع الحد المجهول كمسموح للاختبار، واستخدم مفتاحًا في الإنتاج. تفرّع حسب رمز الحالة وRetry-After، لا حسب نص رسالة 429.
أين يقصّر هذا الفحص
حكم IP إشارة واحدة، وتفوته أشياء. ظهرت أربع حالات أثناء كتابة هذا المقال:
- قد يعود خادم VPN مدرج نظيفًا. كان خادم PIA
91.90.122.25في قائمة PIA المنشورة في 4 أكتوبر، ومع ذلك تعيد واجهتناis_vpn: false. لم يصل إلىstep_upإلا لأنه في قائمة حظر DNS. ولولا ذلك التاريخ لمرّ كاستضافة عادية. - البروكسيات السكنية غير مرئية في معظمها لبيانات IP. الحركة الممرَّرة عبر اتصالات منزلية حقيقية تبدو كمستخدم منزلي. الحقل
is_residential_proxyاستدلال تقريبي من الشبكة وليس كشفًا، فلا تبنِ عليه قاعدة. - العناوين المشتركة تحمل تاريخ آخرين. تضع شركات الجوال وCGNAT مستخدمين كثيرين خلف عنوان واحد، ولهذا تضع الدرجة سقفًا لنطاقات CGNAT ولهذا ينبغي أن يبقى
blockنادرًا هناك. - مواقع الترحيل تقريبية. بالنسبة لـ
104.28.46.99يقول تحديد الموقع لدينا إنه لواندا، أنغولا، بينما تضع قائمة مخارج Apple نفسها ذلك النطاق في لوساكا، زامبيا. استخدم علامة الترحيل لا المدينة في القرارات، كما يشرح دليلنا لتمرير Private Relay.
اقرن فحص IP بما تعرفه عن الحساب: عمر البريد والجهاز وتاريخ الدفع والسرعة. يبيّن دليل منظومة احتيال الدفع موقع إشارات IP بينها.
جرّبه على حركتك الخاصة أولًا
قبل ربطه بمسار حي، شغّل أيًّا من السكربتين على يوم من عناوين IP للتسجيلات السابقة بمفتاح مجاني، وقارن إجراءاته بما تعرفه أصلًا عن تلك الحسابات. إذا كان block سيلتقط حسابات حظرتها لاحقًا، وكان allow يغطي العملاء الذين أبقيتهم، فضعه أمام التسجيل أولًا، ثم الدخول، ثم الدفع. ولفحص عنوان واحد يدويًا، تعرض أداة فحص درجة احتيال IP الدرجة والأسباب نفسها دون أي شيفرة.
الأسئلة الشائعة
هل واجهة GeoIPHub API مجانية للبدء؟
نعم. تشمل الخطة المجانية 1,500 استعلام يوميًا مع جميع الحقول، ومفتاح API واحدًا، دون بطاقة ائتمان. وبدون مفتاح يمكنك إجراء 30 استعلامًا في الدقيقة للاختبار.
أي حقل ينبغي أن تتفرّع عليه شيفرتي؟
scoring.recommended_action. قيمته allow حتى درجة 25، وreview حتى 50، وstep_up حتى 75، وblock فوق 75. اقرأ scoring.detection_methods لتسجيل السبب.
لماذا يحصل سكربت Python لديّ على 403؟
ترد واجهتنا على User-Agent الافتراضي لمكتبة urllib، وهو Python-urllib/3.x، بالرد 403 Forbidden. عيّن ترويسة User-Agent خاصة بك وسيعيد الطلب نفسه 200.
هل يمكنني تخزين الاستجابة مؤقتًا؟
نعم، في شيفرتك أنت. ترسل الواجهة Cache-Control: private, no-store، ولذلك لن تحتفظ بها ذواكر HTTP المشتركة. نحن نعيد استخدام الحكم لمدة ساعة لكل عنوان IP.
هل سيحظر عملائي الحقيقيين؟
لا، إذا استخدمت الإجراءات الأربعة كما صُمّمت. عاد عنوان iCloud Private Relay بالنتيجة allow بدرجة 9 في تجربتنا، وعاد خادم سحابي عادي بـ allow عند 10. فقط عقدة خروج Tor وخادم VPN مؤكَّد بالفحص وصلا إلى block.
هل أحتاج إلى إرسال بيانات شخصية؟
لا. ترسل عنوان IP وتحصل على معلومات استخبارية عن ذلك العنوان. تبقى الأسماء والبريد الإلكتروني وبيانات البطاقة داخل نظامك.
الفريق الذي يقف وراء واجهة GeoIPHub API
بقلم فريق GeoIPHub، وهم من يبنون واجهة GeoIPHub API لاستخبارات IP وماسحها. تُعَدّ المسودات بمساعدة الذكاء الاصطناعي ويحررها الفريق، وأمثلة API استعلامات حقيقية على واجهة API الحية ما لم تُوصف بأنها توضيحية.


