Events
What each webhook topic carries, and how to handle it.
The payload
Every event has the same shape.
{
"id": "bi4brwu1ggngm9oc5jjieait",
"storeId": "ovy3u54frpsl4sd963aja0h1",
"topic": "orders_create",
"data": { "id": "p948dnb4qbo2d86i2mx67svp" },
"isTest": false,
"createdAt": "2026-08-19T12:37:20.940Z"
}Prop
Type
The field is called topic
The event name arrives as topic, not event. Route on that field.
The events
| Topic | Trigger | data.id |
|---|---|---|
products_create | A product is created on the store. | The product ID |
products_update | A product changes: details, status, variants, prices, images. | The product ID |
products_delete | A product is deleted. | The ID of the deleted product |
orders_create | An order is created through the API or Pengine app. | The order ID |
orders_update | An order changes: status, payment, tracking, cancellation, hold and release, line item edits. | The order ID |
Register only what you use. A topic with no URL registered is never queued and never sent.
What to do with each one
For products_create and products_update, fetch
GET /products/:productId and update your stored copy. The
products_update event covers status, price, image, and other product changes.
products_delete is the exception. The product is already gone, so fetching it returns 404.
Remove it from your storefront using the ID in the payload.
orders_create is mostly useful when orders can also start in the Pengine app. Orders your own
storefront created you already know about from the 201 response.
For orders_update, fetch
GET /orders/:orderId and read the current status, fulfillmentStatus
and trackingInfo. Use these fields to notify customers about shipping without polling.
Fetch, then decide
The payload tells you that a resource changed, but not how it changed. Fetch the current resource, compare it with your stored copy, and handle the difference.
New events later
More topics may be added to this version. Handle known topic values and ignore unknown ones. See
Versioning.