order.createdOrder COD/manual yang sah, atau FPX selepas gerbang mengesahkan bayaran.
ROAS.my Developers
ROAS.my menghantar event jualan ke endpoint HTTPS milik anda. Ini ialah kontrak webhook keluar, bukan inbound API. Setiap endpoint memilih event yang diperlukan dan menerima payload lengkap versi 1.
order.createdOrder COD/manual yang sah, atau FPX selepas gerbang mengesahkan bayaran.
order.updatedStatus payment atau fulfillment bagi order sah berubah.
checkout.abandonedCheckout belum selesai selepas kira-kira 5 minit tanpa aktiviti form dan mempunyai nombor telefon. Ini bukan order.
order.payment_failedGerbang mengesahkan kegagalan atau order FPX tamat melalui expiry generic/CHIP.
Recovery daripada bayaran gagal kepada paid masih menghasilkan order.created. Gunakan eventId sebagai idempotency key; event failure dan abandoned menggunakan ID deterministik.
| Header | Nilai |
|---|---|
| Content-Type | application/json |
| X-Roas-Event | Nama event payload |
| X-Roas-Delivery | ID cubaan delivery ROAS.my |
| X-Roas-Signature | Hex HMAC-SHA256 bagi raw request body |
Kira signature sebelum parse atau format semula JSON. Signing secret berbeza bagi setiap endpoint dan boleh disalin dari Settings → Integrasi.
import crypto from "node:crypto";
const rawBody = await readRawRequestBody(request);
const received = request.headers["x-roas-signature"];
const expected = crypto
.createHmac("sha256", process.env.ROAS_WEBHOOK_SECRET)
.update(rawBody)
.digest("hex");
const valid = typeof received === "string" &&
received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!valid) return response.status(401).end();
const payload = JSON.parse(rawBody.toString("utf8"));
await processIdempotently(payload.eventId, payload);
return response.status(204).end();$rawBody = file_get_contents('php://input');
$received = $_SERVER['HTTP_X_ROAS_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $rawBody, getenv('ROAS_WEBHOOK_SECRET'));
if (!hash_equals($expected, $received)) {
http_response_code(401);
exit;
}
$payload = json_decode($rawBody, true, flags: JSON_THROW_ON_ERROR);
process_idempotently($payload['eventId'], $payload);
http_response_code(204);order.package.sku ialah identifier Checkout/package dan bukan line inventori tambahan. SKU fizikal serta kuantitinya berada dalam order.package.variant,order.package.bundleItems dan order.orderBumps. Nilai SKU boleh sama antara metadata package dan item fizikal; jangan jumlahkan kedua-duanya.
{
"schemaVersion": 1,
"event": "order.created",
"eventId": "order.created:ord_01",
"occurredAt": "2026-08-22T04:10:00.000Z",
"test": false,
"order": {
"code": "AH-ABC123-1001",
"status": "confirmed",
"paymentStatus": "cod",
"fulfillmentStatus": "unfulfilled",
"paymentMethod": "cod",
"customer": {
"name": "Nur Aisyah",
"phone": "60123456789",
"email": "[email protected]",
"address1": "12 Jalan Melur",
"address2": "Taman Indah",
"postcode": "40000",
"city": "Shah Alam",
"state": "Selangor",
"customFields": {}
},
"package": {
"slug": "pakej-kombo",
"name": "Pakej Kombo",
"sku": "CHECKOUT-KOMBO",
"quantity": 1,
"variant": null,
"bundleItems": [
{
"componentId": "sku_01",
"name": "Produk A",
"sku": "PROD-A",
"quantity": 2,
"role": "choice"
}
]
},
"orderBumps": [],
"orderBump": null,
"amount": {
"subtotalSen": 12900,
"shippingSen": 1000,
"codFeeSen": 0,
"totalSen": 13900,
"currency": "MYR"
},
"tracking": {
"courier": "",
"number": "",
"url": ""
},
"utm": {
"source": "facebook",
"medium": "paid",
"campaign": "launch",
"content": null,
"term": null,
"fbclid": null
},
"createdAt": "2026-08-22T04:10:00.000Z",
"updatedAt": "2026-08-22T04:10:00.000Z"
}
}{
"schemaVersion": 1,
"event": "order.updated",
"eventId": "evt_update_01",
"occurredAt": "2026-08-22T06:00:00.000Z",
"test": false,
"order": {
"code": "AH-ABC123-1001",
"status": "shipped",
"paymentStatus": "cod",
"fulfillmentStatus": "shipped",
"paymentMethod": "cod",
"customer": {
"name": "Nur Aisyah",
"phone": "60123456789",
"email": "[email protected]",
"address1": "12 Jalan Melur",
"address2": "Taman Indah",
"postcode": "40000",
"city": "Shah Alam",
"state": "Selangor",
"customFields": {}
},
"package": {
"slug": "pakej-kombo",
"name": "Pakej Kombo",
"sku": "CHECKOUT-KOMBO",
"quantity": 1,
"variant": null,
"bundleItems": []
},
"orderBumps": [],
"orderBump": null,
"amount": {
"subtotalSen": 12900,
"shippingSen": 1000,
"codFeeSen": 0,
"totalSen": 13900,
"currency": "MYR"
},
"tracking": {
"courier": "Ninja Van",
"number": "NVMY123",
"url": "https://example.com/track/NVMY123"
},
"utm": {
"source": "facebook",
"medium": "paid",
"campaign": "launch",
"content": null,
"term": null,
"fbclid": null
},
"createdAt": "2026-08-22T04:10:00.000Z",
"updatedAt": "2026-08-22T06:00:00.000Z"
}
}{
"schemaVersion": 1,
"event": "order.payment_failed",
"eventId": "order.payment_failed:ord_02",
"occurredAt": "2026-08-22T05:15:00.000Z",
"test": false,
"order": {
"code": "AH-DEF456-1002",
"status": "failed",
"paymentStatus": "failed",
"fulfillmentStatus": "unfulfilled",
"paymentMethod": "fpx",
"customer": {
"name": "Muhammad Ali",
"phone": "60198765432",
"email": "[email protected]",
"address1": "8 Jalan Damai",
"address2": "",
"postcode": "75450",
"city": "Melaka",
"state": "Melaka",
"customFields": {}
},
"package": {
"slug": "pakej-asas",
"name": "Pakej Asas",
"sku": "CHECKOUT-ASAS",
"quantity": 1,
"variant": null,
"bundleItems": []
},
"orderBumps": [],
"orderBump": null,
"amount": {
"subtotalSen": 7900,
"shippingSen": 800,
"codFeeSen": 0,
"totalSen": 8700,
"currency": "MYR"
},
"tracking": {
"courier": "",
"number": "",
"url": ""
},
"utm": {
"source": null,
"medium": null,
"campaign": null,
"content": null,
"term": null,
"fbclid": null
},
"createdAt": "2026-08-22T04:15:00.000Z",
"updatedAt": "2026-08-22T05:15:00.000Z"
}
}{
"schemaVersion": 1,
"event": "checkout.abandoned",
"eventId": "checkout.abandoned:cs_01",
"occurredAt": "2026-08-22T04:40:00.000Z",
"test": false,
"checkout": {
"customer": {
"name": "Siti Hajar",
"phone": "60111222333",
"email": "",
"address1": "",
"address2": "",
"postcode": "",
"city": "",
"state": "",
"customFields": {}
},
"package": {
"slug": "pakej-kombo",
"name": "Pakej Kombo",
"sku": "CHECKOUT-KOMBO",
"variantSelections": {},
"bundleSelections": {
"sku_01": 2
},
"bundleSlotSelections": {}
},
"paymentMethod": "cod",
"source": {
"landingPage": {
"id": "lp_01",
"slug": "promo-ogos",
"title": "Promo Ogos"
},
"sessionId": "sid_01",
"utm": {
"source": "facebook",
"medium": "paid",
"campaign": "launch",
"content": null,
"term": null,
"fbclid": null
}
},
"createdAt": "2026-08-22T04:00:00.000Z",
"lastActivityAt": "2026-08-22T04:10:00.000Z",
"resumeUrl": "https://shop.example.com/checkout/pakej-kombo?rt=resume_token"
}
}Settings menghantar payload sintetik dengan test: true dan kod TEST-WEBHOOK. Jangan jadikannya order sebenar.
Payload membawa PII yang diperlukan oleh sistem order. Gunakan endpoint terkawal, encrypt at rest dan hadkan retention/access.
Payload tidak membawa API key, payment token, tracking secret atau signing secret. Jangan log raw headers/PII tanpa kawalan.
schemaVersion: 1 ialah kontrak semasa. Field optional baharu boleh ditambah tanpa menaikkan versi; perubahan breaking akan mendapat schemaVersion baharu dan tempoh migrasi. Parser anda patut mengabaikan field yang tidak dikenali.
22 Ogos 2026 — schemaVersion 1
Tambah catalog empat event, Checkout SKU, payload snapshot, synthetic testing dan JSON Schema awam.