الويب هوك
ترسل واميا أحداث JSON موقّعة إلى النقاط التي يسجلها مفتاح عبر apiRegisterWebhook (حتى 10 لكل مفتاح؛ يسردها apiMyWebhooks ويحذف apiDeleteWebhook واحدة). اشترك في أنواع مفردة، أو في عائلة بـ <family>.*، أو في كل شيء بـ *.
كتالوج الأحداث
العائلات وأنواعها. connect.test مسموح دائمًا وهو ما يرسله apiTestWebhook.
الكتالوج المباشر من الواجهة
order.*10
- order.placed
- order.confirmed
- order.preparing
- order.shipped
- order.delivered
- order.completed
- order.cancelled
- order.status_changed
- order.slice_status_changed
- order.refunded
fulfilment.*8
- fulfilment.slice_prepared
- fulfilment.slice_ready
- fulfilment.slice_handed_over
- fulfilment.slice_in_transit
- fulfilment.slice_delivered
- fulfilment.delivery_failed
- fulfilment.slice_returned_to_origin
- fulfilment.return_received
rma.*6
- rma.created
- rma.approved
- rma.rejected
- rma.received
- rma.completed
- rma.seller_contested
listing.*1
- listing.state_changed
inventory.*3
- inventory.position_changed
- inventory.back_in_stock
- inventory.out_of_stock
payout.*6
- payout.scheduled
- payout.approved
- payout.sent
- payout.settled
- payout.bounced
- payout.cancelled
penalty.*2
- penalty.applied
- penalty.reversed
connect.*4
- connect.batch_held
- connect.batch_confirmed
- connect.batch_discarded
- connect.anomaly_detected
inbound.*9
- inbound.declared
- inbound.scheduled
- inbound.rescheduled
- inbound.slot_closed
- inbound.arrived
- inbound.received
- inbound.discrepancy
- inbound.no_show
- inbound.cancelled
withdrawal.*8
- withdrawal.requested
- withdrawal.decided
- withdrawal.scheduled
- withdrawal.slot_closed
- withdrawal.ready
- withdrawal.handed_over
- withdrawal.no_show
- withdrawal.cancelled
return.*2
- return.arrived
- return.processed
دلالات التسليم
مرة واحدة على الأقل، بالترتيب لكل مفتاح
أجب بأي 2xx خلال 10 ثوانٍ؛ ونفّذ العمل لاحقًا. أزل التكرار بـ id: تعيد المحاولات استخدامه. تُحاول تسليمات النقطة الواحدة بالترتيب لكن قد تتجاوز إعادة محاولة حدثًا أحدث — قارن occurredAt قبل الكتابة فوق الحالة المحلية.
إعادة المحاولة والتعطيل التلقائي
يُعاد إرسال التسليم الفاشل حتى 8 مرات بتراجع أسي — 30 ثانية ثم ×3 كل مرة بسقف 8 ساعات مع عشوائية — ويُحمل رقم المحاولة في Wamia-Attempt. بعد 72 ساعة من الفشل المستمر تُعطَّل النقطة ويُبلَّغ المتجر. تُحفظ التسليمات 30 يومًا: يعيد المتجر إرسال ما فات من التكاملات › الويب هوك بعد تعافي النقطة.
الاختبار
يرسل apiTestWebhook حدث connect.test موقّعًا ويعيد رمز الحالة وأول بايتات أجاب بها مستقبِلك، فيُكتشف الرابط الخاطئ من شاشة إعدادات الإضافة.
ترويسات التسليم
POST /wamia/webhook HTTP/1.1
Content-Type: application/json
User-Agent: wamia-webhooks/1
Wamia-Signature: t=1759655553,v1=5f1c0b9e…
Wamia-Delivery-Id: 1b6f0d2a-…
Wamia-Event: listing.state_changed
Wamia-Attempt: 1الحمولة
{
"id": "evt_01J9ZK8Q2M3N4P5R6S7T8V9W0X",
"type": "listing.state_changed",
"occurredAt": "2026-10-05T09:12:33.120Z",
"organizationId": "7c1f9f1e-5b4e-4d8f-9a6b-2f3e4d5c6b7a",
"connectionId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"data": { "sku": "TSHIRT-RED-M", "state": "live", "previousState": "in_review" }
}التحقق من التوقيع
يُعرض السر مرة واحدة عند إنشاء النقطة (whsec_…). احسب HMAC-SHA256 على <t>.<raw body> بسلسلة السر الكاملة كمفتاح، وقارن بزمن ثابت مع v1، وارفض الطوابع الزمنية الأقدم من 5 دقائق. استخدم جسم الطلب الخام — إعادة تسلسل JSON تغيّر البايتات.
TypeScript (Node)
import { createHmac, timingSafeEqual } from 'node:crypto';
const TOLERANCE_SECONDS = 300;
export function verifyWamiaSignature(secret: string, header: string, rawBody: string): boolean {
const parts = Object.fromEntries(header.split(',').map((kv) => kv.split('=') as [string, string]));
const timestamp = Number(parts.t);
if (!Number.isFinite(timestamp) || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) return false;
const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(parts.v1, 'hex');
return a.length === b.length && timingSafeEqual(a, b);
}
// Express: keep the raw body — JSON re-serialisation breaks the signature.
app.post('/wamia/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const raw = req.body.toString('utf8');
if (!verifyWamiaSignature(process.env.WAMIA_WEBHOOK_SECRET!, req.header('Wamia-Signature') ?? '', raw)) {
return res.status(401).end();
}
const event = JSON.parse(raw) as { id: string; type: string; occurredAt: string; data: Record<string, unknown> };
// Deduplicate on event.id, then enqueue; answer 2xx within 10 seconds.
res.status(202).end();
});PHP
<?php
const WAMIA_TOLERANCE_SECONDS = 300;
function wamia_verify_signature(string $secret, string $header, string $rawBody): bool
{
$parts = [];
foreach (explode(',', $header) as $pair) {
[$key, $value] = array_pad(explode('=', $pair, 2), 2, '');
$parts[$key] = $value;
}
if (!isset($parts['t'], $parts['v1']) || !ctype_digit($parts['t'])) {
return false;
}
if (abs(time() - (int) $parts['t']) > WAMIA_TOLERANCE_SECONDS) {
return false;
}
$expected = hash_hmac('sha256', $parts['t'] . '.' . $rawBody, $secret);
return hash_equals($expected, $parts['v1']);
}
// WordPress REST route, WooCommerce plugin: register_rest_route('wamia/v1', '/webhook', ...)
$raw = file_get_contents('php://input');
$header = $_SERVER['HTTP_WAMIA_SIGNATURE'] ?? '';
if (!wamia_verify_signature(getenv('WAMIA_WEBHOOK_SECRET'), $header, $raw)) {
http_response_code(401);
exit;
}
$event = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
// Deduplicate on $event['id'], queue the job (Action Scheduler), answer 202 at once.
http_response_code(202);Python
import hmac, hashlib, time
TOLERANCE_SECONDS = 300
def verify_wamia_signature(secret: str, header: str, raw_body: bytes) -> bool:
parts = dict(kv.split("=", 1) for kv in header.split(",") if "=" in kv)
try:
timestamp = int(parts["t"])
given = parts["v1"]
except (KeyError, ValueError):
return False
if abs(time.time() - timestamp) > TOLERANCE_SECONDS:
return False
message = f"{timestamp}.".encode() + raw_body
expected = hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, given)
# Odoo controller: @http.route('/wamia/webhook', type='http', auth='public', methods=['POST'], csrf=False)
def wamia_webhook(self, **kwargs):
raw = request.httprequest.get_data()
header = request.httprequest.headers.get("Wamia-Signature", "")
if not verify_wamia_signature(secret, header, raw):
return Response(status=401)
event = json.loads(raw)
# Deduplicate on event["id"], enqueue with queue_job, answer 202 at once.
return Response(status=202)عناوين المصدر
تخرج التسليمات من عناوين NAT ثابتة مدرجة في Wamia Shop تحت التكاملات › الويب هوك. أدرجها إن تطلب جدارك الناري ذلك، لكن اعتمد على التوقيع لا على العنوان.