Skip to Content

Webhooks (קבלה)

כשמתרחשים אירועים בפרויקט SupDesk שלכם, webhooks יוצאים מוסרים בקשות HTTP POST לנקודות הקצה שהגדרתם. העמוד הזה מתעד את מבנה המטען, את מנגנון החתימה ואת התנהגות החזרה על ניסיונות, כדי שתוכלו לבנות מקבלי webhook אמינים.

מבנה המטען

כל מסירת webhook משתמשת במעטפת סטנדרטית:

{ "event": "post.created", "timestamp": "2026-07-15T12:00:00.000Z", "project_id": "550e8400-e29b-41d4-a716-446655440000", "data": { ... } }
שדהסוגתיאור
eventstringהאירוע שהפעיל את המסירה
timestampstringחותמת זמן ISO 8601 של מועד האירוע
project_idstringה-UUID של הפרויקט שבו התרחש האירוע
dataobjectמטען ייחודי לאירוע (ראו למטה)

אירועים

post.created

נשלח פוסט משוב חדש.

{ "event": "post.created", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "Add dark mode", "type": "feature", "status": "open", "author_type": "end_user", "body": "It would be great to have a dark mode option.", "created_at": "2026-07-15T12:00:00Z" } }

post.updated

פוסט משוב נערך.

{ "event": "post.updated", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "Add dark mode (updated)", "type": "feature", "status": "open", "author_type": "end_user", "body": "Updated description.", "created_at": "2026-07-15T12:00:00Z" } }

post.deleted

פוסט משוב נמחק.

{ "event": "post.deleted", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "Add dark mode", "type": "feature", "status": "open", "author_type": "end_user", "body": "It would be great to have a dark mode option.", "created_at": "2026-07-15T12:00:00Z" } }

post.status_changed

הסטטוס של פוסט השתנה.

{ "event": "post.status_changed", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "Add dark mode", "type": "feature", "old_status": "open", "new_status": "planned", "author_type": "member", "body": "We're planning this for Q3.", "created_at": "2026-07-15T12:00:00Z" } }

comment.created

נוספה תגובה חדשה לפוסט.

{ "event": "comment.created", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "post_id": "550e8400-e29b-41d4-a716-446655440001", "post_title": "Add dark mode", "author_type": "end_user", "body": "Any update on this?", "created_at": "2026-07-15T12:00:00Z" } }

comment.updated

תגובה נערכה.

{ "event": "comment.updated", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "post_id": "550e8400-e29b-41d4-a716-446655440001", "post_title": "Add dark mode", "author_type": "end_user", "body": "Updated comment.", "created_at": "2026-07-15T12:00:00Z" } }

comment.deleted

תגובה נמחקה.

{ "event": "comment.deleted", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "post_id": "550e8400-e29b-41d4-a716-446655440001", "post_title": "Add dark mode", "author_type": "end_user", "body": "Any update on this?", "created_at": "2026-07-15T12:00:00Z" } }

message.created

נשלחה הודעה פרטית חדשה.

{ "event": "message.created", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "thread_id": "550e8400-e29b-41d4-a716-446655440001", "thread_subject": "Help needed", "sender": "end_user", "body": "I can't find the settings page.", "via": "web", "created_at": "2026-07-15T12:00:00Z" } }

message.updated

הודעה פרטית נערכה.

{ "event": "message.updated", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "thread_id": "550e8400-e29b-41d4-a716-446655440001", "thread_subject": "Help needed", "sender": "end_user", "body": "Updated message.", "via": "web", "created_at": "2026-07-15T12:00:00Z" } }

message.deleted

הודעה פרטית נמחקה.

{ "event": "message.deleted", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "thread_id": "550e8400-e29b-41d4-a716-446655440001", "thread_subject": "Help needed", "sender": "end_user", "body": "I can't find the settings page.", "via": "web", "created_at": "2026-07-15T12:00:00Z" } }

beta_feedback.created

בודק בטא שלח משוב על תוכנית בטא.

{ "event": "beta_feedback.created", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "program": "Acme 2.0 Beta", "category": "bug", "severity": "high", "title": "Crash on export", "body": "The app closes when I export a large file.", "created_at": "2026-07-15T12:00:00Z" } }

severity עשוי להיות null.

beta_feedback.deleted

פריט משוב בטא נמחק.

{ "event": "beta_feedback.deleted", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "program": "Acme 2.0 Beta", "category": "bug", "severity": "high", "title": "Crash on export", "body": "The app closes when I export a large file.", "created_at": "2026-07-15T12:00:00Z" } }

waitlist_signup.created

מבקר הצטרף לרשימת ההמתנה.

{ "event": "waitlist_signup.created", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "jamie@example.com", "status": "waiting", "position": 42, "referral_count": 0, "referral_code": "a1b2c3", "source": "portal", "created_at": "2026-07-15T12:00:00Z", "invited_at": null, "joined_at": null } }

position הוא null עבור רשומות invited ו-joined.

waitlist_signup.invited

רשומה ברשימת ההמתנה הוזמנה מהרשימה.

{ "event": "waitlist_signup.invited", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "jamie@example.com", "status": "invited", "position": null, "referral_count": 0, "referral_code": "a1b2c3", "source": "portal", "created_at": "2026-07-15T12:00:00Z", "invited_at": "2026-07-15T12:00:00Z", "joined_at": null } }

waitlist_signup.joined

רשומה שהוזמנה אישרה והצטרפה.

{ "event": "waitlist_signup.joined", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "jamie@example.com", "status": "joined", "position": null, "referral_count": 0, "referral_code": "a1b2c3", "source": "portal", "created_at": "2026-07-15T12:00:00Z", "invited_at": "2026-07-15T12:00:00Z", "joined_at": "2026-07-15T12:05:00Z" } }

csat_survey.completed

סקר שביעות רצון הושלם עם דירוג.

{ "event": "csat_survey.completed", "data": { "thread_id": "550e8400-e29b-41d4-a716-446655440000", "rating": 5, "comment": "Support was fast and friendly.", "created_at": "2026-07-15T12:00:00Z" } }

rating הוא מספר שלם בין 1 ל-5; comment עשוי להיות null.

אימות חתימה

כל מסירה כוללת כותרת X-SupDesk-Signature:

X-SupDesk-Signature: sha256=a1b2c3d4e5f6...

הערך שאחרי sha256= הוא ה-HMAC-SHA256 בקידוד הקסדצימלי של גוף הבקשה הגולמי, שמחושב באמצעות סוד החתימה של ה-webhook שלכם.

שלבי האימות

  1. קראו את גוף הבקשה הגולמי (אל תפענחו JSON קודם)
  2. חשבו HMAC-SHA256(signing_secret, raw_body)
  3. השוו את התקציר ההקסדצימלי לערך בכותרת החתימה
  4. השתמשו בהשוואה בזמן קבוע כדי למנוע התקפות תזמון

דוגמה ב-Node.js

import { createHmac, timingSafeEqual } from "crypto"; function verifySupDeskWebhook(secret, rawBody, signatureHeader) { const expected = createHmac("sha256", secret) .update(rawBody) .digest("hex"); const received = signatureHeader.replace("sha256=", ""); return timingSafeEqual(Buffer.from(expected), Buffer.from(received)); } // Express example app.post("/webhooks/supdesk", express.raw({ type: "application/json" }), (req, res) => { const sig = req.headers["x-supdesk-signature"]; if (!verifySupDeskWebhook(SECRET, req.body, sig)) { return res.status(401).json({ error: "Invalid signature" }); } const event = JSON.parse(req.body); // Process event... res.status(200).json({ ok: true }); });

דוגמה ב-Python

import hmac import hashlib from flask import Flask, request, jsonify app = Flask(__name__) WEBHOOK_SECRET = "your-signing-secret" def verify_signature(secret: str, body: bytes, header: str) -> bool: expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() received = header.removeprefix("sha256=") return hmac.compare_digest(expected, received) @app.route("/webhooks/supdesk", methods=["POST"]) def handle_webhook(): sig = request.headers.get("X-SupDesk-Signature", "") if not verify_signature(WEBHOOK_SECRET, request.data, sig): return jsonify(error="Invalid signature"), 401 event = request.get_json() # Process event... return jsonify(ok=True), 200

דוגמה ב-Ruby

require "openssl" require "sinatra" require "json" WEBHOOK_SECRET = "your-signing-secret" def verify_signature(secret, body, header) expected = OpenSSL::HMAC.hexdigest("SHA256", secret, body) received = header.sub("sha256=", "") Rack::Utils.secure_compare(expected, received) end post "/webhooks/supdesk" do body = request.body.read sig = request.env["HTTP_X_SUPDESK_SIGNATURE"] || "" unless verify_signature(WEBHOOK_SECRET, body, sig) halt 401, { error: "Invalid signature" }.to_json end event = JSON.parse(body) # Process event... status 200 { ok: true }.to_json end

חזרה על ניסיונות

כשמסירת webhook נכשלת, SupDesk חוזרת עם השהיה מעריכית:

ניסיוןהשהיה
1מיידי
2שנייה אחת
32 שניות

מצבים שמנוסים שוב: תגובות 5xx, שגיאות רשת, פסקי זמן (10 שניות).

לא מנוסים שוב: תגובות 4xx (שגיאות לקוח מעידות על בעיית תצורה).

מענה ל-webhooks

  • החזירו קוד סטטוס 2xx כדי לאשר קבלה
  • השיבו בתוך 10 שניות כדי להימנע מחזרות עקב פסק זמן
  • עבדו את האירוע באופן אסינכרוני אם הלוגיקה שלכם נמשכת יותר
  • החזירו את אותה תגובה למסירות כפולות (אידמפוטנטיות)
Last updated on