Appearance
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
| Event | Resource | Description |
|---|---|---|
product.created | products | New product added |
product.updated | products | Product details changed |
product.deleted | products | Product deactivated |
option.created | options | New option added |
option.updated | options | Option details changed |
option.deleted | options | Option deactivated |
category.created | categories | New category added |
category.updated | categories | Category details changed |
category.deleted | categories | Category deactivated |
group.created | groups | New group added |
group.updated | groups | Group details changed |
group.deleted | groups | Group 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"
}
}| Field | Type | Description |
|---|---|---|
type | string | Event type (e.g. product.updated, option.created) |
resource | string | Resource kind: products, options, categories, groups |
id | string | Identifier of the affected resource |
timestamp | string | ISO 8601 timestamp of when the change occurred |
data | object|array|null | Resource 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
| Resource | ID Format | Example |
|---|---|---|
products | PRODUCT_CODE | CELCOM10 |
options | PRODUCT_CODE:FIELD_ID:OPTION_CODE | CELCOM10:package:10 |
categories | CATEGORY_CODE | MOBILE_PREPAID |
groups | GROUP_CODE | TELCO |
Request Headers
Each webhook request includes these headers:
| Header | Description |
|---|---|
Content-Type | application/json |
X-Webhook-Signature | HMAC-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.
| Attempt | Delay After Failure |
|---|---|
| 1 | Immediate |
| 2 | ~1 minute |
| 3 | ~2 minutes |
| 4 | ~4 minutes |
A delivery is considered failed based on the response:
| Response | Retried? | Reason |
|---|---|---|
5xx server error | Yes | Transient failure |
408 request timeout | Yes | Transient failure |
429 too many requests | Yes | Rate limited |
| Connection error / DNS failure | Yes | Network issue |
4xx except 408 and 429 | No | Permanent 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.
