כל פלטפורמה ששולחת Webhook — מדביקים דוגמה, ממפים בלחיצות, וכל תשלום הופך למסמך.
מתאים לכל מערכת שאין לה חיבור מובנה: Magento, PrestaShop, OpenCart, Konimbo, Typeform, Jotform, Tally, Google Forms (דרך Apps Script), Bubble, Softr, Zapier ("Webhooks by Zapier") או האתר שלכם.
חיבור כל פלטפורמה ב-10 דקות#
- יצירת חיבור. חנויות ואינטגרציות → חיבור מותאם אישית → "חיבור חדש". תנו שם ובחרו אימות (ההמלצה: חתימת HMAC, אם הפלטפורמה תומכת). הסוד מוצג פעם אחת — העתיקו אותו.
- הכתובת. במסך החיבור מעתיקים את כתובת ה-Webhook ומדביקים אותה בפלטפורמה (שיטה POST). אם בחרתם טוקן או חתימה — מגדירים שם את הסוד ואת שם הכותרת (header).
- דוגמה. מדביקים בקשה אמיתית שהפלטפורמה שולחת (JSON או מחרוזת טופס). רוב הפלטפורמות מציגות דוגמה במסך ה-Webhook; אפשר גם לשלוח הזמנת בדיקה לאתר כמו webhook.site ולהעתיק משם.
- מיפוי. לוחצים "הצעת מיפוי אוטומטית" — או בוחרים "מתכון מוכן". עוברים על השדות המסומנים ב"כדאי לבדוק". מזהה ההזמנה חובה, והוא מונע מסמך כפול כשהפלטפורמה שולחת שוב.
- תצוגה מקדימה. בצד המסך רואים את המסמך שייצא: לקוח, שורות, מע"מ וסה"כ. אם הסכום ששולם שונה מסכום המסמך מופיעה אזהרה — מתקנים לפני שממשיכים.
- "להפיק רק כאשר". אם הפלטפורמה שולחת גם הזמנות שעוד לא שולמו, מגדירים שדה וערך
(למשל
status=paid). כל השאר מתקבל ולא מופק. - שמירה ובדיקה. "שמירה", ואז "שליחת בדיקה" — הדוגמה נשלחת חתומה דרך הכתובת האמיתית ומפיקה מסמך אמיתי. התוצאה מופיעה ביומן ההזמנות.
Developer reference#
Receiver#
POST /api/billing/ecommerce/custom/<hookId> — one hookId per connector (a tenant may have many).
Body: JSON, application/x-www-form-urlencoded, or multipart/form-data text fields (Jotform).
Limits, body cap (512 KB) and per-IP rate limits are the generic receiver's
(src/ecommerce/connectors/inbound.ts). Answers: 401 bad/missing auth · 404 unknown or deleted
hookId · 200 {status:"issued", documentId} · 200 {skipped} for an event the "paid when" filter
ignores, a paused connector, or a mapping failure (recorded as an error row in the orders log with
the Hebrew reasons — a platform retry cannot fix a mapping).
Authentication (per connector)#
| Kind | What the platform sends |
|---|---|
hmac-hex | <header>: hex(HMAC-SHA256(secret, rawBody)) (a sha256= prefix is accepted) |
hmac-base64 | same, base64 — Typeform (Typeform-Signature: sha256=…), Tally (Tally-Signature) |
timestamped | <header>: t=<unix>,v1=hex(HMAC-SHA256(secret, "<t>.<rawBody>")), 5-minute tolerance |
token | <header>: <secret> (or Bearer <secret>), or ?token=<secret> on the URL |
hookid | nothing — the URL is the credential; per-hook rate limit (60/h) |
The body-format setting is enforced too: a json connector refuses a form body and vice versa (401).
Mapping DSL#
One expression per document field:
expr := source ( "|" transform )*
source := alt ( "," alt )* several = first non-empty, or joined by | concat
alt := "literal" | 123 | true | false | now | path
path := key(.key)* with [n] [-1] [] [key=value] ["key with.dots"]customer.email,items[0].name,items[-1].price— dot paths and indexes.items[]— "for each element" — only in רשימת הפריטים (lines.each). Line fields are relative to the element (name,price), and theitems[].prefix is also accepted (items[].name).$.pathinside a line field reads from the payload root.answers[field.ref=email].email— the first element whosefield.refequalsemail(Typeform, Tally). Quote values with spaces:fields[label="Payment (price)"].value.- A JSON string met while walking is parsed (
rawRequest.q3_name.firston Jotform). - A flat form key containing dots/brackets (
billing[first_name]) is read verbatim. "תשלום באתר"— a literal;1800,true— literal number / boolean;now— today.
| Transform | Effect |
|---|---|
shekels | decimal major units → integer agorot ("1,234.50" → 123450). Default for money fields with no unit. |
cents | already integer minor units (Stripe-style) — refuses a fraction |
number | parse a number ("₪ 12,5" → 12.5) |
percent | 18 → 1800 basis points |
rate | 0.18 → 1800 basis points |
trim / lower / upper | string cleanup |
default:"…" | value when empty |
concat:" " | join the comma-separated sources, skipping empty ones |
Fields: externalId (required, [A-Za-z0-9._:@-]{1,100}), customerName (default לקוח/ה),
customerEmail, customerVatId, customerAddress, subject (default <connector name> #<id>),
currency (default ILS), taxIncluded (default true), vatBp (default the statutory rate),
issueDate (ISO, DD/MM/YYYY, unix seconds/ms or now; default today), lines —
{mode:"items", each, name, quantity (default 1), unitPrice, vatBp?, description?} or
{mode:"single", name, amount} — shipping (adds a "משלוח" line), discount (fixed, document level),
total (the platform's paid total), paymentType (literal 1..22 or a path + paymentTypeMap
{"paypal":10,"cash":4}; unknown values fall back to 1 with a warning), paymentDate.
Money is never fudged: the payment row is the mapped total; if it differs from the document
computed by the same engine that issues it (computeTotals), the preview warns and a payment
document (receipt / invoice-receipt) is refused rather than issued with a wrong amount.
Stored ids: external_id = "<slug>:<order id>", apiIdentifier = "custom-<slug>-<order id>" — two
connectors in one business never collide, and a redelivery returns the first document.
Examples#
Nested order (Magento/PrestaShop module, custom site)
{"order_id":"A-10045","status":"paid","customer":{"first_name":"נועה","last_name":"לוי","email":"[email protected]"},
"items":[{"name":"כוס קרמיקה","quantity":2,"price":"59.90"}],"shipping":"25.00","total":"144.80"}| Field | Expression |
|---|---|
| externalId | order_id |
| customerName | customer.first_name,customer.last_name | concat:" " |
| lines.each / name / quantity / unitPrice | items[] / name / quantity / price | shekels |
| shipping / total | shipping | shekels / total | shekels |
| paid when | status = paid |
Flat gateway form: transaction_id=TX-1&sum=250.00&full_name=יוסי+כהן&email=y%40example.com
| Field | Expression |
|---|---|
| externalId | transaction_id |
| customerName | full_name | default:"לקוח/ה" |
| lines (single) | name "תשלום באתר", amount sum | shekels |
Typeform with a payment question
| Field | Expression |
|---|---|
| externalId | form_response.token |
| customerEmail | form_response.answers[type=email].email |
| lines (single) | name "תשלום בטופס", amount form_response.answers[type=payment].payment.amount | shekels |
| paid when | form_response.answers[type=payment].payment.success = true |
Test with curl (hmac-hex)#
URL='https://invoice.slk.co.il/api/billing/ecommerce/custom/<hookId>'
SECRET='<the connector secret>'
BODY='{"order_id":"T-1","status":"paid","items":[{"name":"בדיקה","quantity":1,"price":"10.00"}],"total":"10.00"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl -sS -X POST "$URL" -H 'content-type: application/json' -H "x-signature: $SIG" --data "$BODY"Token auth: curl -X POST "$URL?token=$SECRET" -H 'content-type: application/json' --data "$BODY".
Troubleshooting#
- 401 — הסוד, שם הכותרת או פורמט הנתונים לא תואמים. חתימת HMAC מחושבת על הגוף המדויק שנשלח — פלטפורמה שמשנה רווחים או סדר שדות אחרי החתימה לא תעבור.
- 404 — הכתובת שגויה, או שהחיבור נמחק.
- שגיאה ביומן ההזמנות — המיפוי לא הצליח; הסיבה כתובה בעברית ליד ההזמנה. מתקנים את המיפוי, שומרים, והפלטפורמה או "שליחת בדיקה" שולחות שוב.
- "payments … must equal total" — הסכום ששולם שונה מסכום המסמך. בודקים את "המחירים כוללים מע"מ", משלוח והנחה בתצוגה המקדימה.
- חסום / כבוי — המסלול לא כולל חנויות, או שמודול החיוב כבוי. אחרי שדרוג מפיקים מחדש מיומן ההזמנות.