أتمتة تنبيهات أسعار الذهب والفضة باستخدام خطافات الويب
أتمت تنبيهات أسعار الذهب والفضة بخطافات ويب موقّعة: شروط التنبيه، وفترات التهدئة، والحمولة، والتحقق عبر HMAC-SHA256 في Node وPython، وإعادة المحاولة.
التنبيهات بدلًا من الاستطلاع
لا تهتم تكاملات كثيرة بالأسعار إلا في لحظات محددة: عندما يتجاوز الذهب مستوى سأل عنه أحد العملاء، أو عندما تهبط الفضة بحدة، أو عندما تتحرك عملة بما يكفي لإعادة تسعير كتالوج. واستطلاع /latest مرة كل دقيقة لالتقاط تلك اللحظات يكلّف نحو 45,000 طلب شهريًا، ومع ذلك يبقى عليك كتابة المنطق الذي يكتشف لحظة العبور.
تنقل تنبيهات الأسعار في MetalAPI هذا العمل إلى الخادم. إذ يتحقق محرك التنبيهات من كل تنبيه نشط مقابل الأسعار اللحظية فور وصولها، ويُشعرك عند تحقق الشرط. ولا تستهلك عمليات التحقق من التنبيهات حصة API لديك، ولا تعمل شيفرتك إلا عندما يحدث شيء ما.
| الخطة | التنبيهات | التسليم |
|---|---|---|
| Free | لا يوجد | — |
| Lite | 3 | البريد الإلكتروني |
| Standard | 10 | البريد الإلكتروني |
| Advanced | 25 | البريد الإلكتروني أو خطاف الويب |
| Pro | 50 | البريد الإلكتروني أو خطاف الويب |
| Ultra | 100 | البريد الإلكتروني أو خطاف الويب |
| Business | 200 | البريد الإلكتروني أو خطاف الويب |
| Enterprise | 500 | البريد الإلكتروني أو خطاف الويب |
تدير التنبيهات من Alerts في لوحة التحكم. يمثل دليل خطافات الويب المرجع المختصر؛ أما هذه المقالة فتغطي التفاصيل التي تحتاجها لنقطة نهاية تعمل في بيئة الإنتاج. وإذا كنت تحتاج إلى كل تحديث للأسعار بدلًا من أحداث العتبات، فإن مقالة البث اللحظي تتناول النهج الآخر.
شروط التنبيه ومتى تنطلق
يراقب كل تنبيه رمزًا واحدًا مسعّرًا بعملة واحدة، مثل XAU بعملة USD أو XAG بعملة EUR. ويصلح أي رمز نشط، بما في ذلك البلاتين والبلاديوم والمعادن الصناعية والطاقة والعملات والعملات المشفرة. وهناك أربعة شروط:
| الشرط | ينطلق عندما | العتبة |
|---|---|---|
above | السعر ≥ العتبة | سعر بعملة التنبيه |
below | السعر ≤ العتبة | سعر بعملة التنبيه |
change_pct_up | التغيّر منذ الإغلاق السابق ≥ العتبة | نسبة مئوية أقل من 100 |
change_pct_down | التغيّر منذ الإغلاق السابق ≤ −العتبة | نسبة مئوية أقل من 100، تُدخل كرقم موجب |
عتبات المعادن لكل أونصة تروي، وهي الوحدة المعيارية. ويشمل ذلك المعادن الصناعية مثل النحاس، التي كثيرًا ما تُسعَّر بالرطل في أماكن أخرى. أما الطاقة فتستخدم وحدة العقد الخاصة بها، أي البرميل للنفط الخام، وتذكر حمولة خطاف الويب الوحدة دائمًا.
تُحسب الأسعار والتغيرات بالنسبة المئوية بعملة التنبيه. بالنسبة إلى XAG بعملة EUR، يمكن لارتفاع اليورو وحده أن يدفع الفضة إلى ما دون عتبتك أو أن يُطلق change_pct_down، حتى لو لم يتغير سعر الفضة بالدولار الأمريكي.
تنطلق التنبيهات عند العبور، لا مع كل تحديث للسعر ما دام الشرط متحققًا. فعندما يرتفع الذهب متجاوزًا 4,000 دولار، تتلقى إشعارًا واحدًا. ويُعاد تفعيل التنبيه بعد أن يعود الشرط غير متحقق، لذا فإن هبوطًا لاحقًا دون المستوى يعقبه ارتفاع جديد فوقه يُشعرك مرة أخرى.
فترة التهدئة هي الحد الأدنى من الوقت بين إشعارين من التنبيه نفسه: من 5 دقائق إلى 7 أيام (10,080 دقيقة)، والقيمة الافتراضية 60 دقيقة. فالسعر الذي يتأرجح حول عتبة ما قد يعبرها مرات كثيرة في ساعة واحدة، وتحوّل فترة التهدئة ذلك إلى رسالة واحدة. وللمستوى الذي لا يهم إلا مرة واحدة، استخدم فترة تهدئة طويلة وأوقف التنبيه مؤقتًا بعد انطلاقه.
ثمة سلوك مفيد للاختبار. فالتنبيه الجديد لا حالة سابقة له، لذا إذا كان شرطه متحققًا بالفعل، فإنه ينطلق مع تحديث السعر التالي، عادةً خلال دقيقة ما دامت السوق مفتوحة للتداول. ويمنحك تنبيه لـ XAU فوق 1 USD مع فترة تهدئة مدتها 5 دقائق عملية تسليم حقيقية تختبر بها. احذفه بعد ذلك.
التسليم بالبريد الإلكتروني أو عبر خطاف الويب
تتوفر تنبيهات البريد الإلكتروني بدءًا من خطة Lite. وهي موجهة إلى الأشخاص: عنوان مثل «MetalAPI alert: Gold (XAU) rose above 4000 USD»، والسعر الحالي، والملاحظة التي أرفقتها بالتنبيه.
أما خطافات الويب، المتاحة بدءًا من خطة Advanced، فموجهة إلى الأنظمة: طلب HTTPS POST بجسم JSON إلى عنوان URL تختاره، موقَّع بحيث يمكنك إثبات أنه صادر من MetalAPI. ويجب أن يستوفي عنوان URL لخطاف الويب ما يلي:
- أن يستخدم
https://، - أن يُحل إلى عنوان IP عام (تُرفض النطاقات الخاصة وعناوين الاسترجاع loopback وعناوين link-local عند حفظ التنبيه، ويُعاد التحقق منها وقت التسليم)،
- ألا يحتوي على اسم مستخدم أو كلمة مرور.
لا تُتبع عمليات إعادة التوجيه، لذا أدخل عنوان URL النهائي. وإذا انتقل حساب إلى خطة لا تتضمن خطافات الويب، فتُسلَّم تنبيهات خطافات الويب الخاصة به عبر البريد الإلكتروني بدلًا من إسقاطها.
تُوقَّع جميع خطافات الويب في الحساب بسر واحد يبدأ بـ whsec_. يمكنك إنشاؤه وإظهاره ونسخه وتدويره من لوحة Webhook signing secret في صفحة Alerts. وإذا لم تكن قد أنشأته، فإنه يُولَّد مع أول عملية تسليم عبر خطاف الويب ثم يُعرض في اللوحة. ويسري التدوير فورًا، وهذا أمر مهم لشيفرة التحقق أدناه.
طلب خطاف الويب
تبدو كل عملية تسليم على النحو التالي:
POST /webhooks/metalapi HTTP/1.1
Content-Type: application/json
User-Agent: MetalAPI-Webhooks/1.0
X-MetalAPI-Timestamp: 1792765800
X-MetalAPI-Signature: t=1792765800,v1=5f0c1e…9a2b
{
"event": "alert.triggered",
"id": 18342,
"created": 1792765800,
"alert": {
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"symbol": "XAU",
"base": "USD",
"condition": "above",
"threshold": 4000,
"note": "Gold above 4k"
},
"price": 4001.25,
"unit": "troy_oz"
}| الحقل | المعنى |
|---|---|
event | قيمته حاليًا دائمًا alert.triggered. تجاهل القيم غير المعروفة حتى لا تعطّل أنواع الأحداث الجديدة معالجك أبدًا. |
id | معرّف رقمي للحدث، فريد لكل تنبيه منطلق ومتطابق في جميع محاولات إعادة الإرسال |
created | وقت Unix الذي انطلق فيه التنبيه |
alert | التنبيه كما ضُبط: id (معرّف UUID) وsymbol وbase وcondition وthreshold وnote |
price | السعر الذي أطلق التنبيه، بوحدة base لكل unit |
unit | troy_oz للمعادن، وbbl أو mmbtu أو gal للطاقة، وcurrency للعملات، وcoin للعملات المشفرة |
الحقل note، الذي يصل إلى 200 حرف، مكان مناسب لمعلومات التوجيه التي تضبطها في لوحة التحكم، مثل اسم فريق أو قناة أو رمز منتج (SKU).
التحقق من التوقيع في Node.js
تأتي ترويسة X-MetalAPI-Signature على الشكل t=<timestamp>,v1=<signature>. والتوقيع هو قيمة HMAC-SHA256 للسلسلة <timestamp>.<raw body> مُرمّزة بالنظام الست عشري بأحرف صغيرة، باستخدام سر whsec_ الخاص بك كمفتاح. ويُرسل الطابع الزمني أيضًا منفردًا في X-MetalAPI-Timestamp. وثلاث تفاصيل تحدد ما إذا كان التحقق سيعمل في بيئة الإنتاج:
- احسب التجزئة على البايتات الخام. قد يؤدي تحليل JSON ثم إعادة تسلسله إلى تغيير المسافات البيضاء أو تنسيق الأرقام وإفساد التوقيع. اقرأ الجسم كمخزن مؤقت (buffer).
- تحقّق من الطابع الزمني في الترويسة. استخدم
tمن الترويسة، لاcreatedمن الجسم. فكل إعادة إرسال تُوقَّع من جديد وقت الإرسال بقيمةtجديدة، بينما تحتفظcreatedبالوقت الأصلي. ونافذة منع إعادة الإرسال المبنية علىcreatedسترفض عمليات إعادة إرسال صالحة. - اقبل سرّين أثناء التدوير. يؤدي التدوير في لوحة التحكم إلى تحويل التوقيع إلى السر الجديد فورًا. أضف السر الجديد إلى جانب القديم وانشر التحديث؛ وستُعاد محاولة عمليات التسليم التي تفشل في هذه الأثناء لبضع دقائق. ثم أزل السر القديم بمجرد أن يعمل الجديد.
مع Express، يوفر express.raw() الجسم على شكل Buffer:
// webhook-server.mjs — Node.js 18+, npm install express
import crypto from "node:crypto";
import express from "express";
// Comma-separated: the current secret, plus the previous one during a rotation.
const SECRETS = (process.env.METALAPI_WEBHOOK_SECRETS ?? "").split(",").filter(Boolean);
const TOLERANCE_S = 300; // reject signatures older than 5 minutes
export function verifySignature(rawBody, header) {
const parts = {};
for (const item of (header ?? "").split(",")) {
const i = item.indexOf("=");
if (i > 0) parts[item.slice(0, i).trim()] = item.slice(i + 1).trim();
}
const ts = parts.t ?? "";
if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > TOLERANCE_S) return false;
if (!/^[0-9a-f]{64}$/.test(parts.v1 ?? "")) return false;
const given = Buffer.from(parts.v1, "hex");
return SECRETS.some((secret) => {
const expected = crypto.createHmac("sha256", secret).update(`${ts}.`).update(rawBody).digest();
return crypto.timingSafeEqual(expected, given);
});
}
async function handleAlert(event) {
const { alert, price, unit } = event;
console.log(`${alert.symbol}/${alert.base} ${alert.condition} ${alert.threshold}: ${price} per ${unit}`);
}
const app = express();
app.post("/webhooks/metalapi", express.raw({ type: "application/json", limit: "64kb" }), (req, res) => {
if (!Buffer.isBuffer(req.body) || !verifySignature(req.body, req.get("X-MetalAPI-Signature"))) {
return res.status(401).send("invalid signature");
}
const event = JSON.parse(req.body.toString("utf8"));
res.sendStatus(204); // answer within 8 seconds, then do the work
if (event.event === "alert.triggered") {
handleAlert(event).catch((err) => console.error("alert handler failed:", err));
}
});
app.listen(3000);يستجيب المعالج قبل القيام بأي عمل. وإذا كان العمل مهمًا، فخزّن الحدث بشكل دائم أولًا (توضح الأقسام التالية كيفية ذلك)، حتى لا يؤدي تعطّل بعد الاستجابة إلى فقدانه.
التحقق من التوقيع في Python
يستخدم التحقق نفسه في Python المكتبة القياسية فقط. وتوفر hmac.compare_digest مقارنة في زمن ثابت:
# webhook_app.py — pip install flask, then: flask --app webhook_app run --port 3000
import hashlib
import hmac
import json
import os
import time
from flask import Flask, abort, request
# Comma-separated: the current secret, plus the previous one during a rotation.
SECRETS = [s for s in os.environ.get("METALAPI_WEBHOOK_SECRETS", "").split(",") if s]
TOLERANCE_S = 300 # reject signatures older than 5 minutes
app = Flask(__name__)
def verify_signature(raw: bytes, header: str) -> bool:
parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
ts, given = parts.get("t", ""), parts.get("v1", "")
if not ts.isdigit() or abs(time.time() - int(ts)) > TOLERANCE_S:
return False
signed = ts.encode() + b"." + raw
return any(
hmac.compare_digest(hmac.new(s.encode(), signed, hashlib.sha256).hexdigest(), given)
for s in SECRETS
)
@app.post("/webhooks/metalapi")
def metalapi_webhook():
raw = request.get_data() # raw bytes, before any JSON parsing
if not verify_signature(raw, request.headers.get("X-MetalAPI-Signature", "")):
abort(401)
event = json.loads(raw)
if event.get("event") == "alert.triggered":
alert = event["alert"]
# Hand the event to a job queue here and return quickly.
app.logger.warning("%s/%s %s %s: %s", alert["symbol"], alert["base"],
alert["condition"], alert["threshold"], event["price"])
return "", 204لا تختلف أطر العمل الأخرى إلا في طريقة قراءة الجسم الخام: await request.body() في FastAPI وrequest.body في Django. وأيًّا كان إطار العمل، تأكد من أن أي برمجية وسيطة لم تحلّل الجسم وتستهلكه قبل تشغيل معالجك.
إعادة المحاولة والتكرار وأزمنة الاستجابة
تنجح عملية التسليم عندما تستجيب نقطة النهاية لديك بأي حالة 2xx خلال 8 ثوانٍ. وتُعد انتهاءات المهلة وأخطاء الاتصال وعمليات إعادة التوجيه وأي حالة أخرى إخفاقات، فيُعاد إرسالها:
| المحاولة | التوقيت |
|---|---|
| 1 | فور انطلاق التنبيه |
| 2 | بعد نحو دقيقة واحدة من انطلاق التنبيه |
| 3 | بعد نحو 4 دقائق من انطلاق التنبيه |
بعد ثلاث محاولات يُوسَم الحدث بأنه فاشل. ويسرد جدول Recent deliveries في صفحة Alerts كل حدث مع نتيجته، بما في ذلك حالة HTTP أو رسالة الخطأ في المحاولة الأخيرة. وهو أول مكان ينبغي أن تنظر فيه عندما تتصرف نقطة نهاية بشكل غير متوقع.
تستخدم عمليات إعادة الإرسال id الحدث نفسه. فالتسليم الذي عالجه خادمك لكنه استجاب له بعد أكثر من 8 ثوانٍ يُحتسب فاشلًا من جهة MetalAPI ويصل مرة أخرى، لذا يجب أن يكون المعالج آمنًا عند التكرار (idempotent). وأبسط طريقة لذلك جدول مفهرس بمعرّف الحدث:
create table metalapi_events (
event_id bigint primary key, -- payload "id", identical across retries
alert_id uuid not null,
payload jsonb not null,
received_at timestamptz not null default now(),
processed_at timestamptz
);
-- In the webhook handler, after verifying the signature and before responding:
insert into metalapi_events (event_id, alert_id, payload)
values ($1, $2, $3)
on conflict (event_id) do nothing;أدرج الحدث، واستجب بالحالة 204، ودع عاملًا في الخلفية يعالج الصفوف التي تكون فيها processed_at فارغة. أما التسليم المكرر فيصطدم بالمفتاح الأساسي ولا يغيّر شيئًا. ويستغرق الإدراج أجزاءً من الثانية، ما يُبقيك ضمن نافذة الثواني الثماني بأمان حتى عندما يكون العمل اللاحق بطيئًا.
تمرير التنبيهات إلى تطبيقات المحادثة والأنظمة الأخرى
من الاستخدامات الشائعة نشر التنبيهات في محادثة الفريق. يقبل كل من Slack وDiscord خطافات الويب الواردة، لكن لا توجّه تنبيه MetalAPI إليهما مباشرةً: فهما يتوقعان حمولة مختلفة، وستفقد التحقق من التوقيع. تحقّق في نقطة النهاية الخاصة بك، ثم نسّق الرسالة ومرّرها. تحل هذه النسخة من handleAlert محل دالة التسجيل المبدئية في خادم Node.js أعلاه:
const VERBS = { above: "rose above", below: "fell below", change_pct_up: "is up", change_pct_down: "is down" };
async function handleAlert({ alert, price, unit }) {
const level = alert.condition.startsWith("change_pct")
? `${alert.threshold}% since the previous close`
: `${alert.threshold} ${alert.base}`;
const per = unit && unit !== "currency" ? ` per ${unit}` : "";
const text = `${alert.symbol} ${VERBS[alert.condition]} ${level}: now ${price} ${alert.base}${per}` +
(alert.note ? ` (${alert.note})` : "");
// Slack incoming webhooks expect {"text": ...}; Discord webhooks expect {"content": ...}.
const body = process.env.CHAT_KIND === "discord" ? { content: text } : { text };
const res = await fetch(process.env.CHAT_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
signal: AbortSignal.timeout(10_000),
});
if (!res.ok) throw new Error(`chat webhook returned ${res.status}`);
}بالنسبة إلى الحمولة النموذجية، تكون الرسالة «XAU rose above 4000 USD: now 4001.25 USD per troy_oz (Gold above 4k)». ويصلح النمط نفسه لأي نظام يوفر واجهة HTTP API: فتح تذكرة دعم، أو تحديث أسعار المنتجات المخزّنة مؤقتًا في متجرك عندما يتحرك الذهب بأكثر من 2% في يوم واحد، أو إشعار المسؤول عن تغذية الأسعار. شغّل عملية التمرير في عامل خلفي بدلًا من معالج الطلب، حتى لا تتسبب خدمة محادثة بطيئة أبدًا في انتهاء مهلة التسليم.
لاختبار نقطة النهاية دون انتظار السوق، وقّع حمولة نموذجية بنفسك باستخدام سرك:
SECRET="whsec_your_secret"
BODY='{"event":"alert.triggered","id":1,"created":1792765800,"alert":{"id":"00000000-0000-0000-0000-000000000000","symbol":"XAU","base":"USD","condition":"above","threshold":4000,"note":"test"},"price":4001.25,"unit":"troy_oz"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')
curl -i -X POST http://localhost:3000/webhooks/metalapi \
-H "Content-Type: application/json" \
-H "X-MetalAPI-Timestamp: $TS" \
-H "X-MetalAPI-Signature: t=$TS,v1=$SIG" \
--data-raw "$BODY"تعني الحالة 204 أن التحقق من التوقيع قد نجح. ولإجراء اختبار شامل من البداية إلى النهاية، أنشئ تنبيهًا يكون شرطه متحققًا بالفعل، كما هو موضح أعلاه، وراقب ظهور الحدث في Recent deliveries. تبدأ التنبيهات التي تُسلَّم عبر خطافات الويب من خطة Advanced؛ راجع الأسعار للاطلاع على الخطط، أو أنشئ حسابًا لتجربة تنبيهات البريد الإلكتروني في خطة Lite أولًا.
جرّبه مع بياناتك الخاصة
أنشئ حسابًا مجانيًا وأرسل طلبك الأول في أقل من دقيقتين. 100 طلب شهريًا، دون بطاقة ائتمان.
تابع القراءة
واجهة API لأسعار الذهب: الدليل الشامل للمطورين
كيف تستخدم واجهة API لأسعار الذهب: احصل على مفتاح، واستدعِها من curl وJavaScript وPython، واقرأ الأسعار بدقة، وتعامل مع السجل والأخطاء والحصص والتخزين المؤقت.
كيف تضيف ودجة لأسعار الذهب المباشرة إلى موقعك الإلكتروني
أضف ودجة لأسعار الذهب المباشرة إلى WordPress أو Shopify أو صفحة HTML عادية: أنشئها في لوحة تحكم MetalAPI، واختر الرموز والتخطيط والسمة، ثم الصق سطرين فقط.
شرح أسعار الذهب حسب العيار: عيار 24 و22 و18 وكيفية حسابها
ما معنى عيار 24 و22 و18، وكيف ترتبط بها علامات النقاوة مثل 916 و750، وكيف تحسب سعر غرام الذهب لكل عيار وتحصل عليه من واجهة API لأسعار الذهب.