דלגו לתוכן

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

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

דוגמה — הרשמה לקורס עם web2table

עודכן 30.08.2026

דף נחיתה של קורס. הגולש בוחר קורס מרשימה, מסמן נושאי עניין, ממלא פרטים ולוחץ “הרשמה”. מה שצריך לקרות ב‑CRM: שורה בטבלת ההרשמות, מקושרת לאיש קשר — קיים אם הוא כבר במערכת, חדש אם לא.

זו בדיוק העבודה של web2table, והעמוד הזה הוא המימוש המלא שלה — כולל שני הטיפוסים שהכי קל לטעות בהם (Pointer ובחירה מרובה) ותאריך בפורמט שהשכבה הזו מצפה לו.

הטבלה התפקיד השדות שהדוגמה נוגעת בהם
Accounts איש הקשר (ליד או לקוח) F_name, L_name, PhoneNumber, Email, City, CompanyId
Courses טבלת lookup של הקורסים objectId, Name
CourseTopics טבלת lookup לנושאי עניין objectId, Name
CourseRegistrations טבלת היעד — שורה לכל הרשמה CourseId (Pointer→Courses), StartDate (Date), Comment (String), IsPaid (Boolean), array_topics_Pointer_CourseTopics (Array)

שלוש הקבוצות — הנקודה שבה רוב האינטגרציות נשברות

Section titled “שלוש הקבוצות — הנקודה שבה רוב האינטגרציות נשברות”

לכל קבוצת שדות קידומת משלה, ולא מערבבים ביניהן. זו הטבלה של הדוגמה הזו, שדה־שדה:

השדה בבקשה הקבוצה לאן הוא הולך
table מפתח איתור — בלי קידומת שם המחלקה המדויק: "CourseRegistrations"
phone מפתח איתור — בלי קידומת מזין את זיהוי הכפילויות · באיש קשר חדש → Accounts.PhoneNumber
email מפתח איתור — בלי קידומת מזין את הזיהוי · באיש קשר חדש → Accounts.Email
idnum מפתח איתור — בלי קידומת ת.ז. / ח.פ. · באיש קשר חדש → Accounts.CompanyId
account_F_name · account_L_name · account_City שדות Accounts מוחלים רק כשנוצר איש קשר חדש
table_CourseId שדה טבלת היעד Pointer — מחרוזת objectId חשופה בת 10 תווים. אורך אחר או אובייקט Pointer עטוף → השדה נזרק בשקט (200)
table_StartDate שדה טבלת היעד Date — מחרוזת ISO שטוחה ("2026-09-01" או ISO מלא; גם אובייקט Date עטוף מתקבל)
table_Comment שדה טבלת היעד String
table_IsPaid שדה טבלת היעד Boolean — מחרוזת "true" / "false" או בוליאני אמיתי; שניהם נשמרים כ‑Boolean
table_array_topics_Pointer_CourseTopics שדה טבלת היעד מערך — נשמר בדיוק כפי שנשלח (מזהים חשופים או אובייקטי Pointer), בלי המרה ובלי בדיקת אורך
PageName · isExistsAccountCheck תוספות ברמה העליונה ראו מה השרת ממלא לבד

שני המיפויים שנבנים פעם אחת

Section titled “שני המיפויים שנבנים פעם אחת”

הטופס שולח טקסט (“קורס יסודות CRM”); ה‑API רוצה objectId. לכן שולפים את טבלאות ה‑lookup פעם אחת ומטמיעים מפה סטטית.

Bash
# שליפת רשימת הקורסים — פעם אחת, בזמן בניית הדף
curl -sS -G "https://api.mbapps.co.il/parse/classes/Courses" \
-H "X-Parse-Application-Id: $MB_APP_ID" \
-H "X-Parse-API-Key: $MB_API_KEY" \
--data-urlencode 'keys=objectId,Name' \
--data-urlencode 'order=Name' \
--data-urlencode 'limit=200'
JavaScript
// שליפת רשימת הקורסים — פעם אחת, בזמן בניית הדף
const params = new URLSearchParams({
keys: 'objectId,Name',
order: 'Name',
limit: '200',
});
const res = await fetch(`https://api.mbapps.co.il/parse/classes/Courses?${params}`, {
headers: {
'X-Parse-Application-Id': process.env.MB_APP_ID,
'X-Parse-API-Key': process.env.MB_API_KEY,
},
});
const { results } = await res.json();
Python
# שליפת רשימת הקורסים — פעם אחת, בזמן בניית הדף
import os
import requests
res = requests.get(
"https://api.mbapps.co.il/parse/classes/Courses",
headers={
"X-Parse-Application-Id": os.environ["MB_APP_ID"],
"X-Parse-API-Key": os.environ["MB_API_KEY"],
},
params={
"keys": "objectId,Name",
"order": "Name",
"limit": 200,
},
)
results = res.json()["results"]
PHP
<?php
// שליפת רשימת הקורסים — פעם אחת, בזמן בניית הדף
$query = http_build_query([
'keys' => 'objectId,Name',
'order' => 'Name',
'limit' => 200,
]);
$ch = curl_init("https://api.mbapps.co.il/parse/classes/Courses?$query");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-Parse-Application-Id: ' . getenv('MB_APP_ID'),
'X-Parse-API-Key: ' . getenv('MB_API_KEY'),
],
]);
$response = curl_exec($ch);
curl_close($ch);
$results = json_decode($response, true)['results'] ?? [];
JSON
{ "results": [
{ "objectId": "aB3dEf9HiJ", "Name": "קורס יסודות CRM" },
{ "objectId": "kLm4NoP7Qr", "Name": "סדנת אוטומציות" }
] }

ומכאן המפה שנכנסת לקוד:

JavaScript
// נשלף פעם אחת מטבלת Courses — עדכנו כשמוסיפים קורס
const COURSE_MAP = {
'קורס יסודות CRM': 'aB3dEf9HiJ',
'סדנת אוטומציות': 'kLm4NoP7Qr',
};
// נשלף פעם אחת מטבלת CourseTopics
const TOPIC_MAP = {
'אוטומציות': 'uNbbHuMGlK',
'דוחות': 'j17RjXmHdN',
'אינטגרציות': 'GGZUYqBjqT',
};
HTML
<form id="enroll-form" novalidate>
<input name="firstName" placeholder="שם פרטי" />
<input name="lastName" placeholder="שם משפחה" />
<input name="phone" type="tel" placeholder="טלפון" required />
<input name="email" type="email" placeholder="אימייל" />
<input name="idNumber" placeholder="ת.ז." />
<input name="city" placeholder="עיר" />
<select name="course" required>
<option value="">בחרו קורס</option>
<option>קורס יסודות CRM</option>
<option>סדנת אוטומציות</option>
</select>
<input name="startDate" type="date" />
<fieldset>
<legend>נושאים שמעניינים אתכם</legend>
<label><input type="checkbox" name="topics" value="אוטומציות" /> אוטומציות</label>
<label><input type="checkbox" name="topics" value="דוחות" /> דוחות</label>
<label><input type="checkbox" name="topics" value="אינטגרציות" /> אינטגרציות</label>
</fieldset>
<textarea name="notes" placeholder="הערות"></textarea>
<!-- מלכודת דבש: בוט ימלא, אדם לא יראה -->
<input name="company_website" tabindex="-1" autocomplete="off"
style="position:absolute;left:-9999px" aria-hidden="true" />
<button type="submit">הרשמה</button>
<p class="form-msg" role="status" aria-live="polite"></p>
</form>

<input type="date"> מחזיר "2026-09-01" — בדיוק הפורמט ששכבת הטפסים רוצה: ISO שטוח, 10 תווים ומעלה (נשמר כ‑2026-09-01T00:00:00.000Z). אין צורך להמיר לאובייקט Parse — אם כבר המרתם, גם {"__type":"Date","iso":…} מתקבל.

PHP
<?php
declare(strict_types=1);
const APP_ID = 'הדביקו-כאן';
const ENDPOINT = 'https://api.mbapps.co.il/functions/' . APP_ID . '/web2table';
const COURSE_MAP = [
'קורס יסודות CRM' => 'aB3dEf9HiJ',
'סדנת אוטומציות' => 'kLm4NoP7Qr',
];
const TOPIC_MAP = [
'אוטומציות' => 'uNbbHuMGlK',
'דוחות' => 'j17RjXmHdN',
'אינטגרציות' => 'GGZUYqBjqT',
];
/**
* @param array<string,mixed> $form
* @return array{ok:bool, accountId?:string, rowId?:string, error?:string}
*/
function submitEnrollment(array $form): array
{
// בחירה מרובה → מערך של objectId חשופים. ערך שאינו במפה — מושמט.
$topicIds = array_values(array_filter(
array_map(static fn ($t) => TOPIC_MAP[$t] ?? null, $form['topics'] ?? [])
));
$data = array_filter([
// מפתחות איתור — בלי קידומת
'table' => 'CourseRegistrations',
'phone' => $form['phone'] ?? null, // חובה
'email' => $form['email'] ?? null,
'idnum' => $form['idNumber'] ?? null, // → Accounts.CompanyId על איש קשר חדש
// שדות איש הקשר — רק אם הוא נוצר עכשיו
'account_F_name' => $form['firstName'] ?? null,
'account_L_name' => $form['lastName'] ?? null,
'account_City' => $form['city'] ?? null,
// שדות טבלת היעד
'table_CourseId' => COURSE_MAP[$form['course'] ?? ''] ?? null, // objectId חשוף
'table_StartDate' => $form['startDate'] ?? null, // ISO שטוח
'table_Comment' => $form['notes'] ?? null,
'table_IsPaid' => 'false', // בוליאני כמחרוזת
// תוספות ברמה העליונה
'PageName' => 'course-landing-2026',
'isExistsAccountCheck' => 'true',
], static fn ($v) => $v !== null && $v !== '');
if (!empty($topicIds)) {
$data['table_array_topics_Pointer_CourseTopics'] = $topicIds;
}
if (empty($data['phone'])) {
return ['ok' => false, 'error' => 'נא למלא טלפון'];
}
$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);
curl_close($ch);
if ($body === false) return ['ok' => false, 'error' => 'שגיאת רשת'];
$json = json_decode($body, true) ?: [];
if ($status === 400) { // כל שגיאה עסקית חוזרת כ-400 — מסתעפים על הגוף
$err = $json['error'] ?? $json['message'] ?? '';
if (str_contains($err, 'Disabled')) return ['ok' => false, 'error' => 'AcceptWebToTable כבוי במערכת'];
if (str_contains($err, 'missing')) return ['ok' => false, 'error' => 'חסר טלפון או שם טבלה'];
return ['ok' => false, 'error' => 'נתונים לא תקינים', 'raw' => $json]; // למשל code 111 — טיפוס שגוי
}
if ($status !== 200) return ['ok' => false, 'error' => "שגיאת שרת ($status)"];
return empty($json['success'])
? ['ok' => false, 'error' => 'תשובה לא צפויה מהשרת']
: ['ok' => true, 'accountId' => $json['accountId'], 'rowId' => $json['Id']];
}
JSON
{ "success": true, "accountId": "xK9mP2qRsT", "Id": "Cpl9lrueY5" }
השדה מה הוא
accountId ה‑objectId של איש הקשר — שנמצא או שנוצר
Id ה‑objectId של שורת ההרשמה החדשה ב‑CourseRegistrations

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

סטטוס מה זה אומר
400 {"error":"missing \"phone\""} · {"error":"missing \"table\" in request body"} · {"error":"AcceptWebToTable is Disabled"}דגל נפרד מזה של getlead, ראו דגלי Config · {"code":111,"message":"schema mismatch …"} (טיפוס שגוי בשדה account_*) · {"code":100,…"unescaped characters"} (שם טבלה בעברית/עם רווח — table נכנס לנתיב) · {"code":103,"message":"Class X does not exist."}
500 תקלת שרת

כל השגיאות העסקיות הן 400 — אין 422 ואין 403. הסתעפו על גוף התשובה.

אל תשלחו את השדות האלה — הם נקבעים בצד השרת:

השדה בשורת ההרשמה הערך
AccountId Pointer לאיש הקשר שנמצא או נוצר
IsFirst true לאיש קשר חדש; false רק כשאיש הקשר היה קיים וגם נשלח isExistsAccountCheck: true
Name ה‑Name מהבקשה; אחרת "פניה ראשונה" / "פניה חוזרת"
CampaignName / PageName מהבקשה; אחרת "MainLandingPage"
PhoneNumber, Email מועתקים מאיש הקשר
AccountNotExist true תמיד — גם לנרשם חוזר שאותר (מגבלה ידועה). אין לו ערך אבחוני; ההבחנה חדש/חוזר היא IsFirst בלבד. ראו web2table
מה קורה למה מה עושים
נרשם חוזר — הפרטים שלו ב‑CRM לא התעדכנו שדות account_* מוחלים רק על איש קשר חדש זו התנהגות מכוונת. עדכון של איש קשר קיים דורש REST או פונקציית שרת
שדה Pointer (table_*) נשאר ריק, והתשובה 200 הערך לא היה objectId בן בדיוק 10 תווים — או שנשלח כאובייקט Parse עטוף — שתי הצורות נזרקות. מזהה בן 10 תווים שאינו קיים כן נשמר, כמצביע תלוי באוויר הדפיסו את הגוף לפני השליחה ובדקו אורך. ראו אבחון
שדה שנשלח פשוט לא נשמר מפתח שאינו קיים בסכימה נזרק בשקט (table_NoSuchField → 200, ולא נוצר שדה) שלפו סכימה ואמתו כל שדה
שתי הרשמות זהות כל קריאה מוסיפה שורה — אין סמנטיקת עדכון נעילת כפתור + הפניה אחרי הצלחה. אל תנסו שוב אחרי timeout
table_IsPaid לא נקלט מספר (1/0) בשדה בוליאני נדחה ב‑400 code 111. true ו‑"true" שניהם נשמרים בוליאני או מחרוזת "true" / "false"
נושאי העניין נשמרו בצורה לא צפויה המערך נשמר בדיוק כפי שנשלח — מזהים חשופים נשארים מחרוזות, אובייקטי Pointer נשארים אובייקטים, ומזהה באורך שגוי נשמר כמו שהוא. הממשק של המוצר שומר בשדות כאלה אובייקטי Pointer מלאים בחרו צורה אחת ועמדו בה; קוד שקורא את השדה צריך לטפל בשתי הצורות
הרשמה עם קובץ מצורף לא נתמך — ושדה File בגוף אינו נזרק בשקט אלא מפיל את הבקשה ב‑400, ובצד השרת עלולות להיווצר עד 5 שורות כפולות העלאה נפרדת — קבצים
הכול עבד, ואז 400 AcceptWebToTable is Disabled AcceptWebToTable כבוי, או שתי שורות Config לאותו דגל דגלי Config

אחרי הפריסה — מה לבדוק במערכת

Section titled “אחרי הפריסה — מה לבדוק במערכת”
  • נוצרה שורה ב‑CourseRegistrations, וה‑AccountId שלה מצביע לאיש הקשר הנכון
  • CourseId אינו ריק — זו הבדיקה שתופסת 90% מטעויות ה‑Pointer
  • StartDate נראה כתאריך במערכת ולא כטקסט
  • שדה הנושאים מכיל בשליפה את כל המזהים שנשלחו, בצורה שבחרתם (מחרוזות או אובייקטי Pointer)
  • הרשמה של מספר טלפון שכבר קיים מקשרת לאיש הקשר הקיים ולא יוצרת כפילות (בחמישה פורמטי קלט: 05XXXXXXXX, 9725XXXXXXX, +9725XXXXXXX, 05X-XXXXXXX, +972-5X-XXXXXXX. הפורמט 05X-XXX-XXXX — למשל 051-070-5161אינו מאותר, לא כקלט ולא כפורמט שבו המספר כבר שמור אצל הלקוח, ומספר כזה ייצור כפילות שקטה)
  • IsFirst נכון בשני המקרים (חדש / חוזר) — כלומר isExistsAccountCheck נשלח
  • table הוא שם המחלקה המדויק, לא התווית בעברית
  • ה‑CLP של טבלת ההרשמות נשאר סגור