API v2 (בטא)
Webhooks
endpoint של Webhook הוא כתובת URL בשרת שלכם ש-WbizTool קוראת לה כשמשהו קורה להודעה: היא נשלחה, נכשלה, בוטלה או פגה. אתם מקבלים את העדכון מיד במקום לבצע polling, וכל בקשה חתומה, כדי שתוכלו לוודא שהיא הגיעה מ-WbizTool.
| פעולה | Endpoint | הרשאה |
|---|---|---|
| הוספת endpoint | POST /api/v2/webhooks | messages:read |
| רשימת endpoints | GET /api/v2/webhooks | messages:read |
| קבלת endpoint | GET /api/v2/webhooks/{id} | messages:read |
| הסרת endpoint | DELETE /api/v2/webhooks/{id} | messages:read |
| החלפת סוד החתימה | POST /api/v2/webhooks/{id}/rotate-secret | messages:read |
| שליחת אירוע בדיקה | POST /api/v2/webhooks/{id}/test | messages:read |
ה-endpoints שייכים לסביבת העבודה ומקבלים אירועים על כל ההודעות שלה, לא משנה מה שלח אותן: ה-API, לוח הבקרה, קמפיינים, תזכורות או אינטגרציות. לכן מפתח שמוגבל לחלק מהמספרים לא יכול לנהל אותם: כל קריאה מחזירה 403 number_not_allowed.
אירועים#
| סוג | מתי |
|---|---|
message.sent | הודעה יצאה מהמספר שלכם. |
message.failed | לא הצלחנו לשלוח הודעה; error.message מסביר למה. |
message.cancelled | הודעה ממתינה בוטלה: עם ה-API, מלוח הבקרה, או כי המספר או הקמפיין שלה הוסרו. |
message.expired | הודעה לא נשלחה לפני ה-expires_at שלה. |
ping | אירוע בדיקה שביקשתם. |
אירועים למספרים (התחבר, התנתק) ולהודעות נכנסות יגיעו בהמשך. וי של מסירה ושל קריאה לא שולחים אירועים; כדי לראות אותם, קבלו את ההודעה.
הוספת endpoint#
https://wbiztool.com/api/v2/webhooksגוף הבקשה: JSON (application/json)
גוף הבקשה
urlstringחובהה-endpoint שלכם: כתובת
https://ציבורית, עד 500 תווים. כתובותhttp://, פרטיות ומקומיות (כמוlocalhostאו192.168.x.x) נדחות.eventsarray of stringsאופציונליסוגי האירועים שהוא מקבל: כל אחד מ-
message.sent, message.failed, message.cancelledו-message.expired. ברירת המחדל: כל הארבעה. pingתמיד מגיע כשאתם מבקשים אותו.descriptionstringאופציונליהערה לעצמכם, עד 100 תווים, למשל
Order system.
בסביבת עבודה יכולים להיות עד 5 endpoints. התגובה היא 201 עם ה-endpoint וה-secret שלו לחתימה, שמוצג רק הפעם. שמרו אותו במקום שממנו המקבל שלכם יכול לקרוא אותו, למשל במשתנה סביבה. תגובות מאוחרות יותר מציגות רק את secret_last4; אם תאבדו את הסוד, החליפו אותו.
curl -X POST https://wbiztool.com/api/v2/webhooks \
-H "Authorization: Bearer $WBIZTOOL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/wbiztool/events",
"events": ["message.sent", "message.failed"],
"description": "Order system"
}'import os
import requests
response = requests.post(
"https://wbiztool.com/api/v2/webhooks",
headers={"Authorization": f"Bearer {os.environ['WBIZTOOL_API_KEY']}"},
json={
"url": "https://example.com/wbiztool/events",
"events": ["message.sent", "message.failed"],
"description": "Order system",
},
timeout=30,
)
endpoint = response.json()
print("Store this secret now:", endpoint["secret"])// Node.js 18+ (built-in fetch). Save as .mjs to use top-level await.
const response = await fetch("https://wbiztool.com/api/v2/webhooks", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.WBIZTOOL_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
url: "https://example.com/wbiztool/events",
events: ["message.sent", "message.failed"],
description: "Order system",
}),
});
const endpoint = await response.json();
console.log("Store this secret now:", endpoint.secret);<?php
$ch = curl_init('https://wbiztool.com/api/v2/webhooks');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('WBIZTOOL_API_KEY'), 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://example.com/wbiztool/events',
'events' => ['message.sent', 'message.failed'],
'description' => 'Order system',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$endpoint = json_decode(curl_exec($ch), true);
curl_close($ch);
echo 'Store this secret now: ' . $endpoint['secret'];תגובה (201):
{
"id": 41,
"url": "https://example.com/wbiztool/events",
"description": "Order system",
"events": ["message.sent", "message.failed"],
"secret_last4": "9c1e",
"active": true,
"disabled_reason": null,
"created_at": "2026-10-18T11:20:05.733Z",
"last_delivery": null,
"secret": "3f0b…9c1e"
}
ה-secret האמיתי הוא 64 תווים הקסדצימליים. ניסיון חוזר של הבקשה הזו עם אותו Idempotency-Key לא מוסיף endpoint שני, והתגובה שלו משמיטה את הסוד.
שגיאות בהוספת endpoint#
| HTTP | קוד | מתי |
|---|---|---|
400 | validation_failed | url חסר או ארוך מדי, events ריק או מכיל סוג לא מוכר, או ששדה לא מוכר |
403 | webhook_limit_reached | בסביבת העבודה כבר יש 5 endpoints |
403 | number_not_allowed | המפתח מוגבל לחלק מהמספרים |
422 | invalid_webhook_url | url אינו כתובת https:// ציבורית |
רשימת endpoints#
https://wbiztool.com/api/v2/webhooksה-endpoints של סביבת העבודה שמקבלים אירועים, מהישן לחדש, ברשימת data אחת.
curl https://wbiztool.com/api/v2/webhooks \
-H "Authorization: Bearer $WBIZTOOL_API_KEY"
הרשימה משמיטה endpoints שהפסיקו. בעלים רואים את ה-endpoints גם בדף הגדרות ← מפתחות API תחת נקודות קצה של Webhooks, ויכולים להסיר אותם שם.
קבלת endpoint#
https://wbiztool.com/api/v2/webhooks/{id}endpoint אחד. last_delivery מציג איך עברה המסירה האחרונה אליו. endpoint שלא קיים או שהוסר מחזיר 404 webhook_not_found; endpoint שהפסיק מוצג עם active: false.
הסרת endpoint#
https://wbiztool.com/api/v2/webhooks/{id}שום דבר לא נשלח אליו יותר, כולל ניסיונות חוזרים שעדיין ממתינים. התגובה היא {"id": 41, "deleted": true}.
החלפת סוד החתימה#
https://wbiztool.com/api/v2/webhooks/{id}/rotate-secretיוצר סוד חדש ל-endpoint. התגובה היא 200 עם ה-endpoint וה-secret החדש שלו, שמוצג רק הפעם. הסוד הישן מפסיק לעבוד מיד: אירועים שנשלחים מעכשיו חתומים בסוד החדש, כולל ניסיונות חוזרים של אירועים קודמים. עדכנו את המקבל שלכם מיד.
curl -X POST https://wbiztool.com/api/v2/webhooks/41/rotate-secret \
-H "Authorization: Bearer $WBIZTOOL_API_KEY"
שליחת אירוע בדיקה#
https://wbiztool.com/api/v2/webhooks/{id}/testמכניס לתור אירוע ping ל-endpoint הזה, חתום כמו כל אירוע, כדי שתוכלו לבדוק את המקבל שלכם. התגובה היא 202:
{ "event_id": "evt_8a2f4c1d9b7e6a5f4c3b2a19", "type": "ping", "queued": true }
ה-endpoint שלכם מקבל:
{
"id": "evt_8a2f4c1d9b7e6a5f4c3b2a19",
"type": "ping",
"created_at": "2026-10-18T11:21:40.002Z",
"data": { "webhook_id": 41 }
}
תוכן האירוע#
כל אירוע הוא בקשת POST עם גוף JSON:
{
"id": "evt_c41d7e2a9f3b8c6d5e4f3a21",
"type": "message.sent",
"created_at": "2026-10-18T10:15:09.912Z",
"data": {
"message": {
"id": 9817263,
"status": "sent",
"from": { "id": 101, "phone": "919800000000", "platform": "whatsapp" },
"to": { "phone": "+919876543210", "contact_id": 991 },
"text": "Hi Asha, your order #1042 is ready.",
"media": null,
"send_at": null,
"expires_at": null,
"created_at": "2026-10-18T10:15:02.311Z",
"sent_at": "2026-10-18T10:15:09.870Z",
"error": null
}
}
}
| שדה | תיאור |
|---|---|
id | המזהה של האירוע, evt_ ו-24 תווים. אותו אירוע שומר על המזהה שלו בכל ניסיון חוזר. |
type | אחד מסוגי האירועים. |
created_at | מתי האירוע קרה. |
data.message | באירועי message.*: ה-Message כפי שהיה אז. media.url שבו עובד לפחות 26 שעות. |
data.webhook_id | ב-ping: המזהה של ה-endpoint. |
כותרות:
| כותרת | ערך |
|---|---|
Content-Type | application/json |
User-Agent | WbizTool-Webhooks/2 |
WbizTool-Event-Id | ה-id של האירוע, זהה בכל ניסיון חוזר |
WbizTool-Timestamp | מתי נשלח הניסיון הזה, בשניות Unix, למשל 1792310409 |
WbizTool-Signature | v1= ואחריו ה-HMAC-SHA256 ההקסדצימלי של <timestamp>.<raw body>, שמחושב בסוד של ה-endpoint |
אימות החתימה#
בדקו כל בקשה לפני שאתם סומכים עליה: חשבו את החתימה מהגוף הגולמי (לפני שה-framework שלכם מפענח את ה-JSON), השוו אותה לכותרת בהשוואה בזמן קבוע, ודחו חותמות זמן שסוטות ביותר מ-5 דקות, כדי שאי אפשר יהיה לשלוח שוב בקשה ישנה. זו אותה שיטה כמו חתימות ה-Webhook של API v1, עם הסוד של ה-endpoint עצמו.
אחר כך השתמשו ב-WbizTool-Event-Id כדי לסנן כפילויות: אותו אירוע יכול להגיע יותר מפעם אחת, למשל כשהתגובה שלכם אבדה וניסינו שוב.
import hashlib, hmac, json, os, time
SECRET = os.environ["WBIZTOOL_WEBHOOK_SECRET"]
def verify(secret: str, body: bytes, headers) -> bool:
ts, sig = headers.get("WbizTool-Timestamp", ""), headers.get("WbizTool-Signature", "")
if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
return False
mac = "v1=" + hmac.new(secret.encode(), ts.encode() + b"." + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(mac, sig)
# Flask
from flask import Flask, request
app = Flask(__name__)
seen = set() # use your database in production
@app.post("/wbiztool/events")
def events():
body = request.get_data() # the raw bytes
if not verify(SECRET, body, request.headers):
return "", 401
event_id = request.headers["WbizTool-Event-Id"]
if event_id in seen:
return "", 200 # a repeat: already handled
seen.add(event_id)
event = json.loads(body)
if event["type"] == "message.failed":
message = event["data"]["message"]
print("Failed:", message["id"], message["error"]["message"])
return "", 200ב-Django העבירו את request.body ואת request.headers.
const crypto = require("crypto");
const express = require("express");
const SECRET = process.env.WBIZTOOL_WEBHOOK_SECRET;
function verify(secret, rawBody, headers) {
const ts = String(headers["wbiztool-timestamp"] || ""), sig = String(headers["wbiztool-signature"] || "");
if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const mac = "v1=" + crypto.createHmac("sha256", secret).update(ts + ".").update(rawBody).digest("hex");
return sig.length === mac.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(mac));
}
const app = express();
const seen = new Set(); // use your database in production
// express.raw keeps the body as a Buffer: the signature covers the exact bytes.
app.post("/wbiztool/events", express.raw({ type: "*/*" }), (req, res) => {
if (!verify(SECRET, req.body, req.headers)) return res.sendStatus(401);
const eventId = req.headers["wbiztool-event-id"];
if (seen.has(eventId)) return res.sendStatus(200); // a repeat: already handled
seen.add(eventId);
const event = JSON.parse(req.body);
if (event.type === "message.failed") {
const message = event.data.message;
console.log("Failed:", message.id, message.error.message);
}
res.sendStatus(200);
});
app.listen(3000);החזירו 2xx מהר, ובצעו עבודה איטית אחר כך (למשל בתור).
ניסיונות חוזרים#
- החזירו
2xxכלשהו תוך 10 שניות. כל דבר אחר הוא כישלון: קוד סטטוס אחר (כולל4xx), אין תגובה תוך 10 שניות, או אין חיבור תוך 3 שניות. המערכת עוקבת אחרי הפניות (redirects), עד 3, וכל אחת מהן חייבת להוביל לכתובת ציבורית. - מסירה שנכשלה מנוסה שוב אחרי 1, 5 ו-15 דקות, ואז אחרי 1, 2, 4, 8 ו-8 שעות: 9 ניסיונות במשך כ-23 שעות. אחרי האחרון, האירוע הזה נזנח.
- לניסיון החוזר יש אותו גוף ואותו
WbizTool-Event-Id, עםWbizTool-Timestampוחתימה חדשים. - הסדר לא מובטח.
message.sentשנשלח שוב יכול להגיע אחרי אירוע מאוחר יותר. כשהסדר חשוב, השתמשו ב-statusשל ההודעה, או קבלו את ההודעה.
last_delivery ב-endpoint מציג איך עברה המסירה האחרונה: state (pending, delivered או failed), ה-status_code שהשרת שלכם החזיר, ומתי האירוע נוצר.
מתי endpoints מפסיקים#
endpoint מפסיק לקבל אירועים כשקורה אחד מהדברים האלה. הוא נשאר ב-GET /webhooks עם active: false ו-disabled_reason, כדי שתוכלו לראות למה; עדיין אפשר לקרוא ולהסיר אותו, אבל לא לבדוק אותו או לתת לו סוד חדש (404).
- הוא נכשל במשך 24 שעות. אם במשך 24 שעות שום דבר לא נמסר אליו והמסירות אליו המשיכו להיכשל, הוא מושבת והניסיונות החוזרים הממתינים שלו נזנחים. בדף הגדרות ← מפתחות API הוא מוצג אז כמושבתת. תקנו את השרת שלכם, ואז הוסיפו את ה-endpoint שוב.
- התוקף של המפתח שיצר אותו בוטל או פג. ביטול תוקף של מפתח עוצר את כל מה שהוא הגדיר. הוסיפו את ה-endpoint שוב עם מפתח אחר.
- הוא הוסר, עם הסרת endpoint או בדף הגדרות ← מפתחות API.
endpoints שהפסיקו לא נספרים במסגרת ה-5.
האובייקט Webhook endpoint#
| שדה | סוג | תיאור |
|---|---|---|
id | integer | המזהה של ה-endpoint. |
url | string | לאן נשלחים האירועים. |
description | string | ההערה שלכם; "" כשאין. |
events | array | סוגי האירועים שהוא מקבל. |
secret | string | סוד החתימה: רק בתגובות להוספת endpoint ולהחלפת סוד החתימה. |
secret_last4 | string | 4 התווים האחרונים של הסוד, כדי לדעת איזה מהם יש לכם. |
active | boolean | false כשהוא כבר לא מקבל אירועים – ראו מתי endpoints מפסיקים. |
disabled_reason | string or null | למה הוא הפסיק (כישלונות במשך 24 שעות, או שהתוקף של המפתח שיצר אותו בוטל או פג); null כשהוא פעיל. |
created_at | string | מתי הוא נוסף. |
last_delivery | object or null | המסירה האחרונה שנוסתה: state, status_code (או null כשהשרת שלכם לא החזיר תגובה) ו-created_at. null לפני המסירה הראשונה. |
מדריכים קשורים#
- הודעות: האובייקט Message שב-
data.message - חתימות Webhook ל-API v1
- שגיאות
