Skip to content

Webhook Payloads

When your catalog changes, Malovex delivers a webhook to the endpoints you’ve registered. This page documents exactly what those requests look like and how to verify them.

To register an endpoint and pick which changes it receives, see Webhooks in the panel guide.

Every delivery is an HTTP POST to your endpoint URL, with a JSON body:

POST /your/endpoint HTTP/1.1
Content-Type: application/json
X-Malovex-Signature: sha256=3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b
X-Malovex-Timestamp: 1751878500
{
"event": "price",
"store_id": 12,
"product_ids": [1, 2, 3],
"occurred_at": "2026-07-07T09:15:00+00:00"
}
Field Type Description
event string The change type: price, content, or removed.
store_id integer | null The store the change relates to. null for endpoints scoped to all stores where the change isn’t store-specific.
product_ids array of integers The IDs of the products that changed.
occurred_at string ISO 8601 timestamp of when the change was dispatched.
event Meaning
price One or more of the listed products had a price change.
content One or more of the listed products had a content change (title, description, attributes, or images).
removed One or more of the listed products were removed from the store’s catalog.
Header Description
Content-Type Always application/json.
X-Malovex-Timestamp The Unix timestamp (seconds) when the delivery was signed.
X-Malovex-Signature An HMAC-SHA256 signature of the request, prefixed with sha256=. Use it to verify authenticity.

Each delivery is signed with the signing secret shown once when you created the endpoint (it begins whsec_). Verify every request before trusting it.

The signature is computed as:

signature = "sha256=" + HMAC_SHA256(secret, timestamp + "." + raw_request_body)

Where timestamp is the value of the X-Malovex-Timestamp header and raw_request_body is the exact bytes of the request body — verify against the raw body before any JSON parsing or re-serialization.

  1. Read the X-Malovex-Timestamp and X-Malovex-Signature headers.

  2. Compute HMAC-SHA256 over the string {timestamp}.{raw_body} using your signing secret.

  3. Prefix your result with sha256= and compare it against the X-Malovex-Signature header, using a constant-time comparison.

  4. Reject the request if they don’t match.

$timestamp = $_SERVER['HTTP_X_MALOVEX_TIMESTAMP'];
$signature = $_SERVER['HTTP_X_MALOVEX_SIGNATURE'];
$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $secret);
if (! hash_equals($expected, $signature)) {
http_response_code(400);
exit;
}
const crypto = require('crypto');
const timestamp = req.headers['x-malovex-timestamp'];
const signature = req.headers['x-malovex-signature'];
const body = req.rawBody; // the raw request body, not the parsed object
const expected =
'sha256=' +
crypto.createHmac('sha256', secret).update(`${timestamp}.${body}`).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
return res.status(400).end();
}

Return a 2xx status to acknowledge the delivery. Any other status — or a connection failure — is treated as a failed delivery.

Because a delivery may in rare cases be re-sent, make your handler idempotent: use the event plus product_ids to deduplicate, and treat re-processing the same change as a no-op.

Malovex tracks each endpoint’s health:

  • A failed delivery increments the endpoint’s consecutive-failure count.
  • After repeated failures, your account owners are notified.
  • After sustained failures, the endpoint is paused for a cooldown period; deliveries resume automatically on the next change once the cooldown lifts.
  • A single successful delivery resets the failure count and clears any pause.

You can review recent deliveries, their status, and the response code on the endpoint’s page in the panel.

If you’d rather pull changes than receive pushes — or want a way to catch up after downtime — use the changes feed instead of (or alongside) webhooks.

GET /v1/changes

Returns the latest change per product since a timestamp, plus a cursor to continue from.

Parameter Type Default Description
store string default store Slug of the store to read changes for.
since string (ISO 8601) Return changes after this time. Omit on the first call.
Terminal window
curl "https://api.malovex.com/v1/changes?store=eu-store&since=2026-07-07T09:00:00Z" \
-H "Authorization: Bearer mlx_xxx"
{
"changes": [
{ "product_id": 1, "change_type": "price", "created_at": "2026-07-07T09:15:00+00:00" },
{ "product_id": 2, "change_type": "content", "created_at": "2026-07-07T09:16:30+00:00" }
],
"next_since": "2026-07-07T09:16:30+00:00",
"store": "eu-store"
}
Field Description
changes The latest change per product in this window. Each has product_id, change_type (price, content, or removed), and created_at.
next_since Pass this back as since on your next call to continue where you left off.
store The store slug the feed was read for.
  1. On your first poll, call without since to establish a starting cursor from next_since.

  2. On each subsequent poll, pass the previous next_since back as since.

  3. For each returned change, fetch the affected product from Products or Prices to get the new values.

  4. Keep polling with the latest next_since on your own schedule.