وبهوکهای پشتیبانگیری
رویدادهای پشتیبانگیری موفق، ناموفق و جامانده، بازگردانی و عامل؛ با همان پاکت و امضای HMAC وبهوکهای نمایک.
در این صفحه
رویدادها
نشانی وبهوک را در پنل یا با API وبهوک بسازید و رویدادهای پشتیبانگیری را انتخاب کنید. کلیدهای data فقط همینهایند؛ بایتها رشتهاند.
| نوع | نوع / مقدار | توضیح |
|---|---|---|
backup.succeeded | پشتیبانگیری موفق | sourceId، sourceName، snapshotId، jobId، engine، trigger، startedAt، completedAt، durationMs، fileBytes، rawBytes، verifyState |
backup.failed | پشتیبانگیری ناموفق | sourceId، sourceName، jobId، engine، trigger، attempt، willRetry، consecutiveFailures، errorCode، errorMessage، failedAt |
backup.missed | پشتیبانگیری جامانده | sourceId، sourceName، jobId، scheduledFor، reason (agent_offline | not_started)، consecutiveMissed |
backup.verification_failed | رد شدن بررسی یکپارچگی | sourceId، snapshotId، level، errorCode، detectedAt |
restore.completed | پایان بازگردانی | restoreId، sourceId، snapshotId، mode، target، durationMs، completedAt |
restore.failed | خطای بازگردانی | restoreId، sourceId، snapshotId، mode، target، errorCode، errorMessage، failedAt |
agent.offline | قطع شدن عامل | agentId، agentName، lastSeenAt، offlineSince، sourcesCount |
agent.online | وصل شدن دوباره عامل | agentId، agentName، onlineAt، sourcesCount |
نمونه پیام
errorMessage متن فارسی نگاشتشده است، هرگز خروجی خام ابزار. هر رویداد شناسه یکتا دارد؛ تکراریها را با آن کنار بگذارید.
{ "id": "evt_0000000000000000000001", "type": "backup.failed", "created": "2026-10-23T10:00:00.000Z", "data": { "sourceId": "00000000-0000-4000-8000-000000000001", "sourceName": "shop-main", "jobId": "00000000-0000-4000-8000-000000000002", "engine": "postgres", "trigger": "schedule", "attempt": 1, "willRetry": true, "consecutiveFailures": 1, "errorCode": "CONNECTION_FAILED", "errorMessage": "اتصال به پایگاه داده برقرار نشد", "failedAt": "2026-10-23T10:00:00.000Z" }}بررسی امضا
امضا همان امضای همه وبهوکهای نمایک است؛ پیش از هر پردازش، امضا و پنجره زمانی ۳۰۰ ثانیه را بسنجید:
v1 = hex( HMAC-SHA256( key = utf8(secret شامل "whsec_"), message = utf8(t + "." + rawBody) ) )بررسی امضا
import crypto from "node:crypto"; const TOLERANCE_SECONDS = 300; /** rawBody همان بایتهای دریافتی است، پیش از JSON.parse */export function verifyNamayekSignature(rawBody, header, secret, now = Date.now()) { const parts = String(header || "").split(",").map((p) => p.trim().split("=")); const t = Number(parts.find(([k]) => k === "t")?.[1]); const signatures = parts.filter(([k, v]) => k === "v1" && /^[0-9a-f]{64}$/.test(v || "")).map(([, v]) => v); if (!Number.isInteger(t) || signatures.length === 0) return false; if (Math.abs(Math.floor(now / 1000) - t) > TOLERANCE_SECONDS) return false; // replay const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`, "utf8").digest(); return signatures.some((sig) => crypto.timingSafeEqual(expected, Buffer.from(sig, "hex")));} // Express: بدنه خام را نگه دارید// app.post("/hooks/namayek", express.raw({ type: "application/json" }), (req, res) => {// if (!verifyNamayekSignature(req.body.toString("utf8"), req.get("Namayek-Signature"), process.env.NAMAYEK_WEBHOOK_SECRET)) {// return res.sendStatus(400);// }// const event = JSON.parse(req.body.toString("utf8"));// res.sendStatus(200); // سریع پاسخ دهید؛ پردازش در صف// });<?phpconst NAMAYEK_TOLERANCE = 300; function verify_namayek_signature(string $rawBody, string $header, string $secret, ?int $now = null): bool { $now = $now ?? time(); $t = null; $signatures = []; foreach (explode(',', $header) as $part) { [$k, $v] = array_pad(explode('=', trim($part), 2), 2, ''); if ($k === 't' && ctype_digit($v)) { $t = (int) $v; } if ($k === 'v1' && preg_match('/^[0-9a-f]{64}$/', $v)) { $signatures[] = $v; } } if ($t === null || !$signatures) { return false; } if (abs($now - $t) > NAMAYEK_TOLERANCE) { return false; } // replay $expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret); foreach ($signatures as $sig) { if (hash_equals($expected, $sig)) { return true; } } return false;} $raw = file_get_contents('php://input');if (!verify_namayek_signature($raw, $_SERVER['HTTP_NAMAYEK_SIGNATURE'] ?? '', getenv('NAMAYEK_WEBHOOK_SECRET'))) { http_response_code(400); exit;}$event = json_decode($raw, true);http_response_code(200);import hashlib, hmac, re, time TOLERANCE_SECONDS = 300 def verify_namayek_signature(raw_body: bytes, header: str, secret: str, now: float | None = None) -> bool: t, signatures = None, [] for part in (header or "").split(","): key, _, value = part.strip().partition("=") if key == "t" and value.isdigit(): t = int(value) elif key == "v1" and re.fullmatch(r"[0-9a-f]{64}", value): signatures.append(value) if t is None or not signatures: return False if abs(int(now if now is not None else time.time()) - t) > TOLERANCE_SECONDS: # replay return False expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest() return any(hmac.compare_digest(expected, sig) for sig in signatures) # Flask:# @app.post("/hooks/namayek")# def hook():# if not verify_namayek_signature(request.get_data(), request.headers.get("Namayek-Signature", ""), os.environ["NAMAYEK_WEBHOOK_SECRET"]):# abort(400)# event = request.get_json()# return "", 200package namayek import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "regexp" "strconv" "strings" "time") const toleranceSeconds = 300 var sigRe = regexp.MustCompile(`^[0-9a-f]{64}$`) // VerifySignature: rawBody همان بایتهای دریافتی است، پیش از json.Unmarshal.func VerifySignature(rawBody []byte, header, secret string, now time.Time) bool { var t int64 = -1 var signatures []string for _, part := range strings.Split(header, ",") { kv := strings.SplitN(strings.TrimSpace(part), "=", 2) if len(kv) != 2 { continue } switch kv[0] { case "t": if v, err := strconv.ParseInt(kv[1], 10, 64); err == nil { t = v } case "v1": if sigRe.MatchString(kv[1]) { signatures = append(signatures, kv[1]) } } } if t < 0 || len(signatures) == 0 { return false } if d := now.Unix() - t; d > toleranceSeconds || d < -toleranceSeconds { // replay return false } mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(strconv.FormatInt(t, 10) + ".")) mac.Write(rawBody) expected := []byte(hex.EncodeToString(mac.Sum(nil))) for _, sig := range signatures { if hmac.Equal(expected, []byte(sig)) { return true } } return false}آخرین بهروزرسانی: