דלגו לתוכן

או התחילו מנקודה בטוחה: אינדקס הכלים המלא · מפת הטבלאות המרכזיות

מערכת חדשה — 14 יום חינם

דוגמה — שרת ביניים לטופס

עודכן 30.08.2026
Browser → POST /api/lead (your server) → POST https://api.mbapps.co.il/functions/APP_ID/getlead

getlead נועד להיקרא ישירות מהדפדפן, וברוב דפי הנחיתה זו התשובה הנכונה. יש בדיוק שלושה מצבים שבהם צריך חוליה באמצע: CAPTCHA (ה‑secret שלו לא יכול לשבת בדפדפן), לוגיקה או נתונים שאסור לחשוף (מפות Pointer, שדות מחושבים, כללי ניתוב), ודרישה לרישום או לוולידציה משלכם לפני שהפנייה נכנסת ל‑CRM.

העמוד הזה הוא המימוש המלא של החוליה הזו — בחמש שפות, עם כל מסלולי הכישלון סגורים.

מתי צריך שרת ביניים ומתי לא

Section titled “מתי צריך שרת ביניים ומתי לא”
המצב ישירות מהדפדפן דרך שרת ביניים
טופס יצירת קשר פשוט ✔ עדיף — פחות חלקים נעים מיותר
מלכודת דבש בלבד ✔ מספיק מיותר
reCAPTCHA / Turnstile ✘ ה‑secret ידלוף חובה
מיפוי Pointer שאסור לחשוף ✘ המפה בקוד המקור של הדף
שדה מחושב (ניקוד ליד, ניתוב לצוות)
רישום הפניות אצלכם בנוסף ל‑CRM
הגבלת קצב לפי IP ✘ אין לדפדפן שליטה
טופס שכבר קיים בתוסף WordPress תלוי בתוסף — ראו הטמעה בפועל ✔ נפוץ מאוד

החוזה שהשרת שלכם מחזיר לדפדפן

Section titled “החוזה שהשרת שלכם מחזיר לדפדפן”

הפיתוי הוא להעביר את התשובה של getlead כמו שהיא. אל תעשו את זה: קוד הסטטוס של המערכת מספר על המערכת, והדפדפן צריך לדעת מה להגיד למשתמש. הגדירו חוזה משלכם — קטן, קבוע, ובעל שדה code שאפשר לתרגם בממשק:

JSON
{ "ok": true, "leadId": "xK9mP2qRsT" }
{ "ok": false, "code": "VALIDATION", "message": "נא למלא טלפון או אימייל תקינים" }
מה קרה מול getlead מה השרת שלכם מחזיר ה‑code מה המשתמש רואה מה אתם עושים
200 + success: true 200 עמוד תודה שולחים אירוע המרה
400 + error של שדה חסר/לא תקין (למשל missing "PhoneNumber" or "Email") 400 VALIDATION “נא למלא טלפון או אימייל תקינים” כלום — זו טעות של המשתמש
400 + AcceptWebLeads is Disabled 503 DISABLED “אירעה תקלה, נסו שוב מאוחר יותר” התראה לצוות. זו תקלת תצורה, לא של המשתמש
5xx 502 UPSTREAM “אירעה שגיאה, נסו שוב” לוג + ניטור
timeout / שגיאת רשת 504 TIMEOUT “אין חיבור, נסו שוב” לוג + ניטור
מלכודת הדבש מלאה 200 מזויף עמוד תודה לא שולחים כלום ל‑CRM
CAPTCHA נכשל 400 CAPTCHA “נא לאשר שאינכם רובוט” ספירה — ריבוי כשלונות = תקיפה

צד הדפדפן — טופס עם מלכודת דבש

Section titled “צד הדפדפן — טופס עם מלכודת דבש”

החלק הזה זהה בכל השפות: הדפדפן מדבר רק עם השרת שלכם, ולכן אין בו לא Application Id ולא מפות Pointer.

HTML
<form id="lead-form" novalidate>
<input name="fullName" placeholder="שם מלא" />
<input name="phone" type="tel" placeholder="טלפון" required />
<input name="email" type="email" placeholder="אימייל" />
<input name="city" placeholder="עיר" />
<!-- מלכודת דבש: בוט ימלא, אדם לא יראה. אל תקראו לשדה "honeypot". -->
<input name="company_website" tabindex="-1" autocomplete="off"
style="position:absolute;left:-9999px" aria-hidden="true" />
<!-- ווידג'ט Turnstile — ה-sitekey ציבורי; ה-secret יושב בשרת בלבד -->
<div class="cf-turnstile" data-sitekey="0x0000000000000000000000"></div>
<button type="submit">שליחה</button>
<p class="form-msg" role="status" aria-live="polite"></p>
</form>

הכללים שהופכים את זה לטופס שעובד גם ביום רע: כפתור שננעל בזמן השליחה (כדי שלחיצה כפולה לא תיצור שני לידים), הודעה ב‑aria-live כדי שקורא מסך ישמע את השגיאה, ואירוע ההמרה ל‑Analytics רק אחרי הצלחה.

PHP
<?php
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
$APP_ID = getenv('MB_APP_ID'); // מזהה ציבורי — בקונפיג לנוחות בלבד
$TS_SECRET = getenv('TURNSTILE_SECRET'); // סוד — ממנהל סודות או ממשתנה סביבה
$ENDPOINT = "https://api.mbapps.co.il/functions/{$APP_ID}/getlead";
/** תשובה אחידה לדפדפן, ויציאה. */
function reply(int $status, array $body): never
{
http_response_code($status);
echo json_encode($body, JSON_UNESCAPED_UNICODE);
exit;
}
$input = json_decode(file_get_contents('php://input') ?: '[]', true) ?? [];
// 1) מלכודת דבש — בוט. מחזירים "הצלחה" ולא שולחים כלום.
if (!empty($input['company_website'])) {
reply(200, ['ok' => true]);
}
// 2) אימות טוקן ה-CAPTCHA — לפני כל דבר אחר
$verify = json_decode((string) file_get_contents(
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
false,
stream_context_create(['http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => json_encode(['secret' => $TS_SECRET, 'response' => $input['token'] ?? '']),
'timeout' => 5,
'ignore_errors' => true,
]])
), true);
if (empty($verify['success'])) {
reply(400, ['ok' => false, 'code' => 'CAPTCHA', 'message' => 'נא לאשר שאינכם רובוט']);
}
// 3) רשימת היתר — רק שדות שאנחנו מכירים עוברים הלאה
$data = array_filter([
'Name' => $input['fullName'] ?? null,
'PhoneNumber' => $input['phone'] ?? null,
'Email' => $input['email'] ?? null,
// עיר כטקסט חופשי הולכת ל-CityText; השדה City הוא Pointer ל-CityList
'CityText' => $input['city'] ?? null,
// Pointer — objectId חשוף בן 10 תווים. המפה נשארת בשרת:
'LeadSourceId' => ['facebook' => 'vOZLqOWnQf', 'google' => 'fHG8Rgy8Hr'][$input['source'] ?? ''] ?? null,
], static fn ($v) => $v !== null && $v !== '');
if (empty($data['PhoneNumber']) && empty($data['Email'])) {
reply(400, ['ok' => false, 'code' => 'VALIDATION', 'message' => 'נא למלא טלפון או אימייל']);
}
// 4) העברה ל-getlead
$ch = curl_init($ENDPOINT);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => ['Content-Type: application/json',
"X-Parse-Application-Id: {$APP_ID}"],
CURLOPT_POSTFIELDS => json_encode($data, JSON_UNESCAPED_UNICODE),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$netErr = curl_error($ch);
curl_close($ch);
// 5) מיפוי לחוזה שלנו
if ($body === false) {
error_log("getlead network error: {$netErr}"); // בלי פרטי המשתמש
reply(504, ['ok' => false, 'code' => 'TIMEOUT', 'message' => 'אין חיבור, נסו שוב']);
}
if ($status === 200) {
$json = json_decode($body, true) ?: [];
$result = $json['result'] ?? []; // getlead עוטפת את ההצלחה ב-result
if (!empty($result['success'])) reply(200, ['ok' => true, 'leadId' => $result['leadId'] ?? '']);
error_log('getlead 200 without success flag');
reply(502, ['ok' => false, 'code' => 'UPSTREAM', 'message' => 'אירעה שגיאה, נסו שוב']);
}
if ($status === 400) { // כל שגיאה עסקית חוזרת כ-400 — מסתעפים על הגוף
$err = json_decode($body, true)['error'] ?? '';
if (str_contains($err, 'Disabled')) {
error_log('ALERT: AcceptWebLeads is disabled — every web lead is being dropped');
reply(503, ['ok' => false, 'code' => 'DISABLED', 'message' => 'אירעה תקלה, נסו שוב מאוחר יותר']);
}
if (str_contains($err, 'missing')) {
reply(400, ['ok' => false, 'code' => 'VALIDATION', 'message' => 'נא למלא טלפון או אימייל תקינים']);
}
error_log("getlead 400: {$err}"); // למשל "User function not found" — תקלת שרת/תצורה, לא של המשתמש
reply(502, ['ok' => false, 'code' => 'UPSTREAM', 'message' => 'אירעה שגיאה, נסו שוב']);
}
error_log("getlead {$status}: {$body}");
reply(502, ['ok' => false, 'code' => 'UPSTREAM', 'message' => 'אירעה שגיאה, נסו שוב']);

אימות ה‑CAPTCHA — למה דווקא לפני הכול

Section titled “אימות ה‑CAPTCHA — למה דווקא לפני הכול”

הטוקן שהווידג’ט מייצר בדפדפן הוא חד‑פעמי, והאימות מולו דורש secret. שני כללים:

  1. מאמתים לפני שמעבירים. אם תעבירו קודם ל‑getlead ותאמתו אחר כך, כבר יצרתם ליד — והבוט הצליח.
  2. ה‑secret לא נכנס לקוד. משתנה סביבה או מנהל סודות. אצלנו: get-secret; אצלכם — מה שיש.

נקודות הקצה של שכבת הטפסים אינן מאמתות reCAPTCHA, Turnstile או מלכודות דבש בעצמן. אין שם שדה טוקן, ושליחת אחד כזה פשוט נזרקת בשקט כמו כל מפתח לא מוכר (אומת על web2table: token ו‑company_website בגוף → 200, לא נוצר שדה בסכימה). כל האנטי‑ספאם הוא באחריותכם — ראו מודל האבטחה.

שבע מלכודות של שרת ביניים

Section titled “שבע מלכודות של שרת ביניים”
המלכודת מה קורה מה עושים
העברת הגוף כמו שהואfetch(ENDPOINT, { body: req.body }) כל מפתח שהתוקף שולח מגיע לפונקציה. מפתחות לא מוכרים אמנם נזרקים, אבל שדות אמיתיים שלא התכוונתם לחשוף (למשל LeadStatusId) כן ייקלטו רשימת היתר מפורשת, כמו בכל הדוגמאות כאן
בלי timeout בקשה תלויה תופסת worker; מספיק שהמערכת תאט כדי שהשרת שלכם ייתקע timeout על הקריאה ל‑CAPTCHA וגם על הקריאה ל‑getlead
PII בלוגים טלפונים ואימיילים של לידים נכתבים ללוג ונשמרים שנה לוגים בלי גוף הבקשה. מזהה בקשה, קוד סטטוס, וזהו
ניסיון חוזר על 400 הבקשה תיכשל שוב בדיוק באותה צורה חזרו רק על שגיאות רשת ו‑5xx, עם השהיה. ראו מגבלות
«Disabled» בשקט כל הלידים נעלמים ואף אחד לא יודע התראה תפעולית, לא רק לוג
CORS על הנקודה שלכם הטופס יושב בדומיין אחר מהשרת והדפדפן חוסם הגישו את הטופס מאותו דומיין, או החזירו Access-Control-Allow-Origin מדויק (לא * על נקודה שמקבלת נתונים)
200 מזויף למלכודת דבש קל לשכוח, והבוט לומד שהשדה מסגיר אותו תמיד להחזיר בדיוק את אותה תשובה כמו הצלחה
  • שליחה תקינה יוצרת ליד ב‑Accounts עם IsAccount: false, והשדות שנשלחו אכן מלאים (היום — דרך web2table, ראו המגבלה למעלה)
  • שדה Pointer (למשל LeadSourceId) אינו ריק — אם הוא ריק, הערך לא היה objectId בן 10 תווים
  • שליחה בלי טלפון ובלי אימייל מחזירה 400 עם code: "VALIDATION" ולא נוצר ליד
  • מילוי מלכודת הדבש מחזיר 200 ולא נוצר ליד
  • טוקן CAPTCHA שגוי מחזיר 400 עם code: "CAPTCHA"
  • כיבוי זמני של הדגל (400 … is Disabled מהשרת) מחזיר מהשרת שלכם 503 ומייצר התראה (בדקו את זה פעם אחת בכוונה)
  • הלוגים אינם מכילים טלפונים ואימיילים
  • אירוע ההמרה ב‑Analytics נורה רק במסלול ההצלחה