پرش به مطلب اصلی

وب‌هوک‌ها

وب‌هوک راهی است که زربان با آن به شما خبر می‌دهد چیزی رخ داده — واریزی رسیده، برداشتی انجام شده، وثیقه‌ای نقد شده. همین است که جای فراخوانی مکرر API را می‌گیرد.

ثبت آدرس

curl -sS -X POST "https://hd.zarban.io/webhooks/PLN" \
-H "Authorization: Bearer $ZARBAN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.shoma.com/webhooks/zarban"
}'
{
"entityId": "PLN",
"url": "https://api.shoma.com/webhooks/zarban",
"signingKey": "whsec_8f2c1a9b3e7d4f60a1c2b3d4e5f60718"
}

آدرس باید HTTPS باشد.

signingKey فقط در همین پاسخ — و در پاسخ چرخاندن کلید — به‌صورت متن ساده برمی‌گردد. فهرست‌گیری بعدی تنها signingKeyMasked را برمی‌گرداند: whsec_••••0718.

آن را در همین مرحله در محل نگهداری اسرار سرویس خود ذخیره کنید.

دیدن وب‌هوک‌های ثبت‌شده

curl -sS "https://hd.zarban.io/webhooks/PLN" \
-H "Authorization: Bearer $ZARBAN_TOKEN"

همین آدرس‌ها در داشبورد سازمانی هم فهرست می‌شوند و از همان‌جا می‌توانید بسازید، ویرایش کنید، کلید امضا را بچرخانید یا حذف کنید:

تب وب‌هوک‌ها در داشبورد زربان سازمانی
هر ردیف یک اندپوینت ثبت‌شده است. ستون «رویدادها» تعداد رویدادهایی را نشان می‌دهد که به این آدرس فرستاده می‌شود و «کلید امضا» همان جایی است که کلید را می‌چرخانید.

حذف

curl -sS -X DELETE "https://hd.zarban.io/webhooks/PLN?url=https%3A%2F%2Fapi.shoma.com%2Fwebhooks%2Fzarban" \
-H "Authorization: Bearer $ZARBAN_TOKEN"

آدرس در پارامتر url می‌آید و باید URL-encode شده باشد.

بررسی امضا

زربان هر تحویل را با کلید امضای شما امضا می‌کند. امضا HMAC-SHA256 روی بدنه خام درخواست است و در قالب hex می‌آید.

نام دقیق هدر امضا را از تماس سازمانی خود در زربان بگیرید. این مقدار در پیکربندی سازمان شما مشخص می‌شود و در نمونه‌های زیر با SIGNATURE_HEADER نشان داده‌شده است. پیش از رفتن به تولید حتماً آن را جایگزین کنید.

دو قاعده که شکستنشان بررسی امضا را بی‌اثر می‌کند:

  1. روی بدنه خام حساب کنید، نه روی JSON تجزیه‌شده و دوباره سریال‌شده. تجزیه و سریال‌سازی دوباره، فاصله‌ها و ترتیب کلیدها را تغییر می‌دهد و امضا دیگر نمی‌خواند. در بیشتر چارچوب‌های وب باید صریحاً بدنه خام را نگه دارید.

  2. مقایسه را زمان‌ثابت انجام دهیدhmac.compare_digest در پایتون، crypto.timingSafeEqual در نود. مقایسه رشته‌ای ساده با == نشت زمانی دارد.

Node.js

const crypto = require("crypto");

// کلید امضا را در متغیر محیطی نگه دارید، نه در کد.
const signingKey = process.env.ZARBAN_WEBHOOK_SIGNING_KEY;

function isValidSignature(rawBody, signatureHeader) {
const expected = crypto
.createHmac("sha256", signingKey)
.update(rawBody)
.digest("hex");

const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Python

import hmac
import hashlib
import os

signing_key = os.environ["ZARBAN_WEBHOOK_SIGNING_KEY"].encode()

def is_valid_signature(raw_body: bytes, signature_header: str) -> bool:
expected = hmac.new(signing_key, raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header)

چرخاندن کلید امضا

curl -sS -X PUT "https://hd.zarban.io/webhooks/PLN/signing-key?url=https%3A%2F%2Fapi.shoma.com%2Fwebhooks%2Fzarban" \
-H "Authorization: Bearer $ZARBAN_TOKEN"

کلید تازه در پاسخ برمی‌گردد و کلید قبلی بلافاصله باطل می‌شود. اگر نمی‌خواهید در فاصله استقرار رویدادی را از دست بدهید، سرویس خود را طوری بنویسید که بتواند مدتی هر دو کلید را بپذیرد.

رویدادها

یازده رویداد وجود دارد:

رویدادچه وقت
WebhookTransactionCoinDepositواریز روی زنجیره به حساب رسید
WebhookTransactionCoinWithdrawalبرداشت روی زنجیره وضعیت گرفت
WebhookTransactionInternalTransferانتقال داخلی انجام شد
WebhookAccountCreatedحساب ساخته شد
WebhookAccountUpdatedحساب تغییر کرد
WebhookAccountDeletedحساب حذف شد
WebhookDebtPositionCreatedموقعیت وثیقه ساخته شد
WebhookDebtPositionActivatedموقعیت وثیقه فعال شد
WebhookDebtPositionAdjustedوثیقه یا بدهی تغییر کرد
WebhookDebtPositionFailedساخت یا تغییر موقعیت ناموفق بود
WebhookDebtPositionLiquidatedموقعیت نقد شد

رویدادهایی که واقعاً دریافت می‌کنید به دسترسی‌های سازمان شما بستگی دارد.

دو شکل نام‌گذاری در کار است. نام‌های جدول بالا همان چیزی است که در لاگ تحویل، در فیلد name، می‌بینید. اما در بدنه رویداد تحویل‌شده، نام به شکل نقطه‌دار می‌آید:

{
"type": "transaction_event",
"name": "transaction.coin.deposit"
}

تنها همین یک نمونه را به‌طور رسمی مستند کرده‌ایم. پس کنترل‌کننده خود را تدافعی بنویسید: روی نام رویداد switch بزنید و شاخه default بگذارید که رویداد ناشناخته را لاگ کند و 200 برگرداند، نه اینکه خطا بدهد. فهرست دقیق نام‌های نقطه‌دار را از تماس سازمانی خود بگیرید.

کنترل‌کننده درست

سرویس شما باید:

  1. بدنه خام را نگه دارد و اول امضا را بررسی کند. اگر امضا نخواند، 401 برگردانید و ادامه ندهید.

  2. سریع 200 برگرداند. کار سنگین را به صف بسپارید. پاسخ کند یعنی تلاش مجدد، و تلاش مجدد یعنی رویداد تکراری.

  3. ایدمپوتنت باشد. ممکن است یک رویداد بیش از یک بار برسد. پیش از اثر دادن، بررسی کنید که قبلاً پردازشش نکرده‌اید.

  4. رویداد را منبع حقیقت نداند. رویداد یک اعلان است. پیش از تحویل کالا یا خدمت، وضعیت را از API بخوانید:

curl -sS "https://hd.zarban.io/transactions/55012" \
-H "Authorization: Bearer $ZARBAN_TOKEN"

رویداد یک اعلان است، نه منبع حقیقت وضعیت: می‌گوید «چیزی رخ داد»، و وضعیت جاری را API برمی‌گرداند. تراکنشی که رویدادش رسیده ممکن است هنوز در وضعیت پایانی نباشد. الگوی درست این است که با دریافت رویداد، وضعیت را از API بخوانید و اعتباردهی یا تحویل را به وضعیت پایانی گره بزنید.

لاگ تحویل

اگر مطمئن نیستید رویدادی رسیده یا نه، لازم نیست به لاگ سمت خودتان تکیه کنید:

curl -sS "https://hd.zarban.io/webhooks/PLN/logs?limit=20" \
-H "Authorization: Bearer $ZARBAN_TOKEN"
{
"data": [
{
"id": 90215,
"webhookId": 17,
"name": "WebhookTransactionCoinDeposit",
"url": "https://api.shoma.com/webhooks/zarban",
"requestBody": "{\"type\":\"transaction_event\",\"name\":\"transaction.coin.deposit\"}",
"responseBody": "OK",
"statusCode": 200,
"latencyMs": 142,
"retries": 0,
"webhookCallUuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"createdAt": "2026-06-27T08:15:30Z"
}
],
"hasMore": true,
"nextCursor": 90150
}

این لاگ برای عیب‌یابی بسیار کارآمد است:

  • statusCode برابر null یعنی خطای شبکه — آدرس شما اصلاً پاسخ نداد.
  • retries بزرگ‌تر از صفر یعنی تحویل‌های قبلی ناموفق بوده‌اند.
  • webhookCallUuid همه تلاش‌های یک رویداد را به هم وصل می‌کند. برای فهمیدن اینکه یک رویداد چند بار و با چه نتیجه‌ای فرستاده شده، بر اساس همین فیلد گروه‌بندی کنید — نه بر اساس id که برای هر تلاش متفاوت است.
  • latencyMs بالا یعنی کنترل‌کننده شما کند است و در آستانه ایجاد تحویل تکراری قرار دارد.

صفحه‌بندی با cursor انجام می‌شود، مانند فهرست حساب‌ها.

همین لاگ در داشبورد هم هست. برای عیب‌یابی سریع معمولاً همین کافی است:

تب لاگ‌های وب‌هوک در داشبورد زربان سازمانی
ردیف سوم نمونه‌ای از یک تحویل ناموفق است: پاسخ ۵۰۴ پس از ۳۰ ثانیه، و دو تلاش مجدد. کنار آن دو تحویل موفق با تأخیر حدود ۱۳۰ میلی‌ثانیه — همان تفاوتی که در «کنترل‌کننده درست» توضیح دادیم.

در توسعه، بدون آدرس عمومی

ثبت وب‌هوک به یک آدرس عمومی HTTPS نیاز دارد، چون زربان رویداد را به سرویس شما می‌فرستد. در توسعه محلی می‌توانید با یک تونل (مانند ngrok) آدرس موقتی بسازید و همان را ثبت کنید.

وب‌هوک توسعه را روی سازمان تولیدی خود ثبت نکنید و پس از پایان کار پاکش کنید. آدرس تونلی که منقضی شده، تنها تحویل‌های ناموفق را در لاگ انباشته می‌کند.