هشدار قیمت یعنی «وقتی این اتفاق افتاد، به سرورم خبر بدهید». نِت اَرز هر دقیقه هشدارهای اپهای پرو را با نرخ زنده میسنجد و برای هر کدام که فعال شود یک POST امضاشده به آدرس شما میفرستد؛ اگر سرور شما جواب نداد، تا ۶ بار با فاصلهٔ افزایشی دوباره تلاش میکند.
انواع هشدار
| type | کی فعال میشود | پارامتر |
|---|---|---|
above | میانگین از پایین به بالای آستانه عبور کند. بعد از فعالشدن «خلع سلاح» میشود و وقتی نرخ دوباره زیر آستانه رفت، خودکار مسلح میشود — پس هر عبور فقط یک بار خبر میدهد. | threshold (تومان) |
below | عبور از بالا به پایین آستانه؛ همان منطق. | threshold |
change | نرخ نسبت به آخرین اعلان دستکم percent درصد (بالا یا پایین) حرکت کرده باشد. مناسب «با هر ۱٪ تغییر خبرم کنید». | percent (حداقل ۰٫۱) |
update | هر بار که نرخ منتشرشده عوض شود (چند بار در ساعت در بازار فعال). برای آینهکردن نرخ در سیستم خودتان. | — |
ساخت هشدار
میتوانید از پنل هم بسازید. secret را میتوانید خودتان بدهید (۱۶ تا ۶۴ کاراکتر) یا بگذارید تولید شود؛ در هر دو حالت فقط یک بار در پاسخ ساخت برمیگردد.
curl -X POST https://netarz.ir/api/fx/v1/alerts \
-H "Authorization: Bearer $NETARZ_FX_KEY" \
-H "Content-Type: application/json" \
-d '{
"label": "دلار بالای ۱۱۰ هزار",
"code": "USD",
"type": "above",
"threshold": 110000,
"webhook_url": "https://example.com/hooks/netarz-fx"
}'
{
"data": {
"id": 42, "label": "دلار بالای ۱۱۰ هزار", "code": "USD", "type": "above", "threshold": 110000, "percent": null,
"webhook_url": "https://example.com/hooks/netarz-fx", "is_active": true, "armed": true,
"last_value": null, "last_triggered_at": null, "triggered_count": 0, "created_at": "2026-09-05T10:20:00+03:30",
"secret": "whsec_R3q…" // فقط همین یک بار
}
}
سایر عملیات: GET /alerts فهرست، GET /alerts/{id} جزئیات + ۲۰ تحویل اخیر، PATCH /alerts/{id} با is_active / label / webhook_url، DELETE /alerts/{id}. حداکثر ۲۰ هشدار برای هر اپ.
آدرس وبهوک
- باید https باشد و به یک آدرس عمومی اشاره کند؛ آدرسهای شبکهٔ داخلی و loopback رد میشوند (
invalid_webhook_url). - سرور شما باید ظرف ۱۰ ثانیه یک پاسخ 2xx بدهد. کارهای سنگین را به صف بسپارید و سریع ۲۰۰ برگردانید.
- تحویلهای تکراری ممکن است (مثلاً پاسخ شما در راه گم شود)؛ با
X-NetArz-Delivery(یاidدر بدنه) idempotent باشید.
درخواستی که میگیرید
POST /hooks/netarz-fx HTTP/1.1
Content-Type: application/json
User-Agent: NetArz-FX-Webhooks/1.0
X-NetArz-Event: rate.above
X-NetArz-Delivery: 7e0c4b0e-2a1d-4d4a-9a0f-1f8a1e1b2c3d
X-NetArz-Signature: t=1788600000,v1=5f1a2b…c9
{
"id": "7e0c4b0e-2a1d-4d4a-9a0f-1f8a1e1b2c3d",
"event": "rate.above",
"created_at": "2026-09-05T10:20:00+03:30",
"alert": { "id": 42, "label": "دلار بالای ۱۱۰ هزار", "type": "above", "code": "USD", "threshold": 110000, "percent": null },
"app_id": 12,
"reason": "mid crossed above 110000",
"rate": { "code": "USD", "quote_currency": "IRT", "mid": 110200, "buy": 109700, "sell": 110700, "previous_mid": 109800, "as_of": "2026-09-05T10:20:00+03:30" }
}
event یکی از rate.above، rate.below، rate.change، rate.update یا test است. previous_mid مقدار مرجع قبلی است (برای change، مقدار آخرین اعلان).
اعتبارسنجی امضا
هر تحویل با X-NetArz-Signature: t=<timestamp>,v1=<hex> میآید که v1 برابر HMAC-SHA256 رشتهٔ "<t>.<بدنهٔ خام>" با secret هشدار است. همیشه با بدنهٔ خام (قبل از parse) محاسبه کنید و برای مقایسه از تابع constant-time استفاده کنید. در هر تلاش مجدد امضا با timestamp تازه ساخته میشود.
import crypto from "node:crypto";
import express from "express";
const app = express();
app.post("/hooks/netarz-fx", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("X-NetArz-Signature") || "";
const t = header.match(/t=(\d+)/)?.[1];
const v1 = header.match(/v1=([a-f0-9]+)/)?.[1];
if (!t || !v1) return res.status(400).end();
// ۱) پنجرهٔ زمانی ۵ دقیقهای در برابر replay
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.status(400).end();
// ۲) HMAC-SHA256 روی "<t>.<body خام>"
const expected = crypto.createHmac("sha256", process.env.NETARZ_FX_WEBHOOK_SECRET)
.update(`${t}.${req.body.toString("utf8")}`).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) return res.status(401).end();
const event = JSON.parse(req.body);
console.log(event.event, event.rate.code, event.rate.mid);
res.status(200).end(); // هر 2xx = تحویل شد
});
<?php
// Laravel — routes/api.php (بدون CSRF)
Route::post('/hooks/netarz-fx', function (\Illuminate\Http\Request $request) {
$header = (string) $request->header('X-NetArz-Signature');
preg_match('/t=(\d+)/', $header, $t);
preg_match('/v1=([a-f0-9]+)/', $header, $v1);
abort_unless($t && $v1, 400);
abort_if(abs(time() - (int) $t[1]) > 300, 400);
$expected = hash_hmac('sha256', $t[1].'.'.$request->getContent(), config('services.netarz.webhook_secret'));
abort_unless(hash_equals($expected, $v1[1]), 401);
$event = $request->json()->all();
// … کاری که میخواهید: ذخیره، پیام تلگرام، تغییر قیمت محصولات
return response()->noContent();
});
import hmac, hashlib, time, json
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = b"whsec_..."
@app.post("/hooks/netarz-fx")
def hook():
sig = request.headers.get("X-NetArz-Signature", "")
parts = dict(p.split("=", 1) for p in sig.split(","))
if abs(time.time() - int(parts["t"])) > 300:
abort(400)
expected = hmac.new(SECRET, f'{parts["t"]}.'.encode() + request.get_data(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, parts["v1"]):
abort(401)
event = json.loads(request.get_data())
print(event["event"], event["rate"]["mid"])
return "", 200
تلاش مجدد
پاسخ غیر 2xx یا timeout = تلاش مجدد با فاصلههای فوری، ۱ دقیقه، ۵ دقیقه، ۱۵ دقیقه، ۱ ساعت، ۳ ساعت. بعد از ۶ تلاش، تحویل «ناموفق» علامت میخورد و در پنل و GET /alerts/{id} با کد پاسخ و بدنهٔ خطا دیده میشود.
تست
curl -X POST https://netarz.ir/api/fx/v1/alerts/42/test \
-H "Authorization: Bearer $NETARZ_FX_KEY"
# { "data": { "delivery_id": "…", "status": "delivered", "response_code": 200, "attempts": 1 } }
قیمت محصولات فروشگاه را با هر rate.update دوباره حساب کنید · با above/below پیام تلگرام به تیم فروش بفرستید · با change ۰٫۵٪ کش نرخ خود را باطل کنید.