Skip to content

Catalog Webhook Events

Catalog webhooks deliver real-time notifications when your product catalog changes. Each HTTP POST request to your registered endpoint contains a single event signed with HMAC-SHA256.

For setup instructions and configuration, see the Catalog Webhooks Guide.

Event Types

EventResourceDescription
product.createdproductsNew product added
product.updatedproductsProduct details changed
product.deletedproductsProduct deactivated
option.createdoptionsNew option added
option.updatedoptionsOption details changed
option.deletedoptionsOption deactivated
category.createdcategoriesNew category added
category.updatedcategoriesCategory details changed
category.deletedcategoriesCategory deactivated
group.createdgroupsNew group added
group.updatedgroupsGroup details changed
group.deletedgroupsGroup deactivated

Payload Format

json
{
  "type": "product.updated",
  "resource": "products",
  "id": "CELCOM10",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "data": {
    "product_code": "CELCOM10",
    "product_category_code": "MOBILE_PREPAID",
    "name": "Celcom Prepaid",
    "display_name": "Celcom Prepaid Reload",
    "note": null,
    "image_url": "https://dashboard.iimmpact.com/img/CELCOM10.png",
    "processing_time": "instant",
    "display_order": 1,
    "fields": [],
    "fulfillment": null,
    "is_active": true,
    "options_source_type": "static"
  }
}
FieldTypeDescription
typestringEvent type (e.g. product.updated, option.created)
resourcestringResource kind: products, options, categories, groups
idstringIdentifier of the affected resource
timestampstringISO 8601 timestamp of when the change occurred
dataobject|array|nullResource data, or null for delete events. For option events, may be an array of all options for that field.

Delete Events

For *.deleted events, the data field is null. Use the id field to remove the resource from your local store.

ID Formats

ResourceID FormatExample
productsPRODUCT_CODECELCOM10
optionsPRODUCT_CODE:FIELD_ID:OPTION_CODECELCOM10:package:10
categoriesCATEGORY_CODEMOBILE_PREPAID
groupsGROUP_CODETELCO

Request Headers

Each webhook request includes these headers:

HeaderDescription
Content-Typeapplication/json
X-Webhook-SignatureHMAC-SHA256 signature: sha256=<hex>

Signature Verification

All webhook requests include an X-Webhook-Signature header. Always verify the signature before processing the event to confirm it originated from IIMMPACT and was not tampered with.

The header format is sha256=<hex_digest>, computed as HMAC-SHA256 of the raw request body using your webhook secret.

typescript
import crypto from "crypto";

function verifyWebhookSignature(
  rawBody: string,
  signature: string | null,
  secret: string,
): boolean {
  if (!signature) return false;

  const providedSig = signature.startsWith("sha256=")
    ? signature.slice(7)
    : signature;

  const expectedSig = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  try {
    return crypto.timingSafeEqual(
      Buffer.from(providedSig, "hex"),
      Buffer.from(expectedSig, "hex"),
    );
  } catch {
    return false;
  }
}

Use Raw Body

Verify the signature against the raw request body bytes, not a parsed-and-re-serialized JSON object. Re-serializing can change key order or whitespace, causing a mismatch.

Handler Example

A complete webhook handler should verify the signature, process the event idempotently, and return 200 OK.

typescript
app.post("/iimmpact/catalog-webhook", async (req, res) => {
  const signature = req.headers["x-webhook-signature"];
  if (!verifyWebhookSignature(req.rawBody, signature, WEBHOOK_SECRET)) {
    return res.status(401).send("Invalid signature");
  }

  const { type, id, data } = req.body;

  switch (type) {
    case "product.created":
    case "product.updated":
      await db.products.upsert({ id, ...data });
      break;

    case "product.deleted":
      await db.products.deactivate(id);
      break;

    case "option.created":
    case "option.updated":
      // data may be an array for field-level changes
      const options = Array.isArray(data) ? data : [data];
      for (const option of options) {
        await db.options.upsert({ id, ...option });
      }
      break;

    case "option.deleted":
      await db.options.deactivate(id);
      break;

    // Handle category.* and group.* similarly.
  }

  return res.status(200).send("OK");
});

Respond Quickly

Return 2xx as soon as your backend accepts the event. If processing is slow, queue the work and process it asynchronously.

Retry Policy

Failed deliveries are retried with exponential backoff. After all retry attempts are exhausted, the event is dropped.

AttemptDelay After Failure
1Immediate
2~1 minute
3~2 minutes
4~4 minutes

A delivery is considered failed based on the response:

ResponseRetried?Reason
5xx server errorYesTransient failure
408 request timeoutYesTransient failure
429 too many requestsYesRate limited
Connection error / DNS failureYesNetwork issue
4xx except 408 and 429NoPermanent failure

Permanent Failures

4xx responses, except 408 and 429, are treated as permanent failures and are not retried. Return 4xx only for genuinely invalid requests, such as bad signatures.

IIMMPACT API Documentation