Skip to content

Events & payloads

Receiving an event needs the scope for its data

Section titled “Receiving an event needs the scope for its data”

Creating a subscription needs write_webhooks. Receiving an event needs the read scope of the data it carries, checked on every delivery against what your installation holds at that moment:

Event family Scope needed to receive
order.* read_orders (write_orders includes it)
product.* read_products
customer.* read_customers
form.submitted read_forms
blog_post.* read_blogs (write_blogs includes it)
metaobject.* read_metaobjects (write_metaobjects includes it)
page.* read_content (write_content includes it)

Fields marked personal data below are removed from the body unless your installation also holds read_customer_pii. If the merchant withdraws a scope, the next event is not delivered to you. Subscribing to an event you cannot receive is refused with 400, naming the scope you need.

Event Subscribable Sent today
order.created yes yes — when the order exists (checkout, channel import), in whatever state it starts
order.paid yes yes
order.processing yes yes
order.fulfilled yes yes — once, when the whole order first leaves the store (Shipped, or Delivered without Shipped)
order.partially_fulfilled yes yes — once, when the first parcel of an order shipped in parts leaves and items are still to ship; order.fulfilled follows with the last parcel
order.shipped yes yes
order.delivered yes yes
order.cancelled yes yes
order.refunded yes yes
product.created / product.updated / product.deleted yes yes — on every committed catalogue change, a variant’s included
customer.created / customer.updated yes yes — on sign-up, import or the merchant adding one, and on every profile or address-book change
form.submitted yes yes
blog_post.created / blog_post.updated / blog_post.published / blog_post.deleted yes yes — on every committed blog change, whichever writer made it; published when a save makes a post live, and when a scheduled post reaches its time. See blog posts
metaobject.created / metaobject.updated / metaobject.published / metaobject.deleted yes yes — on every committed custom-data entry change, whichever writer made it (admin, an app, an import); published when a save makes an entry live (a scheduled entry going live on its own sends nothing)
page.created / page.updated / page.published / page.deleted yes yes — on every committed page change; published when a save makes a page live (a scheduled go-live swept by the clock sends nothing)

Every event in the catalogue is sent. Events go out after the change is committed, typically within seconds.

There is no envelope: the body is the event object, camelCase JSON. The event type travels in the Ciqra-Event header and the event id in Ciqra-Event-Id.

{
"orderId": "5b1e…",
"orderNumber": "SEY-100482",
"status": "Paid",
"previousStatus": "PendingPayment",
"customerEmail": "[email protected]",
"occurredAt": "2026-09-22T10:15:03.412+00:00",
"createdAt": "2026-09-22T10:14:41.020+00:00",
"currency": "USD",
"total": 54.00,
"taxTotal": 4.00,
"shippingTotal": 5.00,
"discountTotal": 0.00,
"giftCardTotal": 0.00,
"paymentMethod": "CiqraPay",
"lines": [
{ "productId": "…", "variantId": "…", "name": "Mug", "quantity": 2, "unitPrice": 22.50, "discount": 0.00 }
]
}
Field Notes
orderId UUID.
orderNumber The number the merchant and shopper see.
status The status the order moved to (for order.created, the status it was created in).
previousStatus The status it moved from; null on order.created.
currency, total, taxTotal, shippingTotal, discountTotal, giftCardTotal The order’s money, in its currency.
lines One entry per order line: product and variant ids, the name at purchase, quantity, unit price, line discount.
customerEmail Personal data — present only with read_customer_pii.
occurredAt When the transition was recorded.

The event id is {orderId}:{status} ({orderId}:created, {orderId}:fulfilled), so one order produces at most one event of each kind.

Addresses are not in the body; for those you will need the order API, which is not yet open to apps — see limitations.

{
"productId": "9c2d…",
"name": "Ceramic Mug",
"slug": "ceramic-mug",
"status": "Active",
"kind": "Physical",
"tags": ["kitchen"],
"publishedAt": "2026-09-20T08:00:00+00:00",
"createdAt": "2026-09-01T12:00:00+00:00",
"updatedAt": "2026-09-22T10:15:03+00:00",
"deleted": false,
"variants": [
{ "variantId": "…", "sku": "MUG-01", "price": 22.50, "compareAtPrice": null, "currency": "USD" }
],
"occurredAt": "2026-09-22T10:15:03.412+00:00"
}
Field Notes
productId UUID.
status, kind The product’s state (Active, Draft, …) and kind (Physical, Digital, …).
variants Every variant: id, SKU, price, compare-at price and currency.
occurredAt When the change was committed.

product.updated is sent for a change to the product or any of its variants. A change only to its sales or rating counters is not an update. Stock levels are not in this event.

product.deleted carries only the id and when it went:

{ "productId": "9c2d…", "deleted": true, "deletedAt": "2026-09-22T10:15:03+00:00", "occurredAt": "2026-09-22T10:15:03.412+00:00" }
{
"customerId": "41a7…",
"email": "[email protected]",
"firstName": "Ada",
"lastName": "Lovelace",
"phone": "+15555550123",
"emailConfirmed": true,
"isActive": true,
"tags": ["vip"],
"preferredLanguage": "en",
"createdAt": "2026-09-01T12:00:00+00:00",
"updatedAt": "2026-09-22T10:15:03+00:00",
"closedAt": null,
"occurredAt": "2026-09-22T10:15:03.412+00:00"
}
Field Notes
email, firstName, lastName, phone Personal data — present only with read_customer_pii.
isActive, closedAt Whether the account can sign in, and when it was closed (null while open).

customer.updated is sent for a change to the profile or the address book. A password change or sign-in is not an update.

{
"formId": "…",
"formName": "Contact",
"submissionId": "…",
"languageCode": "tr",
"submittedAt": "2026-09-22T10:15:03+00:00",
"values": { "name": "…", "email": "…", "message": "…" }
}
Field Notes
values Personal data — present only with read_customer_pii. Keyed by the form’s own field keys, which the merchant defines; it holds whatever the shopper typed.

With read_forms alone you learn that a form was submitted, which one, when and in which language.

{
"postId": "5b1e…",
"slug": "bathrobe-or-towel",
"title": "Bornoz mu havlu mu?",
"status": "published",
"publishedAt": "2026-10-15T08:00:00+00:00",
"editions": [ { "locale": "en", "slug": "bathrobe-or-towel-en" } ],
"categoryIds": [ "…" ],
"deleted": false,
"occurredAt": "2026-10-15T08:00:02+00:00"
}
Field Notes
status draft, scheduled or published.
editions Every language the post exists in; slug is null when that edition follows the post’s address.
deleted true on blog_post.deleted, which carries only postId, deleted, deletedAt, occurredAt and, when still readable, slug and title.

No personal data and no body text: read_blogs alone receives the whole payload. blog_post.published is sent when a save makes the post live, and once more when a scheduled post reaches its time (within a minute or two of publishedAt). Details: blog posts.

{
"entryId": "9c2a…",
"type": "tour",
"handle": "bosphorus-sunset",
"status": "published",
"publishedAt": "2026-11-01T08:00:00+00:00",
"locales": null,
"deleted": false,
"occurredAt": "2026-11-01T08:00:02+00:00"
}
Field Notes
type The entry type’s handle (the definition it belongs to).
locales The languages the entry is restricted to, or null for every language.
deleted true on metaobject.deleted, which carries only entryId, type, handle, deleted, deletedAt, occurredAt.

The field values are never in the payload (a field may hold anything the merchant typed): read them with read_metaobjects. No personal data, so read_metaobjects alone receives the whole payload.

{
"pageId": "31d0…",
"slug": "about-us",
"title": "About us",
"status": "published",
"publishedAt": "2026-11-01T08:00:00+00:00",
"locales": [ "tr" ],
"deleted": false,
"occurredAt": "2026-11-01T08:00:02+00:00"
}

status is draft, scheduled or published; locales lists the translated editions. page.deleted carries only pageId, slug, title (when still readable), deleted, deletedAt, occurredAt. No body text and no personal data: read_content alone receives the whole payload.