وبهوکها
وبهوک راهی است که زربان با آن به شما خبر میدهد چیزی رخ داده — واریزی رسیده، برداشتی انجام شده، وثیقهای نقد شده. همین است که جای فراخوانی مکرر 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 نشان دادهشده است. پیش از رفتن به تولید حتماً آن را جایگزین کنید.
دو قاعده که شکستنشان بررسی امضا را بیاثر میکند:
-
روی بدنه خام حساب کنید، نه روی JSON تجزیهشده و دوباره سریالشده. تجزیه و سریالسازی دوباره، فاصلهها و ترتیب کلیدها را تغییر میدهد و امضا دیگر نمیخواند. در بیشتر چارچوبهای وب باید صریحاً بدنه خام را نگه دارید.
-
مقایسه را زمانثابت انجام دهید —
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 برگرداند، نه اینکه خطا بدهد. فهرست دقیق نامهای نقطهدار را از تماس سازمانی خود بگیرید.
کنترلکننده درست
سرویس شما باید:
-
بدنه خام را نگه دارد و اول امضا را بررسی کند. اگر امضا نخواند،
401برگردانید و ادامه ندهید. -
سریع
200برگرداند. کار سنگین را به صف بسپارید. پاسخ کند یعنی تلاش مجدد، و تلاش مجدد یعنی رویداد تکراری. -
ایدمپوتنت باشد. ممکن است یک رویداد بیش از یک بار برسد. پیش از اثر دادن، بررسی کنید که قبلاً پردازشش نکردهاید.
-
رویداد را منبع حقیقت نداند. رویداد یک اعلان است. پیش از تحویل کالا یا خدمت، وضعیت را از 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) آدرس موقتی بسازید و همان را ثبت کنید.
وبهوک توسعه را روی سازمان تولیدی خود ثبت نکنید و پس از پایان کار پاکش کنید. آدرس تونلی که منقضی شده، تنها تحویلهای ناموفق را در لاگ انباشته میکند.