CommerceOS docsmenu

Build

Events & webhooks

Everything that happens in a store is a domain event, recorded transactionally with the change itself and delivered to your endpoints as HMAC-signed webhooks. Ten event types cover the storefront-relevant lifecycle.

The catalog

EventTypeDescription
order.createdeventCheckout created a pending order.
order.paideventThe provider webhook confirmed payment.
order.refundedeventA refund settled (partial or full).
inventory.updatedeventSellable stock changed for a variant.
product.createdeventA product became visible to the storefront.
product.updatedeventProduct or variant fields changed.
product.deletedeventA product left the catalog.
content.updatedeventA region was published or a page/menu changed.
theme.publishedeventA theme version went live.
app.installedeventA merchant installed your app.

Delivery

wire · a signed delivery
▸ POST https://your-app.example.com/hooks/commerceos▸ x-webhook-event: order.paid▸ x-webhook-signature: 3f9a1c…       # hex hmac-sha256 over the exact body▸ x-webhook-attempt: 1{  "id": "184",  "event": "order.paid",  "createdAt": "2026-07-07T14:16:02.000Z",  "data": { "orderId": "4593eb78-…", "storeId": "91506f23-…", … }}◂ 2xx from you = delivered · anything else retries with backoff

Events are written in the same database transaction as the change that caused them (an outbox), then delivered asynchronously. That means at-least-once delivery: you can receive a delivery twice, but you can never miss one that committed.

Verify, then dedupe

wire · signature verification
import { createHmac, timingSafeEqual } from 'node:crypto'; function verify(rawBody: Buffer, signature: string, secret: string) {  const expected = createHmac('sha256', secret).update(rawBody).digest('hex');  const a = Buffer.from(expected);  const b = Buffer.from(signature);  return a.length === b.length && timingSafeEqual(a, b);}// compare against x-webhook-signature BEFORE parsing;// dedupe on the delivery id — deliveries are at-least-once

Verify before you parse. Compute the HMAC over the raw request body with your per-endpoint secret and compare in constant time. Then dedupe on the event id — the platform applies the same discipline to payment-provider webhooks it receives, and it is the difference between at-least-once delivery and double-shipping an order.

Good citizenship

  • Return 2xx fast; do real work on your own queue.
  • Failures retry with exponential backoff — idempotent handlers make retries free.
  • Use content.updated and product.updated to invalidate storefront caches instead of polling.