Webhooks של Mosaqo
אנחנו שולחים POST עם גוף JSON חתום לנקודת הקצה שלכם בכל אירוע שנרשמתם אליו, ומנסים שוב עם השהיה גדלה עד שתקבלו אותו.
הרשמה
הוסיפו נקודת קצה במסך Bulk & API, או רשמו אותה מהקוד שלכם עם מפתח בעל webhooks:write — כך פלטפורמת אוטומציה מדליקה ומכבה טריגר:
curl -X POST https://api.mosaqo.app/v1/public-api/webhooks \
-H "Authorization: Bearer $MOSAQO_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/mosaqo",
"eventTypes": ["qr.published", "qr.updated"]
}'התשובה מכילה את סוד החתימה בשדה secret. הוא מוצג פעם אחת — שמרו אותו, או החליפו את המנוי מאוחר יותר.
אירועים
| אירוע | מופעל כאשר |
|---|---|
qr.created | קוד נוצר, באפליקציה או דרך ה-API |
qr.published | קוד עולה לאוויר וההפניה שלו מתחילה לעבוד |
qr.updated | תוכן, עיצוב, שם או סטטוס משתנים |
qr.archived | קוד מועבר לארכיון ויורד ממארח ההפניות |
qr.deleted | קוד נמחק לצמיתות |
redirect.rule_changed | יעד או כלל מיקוד משתנים |
scan.aggregate_ready | צבירת סריקות הושלמה |
export.ready | ייצוא אסינכרוני הסתיים |
approval.changed | בקשת אישור אושרה או נדחתה |
bulk.completed | משימת אצווה מסתיימת |
מטען
כשאירוע נוגע לקוד QR, השדה data.qr נושא את אותו סיכום שמחזירה הקריאה GET /qr/{id} — כך שטריגר אינו צריך לקרוא בחזרה כדי לקבל את הרשומה שעליה בדיוק דיווחו לו.
{
"id": "0b0f…",
"type": "qr.published",
"workspaceId": "2f60…",
"occurredAt": "2026-08-05T10:20:30.456Z",
"data": {
"qrId": "e7b7…",
"source": "public_api",
"qr": {
"id": "e7b7…",
"name": "Autumn campaign",
"slug": "autumn-campaign-1a2b3c4d",
"mode": "dynamic",
"contentType": "url",
"status": "published",
"approvalState": "not_required",
"folderId": null,
"updatedAt": "2026-08-05T10:20:30.401Z",
"publicUrl": "https://mosaqo.link/aB3dEf"
}
}
}אימות מסירה
כל בקשה נושאת חותמת זמן וחתימה על חותמת הזמן והגוף גם יחד:
X-Mosaqo-Timestamp: 1785942718
X-Mosaqo-Signature: t=1785942718,v1=<hex HMAC-SHA256 of "{timestamp}.{raw body}">
X-Mosaqo-Delivery: 0b0f…
X-Mosaqo-Event: qr.publishedדחייה של כל מה שחותמת הזמן שלו רחוקה מהשעון שלכם היא מה שהופך מסירה שנלכדה לבלתי ניתנת לשחזור, אז אל תדלגו על הבדיקה הזו. השוו חתימות בזמן קבוע, והשתמשו בגוף הבקשה הגולמי — סריאליזציה מחדש של ה-JSON המפוענח משנה את הבתים והחתימה לא תתאים.
Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyMosaqoWebhook(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(
header.split(',').map((piece) => piece.split('=').map((s) => s.trim())),
);
const timestamp = Number(parts.t);
if (!Number.isFinite(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
const expected = createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest();
const received = Buffer.from(parts.v1 ?? '', 'hex');
return expected.length === received.length && timingSafeEqual(expected, received);
}PHP
<?php
function verify_mosaqo_webhook(string $rawBody, string $header, string $secret, int $tolerance = 300): bool {
$parts = [];
foreach (explode(',', $header) as $piece) {
[$key, $value] = array_map('trim', explode('=', $piece, 2));
$parts[$key] = $value;
}
if (!isset($parts['t'], $parts['v1'])) return false;
if (abs(time() - (int) $parts['t']) > $tolerance) return false;
$expected = hash_hmac('sha256', $parts['t'] . '.' . $rawBody, $secret);
return hash_equals($expected, $parts['v1']);
}Python
import hmac, time
from hashlib import sha256
def verify_mosaqo_webhook(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(piece.strip().split("=", 1) for piece in header.split(","))
if "t" not in parts or "v1" not in parts:
return False
if abs(time.time() - int(parts["t"])) > tolerance:
return False
signed = f"{parts['t']}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])ניסיונות חוזרים וכשלים
החזירו כל 2xx ברגע שאחסנתם את האירוע; את העבודה בצעו אחר כך. אנחנו מנסים עד שש פעמים עם השהיה מעריכית, כך שמטפל אטי הופך למסירות כפולות — התייחסו לכותרת X-Mosaqo-Delivery כאל מפתח אידמפוטנטיות אצלכם.
נקודות קצה חייבות להיות HTTPS על מארח ציבורי. כתובות מקומיות ופרטיות נדחות כבר ביצירת המנוי.
חתימה מיושנת
הכותרת X-Mosaqo-Signature-V0 נושאת חתימה ישנה יותר המחושבת על הגוף בלבד. היא אינה יכולה לבטא הגנה משחזור, נשלחת לגרסה אחת כדי שמקבלים קיימים יספיקו לעבור, ואין להשתמש בה לשום דבר חדש.
נתוני סריקה אינם נדחפים
במכוון אין webhook לכל סריקה. Mosaqo אינה שולחת אנליטיקה ברמת הסריקה לנקודות קצה של צד שלישי, ואף אירוע בטבלה שלמעלה אינו נושא אותה.
היסטוריית הסריקות זמינה רק אם תמשכו אותה בעצמכם עם המפתח שלכם — GET /scans — וגם אז היא כפופה להגדרות פרטיות האנליטיקה של סביבת העבודה: ממדים שכיביתם מושמטים מכל שורה, מסנן הבוטים פועל, וגיבוב המבקר הפסאודונימי שמאחורי "סריקות ייחודיות" לעולם אינו נחשף. התשובה מפרטת את allowedDimensions כדי שתדעו מה אתם מקבלים.
האירוע scan.aggregate_ready מודיע שצבירה מוכנה; הוא אינו מכיל את הסריקות עצמן. אם האינטגרציה שלכם באמת זקוקה לסריקות בודדות בזמן אמת, זה זמין לפי בקשה — דברו איתנו על כך במקום להניח שזה קיים.
אם אינכם יכולים לקבל webhooks
תשאלו במקום זאת. הקריאה GET /qr?updatedSince=… מחזירה קודים שהשתנו, והקריאה GET /scans?since=… מתקדמת בהיסטוריית הסריקות עם סמן יציב.