Webhooks
استقبل أحداث هيو ميل الموقّعة على نقطة HTTPS لديك — التسليم والتفاعل وجهات الاتصال والتحويلات وأحداث المتجر — وتحقق من التوقيع وتعامل مع إعادة المحاولة.
في هذه الصفحة
تتيح Webhooks لـ HyoNova أن يُبلغ أنظمتك عند وقوع حدث ما، بدل أن تسأل أنظمتك مراراً. وهي جزء من هيو ميل: تُبلغ عن أحداث الرسائل المرسلة عبر واجهة Mail البرمجية، ومستلمي الحملات، وجهات الاتصال، والطلبات القادمة من متجر مربوط.
من يستطيع إعدادها#
يحتاج إنشاء Webhooks وتعديلها وحذفها إلى صلاحية إدارة البريد — وهي افتراضياً للمالكين والمديرين العامين.
إنشاء Webhook#
- افتح Webhooks. انتقل إلى البريد › الأتمتة وافتح تبويب Webhooks.
- أضف نقطة نهاية. اختر ويب هوك جديد، وأدخل عنوان نقطة النهاية لديك ووصفاً اختيارياً.
- اختر الأحداث. حدّد الأحداث التي تريدها. لا تتلقى نقطة النهاية إلا الأحداث التي اشتركت فيها.
- انسخ السر. بعد الحفظ يُعرض سر الويب هوك مرة واحدة. احفظه في مدير الأسرار لديك، فأنت تحتاجه للتحقق من التواقيع.
يجب أن تستخدم نقطة النهاية https:// وأن تشير إلى عنوان عام على الإنترنت. تُرفض العناوين الخاصة وعناوين الحلقة المحلية والعناوين الداخلية بالرمز WEBHOOK_HOST_NOT_ALLOWED عند الحفظ، ويُعاد فحصها قبل كل تسليم.
يغيّر تعديل الويب هوك العنوانَ أو الأحداث أو مفعّل. وتعرض التسليمات آخر التسليمات مع الحدث والحالة والمحاولات ورمز HTTP الذي ردّت به نقطة النهاية.
تدوير السر#
يُصدر تدوير السر سراً جديداً ويعرضه مرة واحدة. وخلال الـ 24 ساعة التالية يُوقَّع كل تسليم بالسر الجديد والسر السابق معاً — فتحمل الترويسة قيمتين لـ v1 — لتنقل نقطة النهاية إلى السر الجديد دون أن تفقد أحداثاً. وبعد 24 ساعة يوقّع السر الجديد وحده.
الأحداث#
| الحدث | يُرسل عندما | حقول data |
|---|---|---|
sent | قبلت خدمة البريد الرسالة | المعاملاتية: messageId وemail وoccurredAt. الحملات: campaignId وrecipientId وcontactId وemail وoccurredAt |
delivered | قبلها خادم بريد المستلم | كما في sent |
bounced | ارتدّت نهائياً، ويُحجب العنوان | كما في sent |
complaint | أبلغ المستلم أنها مزعجة، ويُحجب العنوان | كما في sent |
failed | لم تُرسل، أو رفضتها خدمة البريد | كما في sent، مع error للرسائل المعاملاتية |
opened | فتح مستلم الحملة الرسالة أول مرة | حقول الحملة |
clicked | نقر مستلم الحملة رابطاً متتبَّعاً أول مرة | حقول الحملة، مع linkId |
unsubscribed | ألغى مستلم الحملة اشتراكه | حقول الحملة |
message.accepted | قبلت خدمة البريد رسالة معاملاتية | messageId وemail وproviderMessageId وoccurredAt |
campaign.sent | انتهى إرسال حملة | campaignId وaccepted وfailed وsuppressed وskipped وunknown وoccurredAt |
campaign.click | كل نقرة على رابط حملة متتبَّع، لا الأولى فقط | حقول الحملة، مع linkId وsuspect (true عندما تبدو النقرة آلية) |
contact.updated | أُنشئت جهة اتصال أو تغيّرت عبر الواجهة البرمجية أو ربط متجر | contactId وemail وstatus وsource وchange (created أو updated أو consent) وoccurredAt |
conversion.recorded | سُجّل تحويل أو عُدّل | conversion، ومع الاسترداد أيضاً adjustment وnetRevenue |
order.event | أرسل متجر مربوط أي حدث طلب | integrationId وprovider وorder |
order.created | أبلغ متجر مربوط عن طلب جديد | كما في order.event |
order.paid | أبلغ متجر مربوط عن طلب مدفوع | كما في order.event |
الطلب#
يرسل HyoNova كل حدث في طلب HTTP من نوع POST بمحتوى JSON:
POST /your/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: HyoNova-Mail-Webhooks/1.0
HyoNova-Signature: t=1792270000,v1=5d41402abc4b2a76b9719d911017c592...
HyoNova-Delivery: 0f8e7d6c-5b4a-4321-9876-0123456789ab
HyoNova-Attempt: 1{
"id": "0f8e7d6c-5b4a-4321-9876-0123456789ab",
"event": "delivered",
"occurredAt": "2026-10-18T09:30:05.000Z",
"data": {
"messageId": "6f1c2d4e-9a7b-4c1d-8e2f-3a4b5c6d7e8f",
"email": "name@example.com",
"occurredAt": "2026-10-18T09:30:05.000Z"
}
}idهو معرّف التسليم. ويظهر أيضاً في الترويسةHyoNova-Delivery، ويبقى نفسه عند إعادة المحاولة، فاستخدمه لتجاهل التكرار.HyoNova-Attemptهو رقم المحاولة، ويبدأ من 1.
التحقق من التوقيع#
تأخذ HyoNova-Signature الشكل t=<ثوانٍ بتوقيت يونكس>,v1=<التوقيع>. والتوقيع هو HMAC-SHA256 بصيغة hex للنص <t>.<محتوى الطلب الخام> بمفتاح سر الويب هوك. وأثناء تدوير السر تحمل الترويسة قيمتين لـ v1؛ اقبل الطلب إذا طابقت أيٌّ منهما.
للتحقق من طلب:
- اقرأ المحتوى الخام كما وصل تماماً، قبل أي تحليل JSON.
- احسب HMAC-SHA256 للنص
t + "." + bodyبالسر، بصيغة hex. - قارنه بكل قيمة
v1مقارنةً ثابتة الزمن، وارفض الطلب إن لم تطابق أي قيمة. - ارفض الطلبات التي تبتعد قيمة
tفيها أكثر من خمس دقائق عن ساعتك، لمنع إعادة الإرسال.
import { createHmac, timingSafeEqual } from 'node:crypto';
import { createServer } from 'node:http';
export function verifyHyoMailSignature(rawBody: string, header: string, secret: string, toleranceSeconds = 300): boolean {
const parts = header.split(',').map((p) => p.trim());
const t = parts.find((p) => p.startsWith('t='))?.slice(2) ?? '';
const signatures = parts.filter((p) => p.startsWith('v1=')).map((p) => p.slice(3));
if (!/^\d+$/.test(t) || signatures.length === 0) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - Number(t)) > toleranceSeconds) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
return signatures.some((s) => {
const given = Buffer.from(s, 'hex');
return given.length === expected.length && timingSafeEqual(given, expected);
});
}
createServer((req, res) => {
const chunks: Buffer[] = [];
req.on('data', (chunk) => chunks.push(chunk));
req.on('end', () => {
const raw = Buffer.concat(chunks).toString('utf8');
const header = String(req.headers['hyonova-signature'] ?? '');
if (!verifyHyoMailSignature(raw, header, process.env.HYO_MAIL_WEBHOOK_SECRET!)) {
res.writeHead(400).end();
return;
}
const event = JSON.parse(raw); // { id, event, occurredAt, data }
// Ignore event.id if you have already handled it, then queue the work.
res.writeHead(200).end();
});
}).listen(3000);<?php
function hyo_mail_verify(string $rawBody, string $header, string $secret, int $tolerance = 300): bool
{
$timestamp = '';
$signatures = [];
foreach (explode(',', $header) as $part) {
$pair = explode('=', trim($part), 2);
if (count($pair) !== 2) {
continue;
}
if ($pair[0] === 't') {
$timestamp = $pair[1];
} elseif ($pair[0] === 'v1') {
$signatures[] = $pair[1];
}
}
if (!ctype_digit($timestamp) || !$signatures || abs(time() - (int) $timestamp) > $tolerance) {
return false;
}
$expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
foreach ($signatures as $signature) {
if (hash_equals($expected, $signature)) {
return true;
}
}
return false;
}
$raw = file_get_contents('php://input');
$header = $_SERVER['HTTP_HYONOVA_SIGNATURE'] ?? '';
if (!hyo_mail_verify($raw, $header, getenv('HYO_MAIL_WEBHOOK_SECRET'))) {
http_response_code(400);
exit;
}
$event = json_decode($raw, true); // ['id' => ..., 'event' => ..., 'data' => ...]
// Ignore $event['id'] if you have already handled it, then queue the work.
http_response_code(200);# BODY is the raw request body, T is the t= value from HyoNova-Signature
printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$HYO_MAIL_WEBHOOK_SECRET" -hex
# Compare the result with the v1= value(s)الرد وإعادة المحاولة#
- ردّ بأي حالة
2xxخلال 10 ثوانٍ، وأجّل العمل البطيء إلى ما بعد الرد. - لا تُتبع عمليات إعادة التوجيه؛ وجّه Webhook إلى العنوان النهائي.
- أي رد آخر أو انتهاء مهلة أو خطأ شبكة يُحسب محاولة فاشلة. يجري HyoNova حتى 5 محاولات إجمالاً، وينتظر بينها نحو دقيقة، ثم 5 دقائق، ثم 15 دقيقة، ثم ساعة.
- بعد المحاولة الخامسة الفاشلة ينتقل التسليم إلى حالة الرسائل الميتة (
dead_letter) ولا يُعاد تلقائياً. يحتفظ به HyoNova لإعادة محاولة يدوية، لكن قائمة التسليمات لا تعرض زر إعادة المحاولة بعد. أصلح نقطة النهاية، وستُسلَّم الأحداث الجديدة كالمعتاد. - قد تصل التسليمات أكثر من مرة، وليس دائماً بالترتيب. اجعل معالجتك آمنة من التكرار باستخدام
idالتسليم، واستخدمoccurredAtلترتيب الأحداث. - لا يسجّل HyoNova إلا رمز HTTP الذي ردّت به نقطة النهاية، ولا يحفظ محتوى الرد أبداً.
الحالة#
| الميزة | الحالة | ملاحظات |
|---|---|---|
| Webhooks موقّعة | متاحيعمل اليوم لمساحات العمل التي تتضمنه خطتها. | استقبل أحداث هيو ميل الموقّعة عن التسليم والتفاعل وجهات الاتصال والمتجر على نقطة نهاية HTTPS، مع إعادة محاولة تلقائية ويدوية. |
هل كانت هذه الصفحة مفيدة؟
شكراً. لا تُجمع تقييمات الصفحات بعد، لذلك لم يُرسَل شيء. إذا وجدت نقصاً أو خطأً فراجع صفحة «الحصول على المساعدة».