Webhooks
Webhook endpoint setup, delivery behavior, and event payload fields.
Last updated:
You can subscribe to 38 events across 5 categories. Each JSON body matches the model shown for that event; there is no shared envelope.
Configure the endpoint
Register an endpoint for the store with POST /v1/webhooks/endpoints. The URL must use HTTPS, be publicly reachable, and not resolve to a private or local network.
On creation, Ecomiq sends webhook.endpoint.test. The endpoint is saved only when that request returns 2xx within the 15-second timeout.
| Requirement | Value |
|---|---|
| Scheme | https |
| Maximum length | 2048 characters |
| Uniqueness | The same URL cannot repeat in the current store and seller scope |
| Successful response | Any 2xx |
| Timeout | 15 seconds |
Delivery headers
| Header | Content |
|---|---|
X-Ecomiq-Event | Event type, such as catalog.product.created |
X-Ecomiq-Delivery | Delivery UUID |
X-Ecomiq-Timestamp | Attempt time in Unix seconds |
X-Ecomiq-Signature | sha256=<hmac>, only when a secret is configured |
Verify the signature
When you configure a secret, Ecomiq signs the raw body with HMAC-SHA256 and encodes the result as lowercase hexadecimal. Verify the signature before parsing JSON.
import { createHmac, timingSafeEqual } from "node:crypto"
export function isValidSignature(rawBody: string, header: string, secret: string) {
const expected = createHmac("sha256", secret).update(rawBody).digest("hex")
const received = header.replace(/^sha256=/, "")
const a = Buffer.from(expected, "hex")
const b = Buffer.from(received, "hex")
return a.length === b.length && timingSafeEqual(a, b)
}Additional authentication
You can configure credentials for Ecomiq to include on every request. You can also add fixed headers with headers.
| Type | Required configuration |
|---|---|
Basic | Username and Password |
Bearer | Token |
OAuth2 | Public HTTPS TokenUrl, ClientId, and ClientSecret |
Delivery and retries
Delivery is at least once. A failure can repeat the same event with the same X-Ecomiq-Delivery; process it idempotently and store that identifier only after your work completes.
A successful response resets the failure counter. After 10 consecutive failed attempts, Ecomiq pauses the endpoint. Fix the receiver, then call POST /v1/webhooks/endpoints/{id}/enable.
Re-enqueue a failed delivery with POST /v1/webhooks/deliveries/{id}/retry. A manual retry keeps the original body and creates a new X-Ecomiq-Delivery.
Available events
Catalog
| Event | Model |
|---|---|
catalog.product.created | ProductChangedWebhookModel |
catalog.product.updated | ProductChangedWebhookModel |
catalog.product.deleted | ProductDeletedWebhookModel |
catalog.product.status_changed | ProductStatusChangedWebhookModel |
catalog.stock.changed | StockChangedWebhookModel |
catalog.inventory.batch_processed | InventoryBatchProcessedWebhookModel |
catalog.price.changed | PriceChangedWebhookModel |
catalog.brand.created | BrandWebhookModel |
catalog.brand.updated | BrandWebhookModel |
catalog.brand.deleted | BrandDeletedWebhookModel |
catalog.category.created | CategoryWebhookModel |
catalog.category.updated | CategoryWebhookModel |
catalog.category.deleted | CategoryDeletedWebhookModel |
catalog.collection.created | CollectionWebhookModel |
catalog.collection.updated | CollectionWebhookModel |
catalog.collection.deleted | CollectionDeletedWebhookModel |
Channels
| Event | Model |
|---|---|
channel.listing.published | ListingPublishedWebhookModel |
channel.listing.price_changed | ListingPriceChangedWebhookModel |
channel.created | ChannelCreatedWebhookModel |
channel.sales.enabled | ChannelStateWebhookModel |
channel.deleted | ChannelDeletedWebhookModel |
Customers
| Event | Model |
|---|---|
customer.created | CustomerWebhookModel |
Carts
| Event | Model |
|---|---|
cart.created | CartCreatedWebhookModel |
cart.updated | CartUpdatedWebhookModel |
cart.completed | CartCompletedWebhookModel |
cart.item_added | CartItemWebhookModel |
cart.item_removed | CartItemWebhookModel |
Orders
| Event | Model |
|---|---|
order.created | OrderWebhookModel |
order.confirmed | OrderWebhookModel |
order.invoiced | OrderWebhookModel |
order.in_progress | OrderWebhookModel |
order.ready_to_pick_up | OrderWebhookModel |
order.in_transit | OrderWebhookModel |
order.delivered | OrderWebhookModel |
order.cancelled | OrderWebhookModel |
order.returned | OrderWebhookModel |
order.closed | OrderWebhookModel |
order.invalidated | OrderWebhookModel |
Payload fields
Field names use PascalCase. Resource identifiers are serialized as opaque strings: store them, compare them for exact equality, and send them back without interpreting their value. Do not convert them to numbers, perform arithmetic on them, or infer information from their value.
ProductChangedWebhookModel
Events: catalog.product.created · catalog.product.updated
| Field | JSON type | Nullable | Description |
|---|---|---|---|
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
SellerId | string | Yes | Seller identifier when the product belongs to a seller |
ProductId | string | No | Product identifier |
Product | object | No | Product payload |
OccurredOn | string (date-time) | No | Event occurrence date |
TargetChannelIds | string[] | No | Channels targeted by the product event |
ChangeType | string | No | Whether the change was created or updated |
ProductDeletedWebhookModel
Events: catalog.product.deleted
| Field | JSON type | Nullable | Description |
|---|---|---|---|
ProductId | string | No | Product identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
SellerId | string | No | Seller identifier |
OccurredOn | string (date-time) | No | Event occurrence date |
ProductStatusChangedWebhookModel
Events: catalog.product.status_changed
| Field | JSON type | Nullable | Description |
|---|---|---|---|
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
ProductId | string | No | Product identifier |
SellerId | string | No | Seller identifier |
IsActive | boolean | No | Whether the product is active |
OccurredOn | string (date-time) | No | Event occurrence date |
StockChangedWebhookModel
Events: catalog.stock.changed
| Field | JSON type | Nullable | Description |
|---|---|---|---|
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
ProductId | string | No | Product identifier |
VariantId | string | No | Variant identifier |
LocationId | string | No | Location identifier |
AvailableQuantity | integer | No | Available stock quantity |
OccurredOn | string (date-time) | No | Event occurrence date |
InventoryBatchProcessedWebhookModel
Events: catalog.inventory.batch_processed
| Field | JSON type | Nullable | Description |
|---|---|---|---|
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
Mode | string | No | Batch adjustment mode |
AdjustmentsCount | integer | No | Number of adjusted inventory lines |
ProductsCount | integer | No | Number of affected products |
VariantsCount | integer | No | Number of affected variants |
LocationsCount | integer | No | Number of affected locations |
Lines | array | No | Adjusted inventory lines |
OccurredOn | string (date-time) | No | Event occurrence date |
PriceChangedWebhookModel
Events: catalog.price.changed
| Field | JSON type | Nullable | Description |
|---|---|---|---|
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
ProductId | string | No | Product identifier |
SellerId | string | No | Seller identifier |
Price | number (decimal) | No | Current product price |
CompareAtPrice | number (decimal) | Yes | Original comparison price |
Currency | string | No | Currency code |
OccurredOn | string (date-time) | No | Event occurrence date |
BrandWebhookModel
Events: catalog.brand.created · catalog.brand.updated
| Field | JSON type | Nullable | Description |
|---|---|---|---|
BrandId | string | No | Brand identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
Name | string | No | Brand name |
Slug | string | No | Brand slug |
IsActive | boolean | Yes | Whether the brand is active |
OccurredOn | string (date-time) | No | Event occurrence date |
BrandDeletedWebhookModel
Events: catalog.brand.deleted
| Field | JSON type | Nullable | Description |
|---|---|---|---|
BrandId | string | No | Brand identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
OccurredOn | string (date-time) | No | Event occurrence date |
CategoryWebhookModel
Events: catalog.category.created · catalog.category.updated
| Field | JSON type | Nullable | Description |
|---|---|---|---|
CategoryId | string | No | Category identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
Name | string | No | Category name |
Slug | string | No | Category slug |
ParentId | string | Yes | Parent category identifier |
IsActive | boolean | Yes | Whether the category is active |
OccurredOn | string (date-time) | No | Event occurrence date |
CategoryDeletedWebhookModel
Events: catalog.category.deleted
| Field | JSON type | Nullable | Description |
|---|---|---|---|
CategoryId | string | No | Category identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
OccurredOn | string (date-time) | No | Event occurrence date |
CollectionWebhookModel
Events: catalog.collection.created · catalog.collection.updated
| Field | JSON type | Nullable | Description |
|---|---|---|---|
CollectionId | string | No | Collection identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
Name | string | No | Collection name |
Slug | string | No | Collection slug |
Description | string | Yes | Collection description |
Status | string | No | Collection status |
Type | string | No | Collection type |
DynamicOperator | string | No | Dynamic rule operator |
ValidFrom | string (date-time) | Yes | Collection start date |
ValidTo | string (date-time) | Yes | Collection end date |
ImageUrl | string | Yes | Collection image URL |
OccurredOn | string (date-time) | No | Event occurrence date |
CollectionDeletedWebhookModel
Events: catalog.collection.deleted
| Field | JSON type | Nullable | Description |
|---|---|---|---|
CollectionId | string | No | Collection identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
OccurredOn | string (date-time) | No | Event occurrence date |
ListingPublishedWebhookModel
Events: channel.listing.published
| Field | JSON type | Nullable | Description |
|---|---|---|---|
ListingId | string | No | Listing identifier |
ChannelId | string | No | Channel identifier |
ProductId | string | No | Product identifier |
ExternalId | string | No | External listing identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
SellerId | string | No | Seller identifier |
OccurredOn | string (date-time) | No | Event occurrence date |
ListingPriceChangedWebhookModel
Events: channel.listing.price_changed
| Field | JSON type | Nullable | Description |
|---|---|---|---|
ListingId | string | No | Listing identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
SellerId | string | No | Seller identifier |
ChannelId | string | No | Channel identifier |
ProductId | string | No | Product identifier |
ExternalId | string | Yes | External listing identifier |
OldPrice | number (decimal) | No | Previous listing price |
NewPrice | number (decimal) | No | New listing price |
OccurredOn | string (date-time) | No | Event occurrence date |
ChannelCreatedWebhookModel
Events: channel.created
| Field | JSON type | Nullable | Description |
|---|---|---|---|
ChannelId | string | No | Channel identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
SellerId | string | Yes | Seller identifier |
Name | string | No | Channel name |
IntegrationId | string | No | Provider identifier |
ChannelType | string | No | Channel type |
IsPrincipal | boolean | No | Whether the channel is the principal channel |
OccurredOn | string (date-time) | No | Event occurrence date |
ChannelStateWebhookModel
Events: channel.sales.enabled
| Field | JSON type | Nullable | Description |
|---|---|---|---|
ChannelId | string | No | Channel identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
SellerId | string | Yes | Seller identifier |
OccurredOn | string (date-time) | No | Event occurrence date |
ChannelDeletedWebhookModel
Events: channel.deleted
| Field | JSON type | Nullable | Description |
|---|---|---|---|
ChannelId | string | No | Channel identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
SellerId | string | Yes | Seller identifier |
ConnectionId | string | No | Connection identifier |
ProviderId | string | No | Provider identifier |
OccurredOn | string (date-time) | No | Event occurrence date |
CustomerWebhookModel
Events: customer.created
| Field | JSON type | Nullable | Description |
|---|---|---|---|
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
CustomerId | string | No | Customer identifier |
Email | string | Yes | Customer email |
FirstName | string | Yes | Customer first name |
LastName | string | Yes | Customer last name |
CustomerType | string | No | Customer type |
CustomerStatus | string | No | Customer status |
OccurredOn | string (date-time) | No | Event occurrence date |
CartCreatedWebhookModel
Events: cart.created
| Field | JSON type | Nullable | Description |
|---|---|---|---|
CartId | string | No | Cart identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
CurrencyCode | string | No | Currency code |
CreatedBy | string (uuid) | No | Actor identifier |
OccurredOn | string (date-time) | No | Event occurrence date |
CartUpdatedWebhookModel
Events: cart.updated
| Field | JSON type | Nullable | Description |
|---|---|---|---|
CartId | string | No | Cart identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
UpdatedBy | string (uuid) | No | Actor identifier |
OccurredOn | string (date-time) | No | Event occurrence date |
CartCompletedWebhookModel
Events: cart.completed
| Field | JSON type | Nullable | Description |
|---|---|---|---|
CartId | string | No | Cart identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
TotalAmount | number (decimal) | No | Cart total amount |
CurrencyCode | string | No | Currency code |
CompletedBy | string (uuid) | No | Actor identifier |
OccurredOn | string (date-time) | No | Event occurrence date |
CartItemWebhookModel — cart.item_added
Events: cart.item_added
| Field | JSON type | Nullable | Description |
|---|---|---|---|
CartId | string | No | Cart identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
CartItemId | string | No | Cart item identifier |
ProductId | string | No | Product identifier |
VariantId | string | No | Variant identifier |
Quantity | integer | No | Quantity added |
UnitPrice | number (decimal) | No | Unit price |
ActorId | string (uuid) | Yes | Actor identifier |
OccurredOn | string (date-time) | No | Event occurrence date |
CartItemWebhookModel — cart.item_removed
Events: cart.item_removed
| Field | JSON type | Nullable | Description |
|---|---|---|---|
CartId | string | No | Cart identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
CartItemId | string | No | Cart item identifier |
ProductId | string | No | Product identifier |
VariantId | string | No | Variant identifier |
Quantity | integer | Yes | Always null when an item is removed |
UnitPrice | number (decimal) | Yes | Always null when an item is removed |
ActorId | string (uuid) | Yes | Actor identifier |
OccurredOn | string (date-time) | No | Event occurrence date |
OrderWebhookModel
Events: order.created · order.confirmed · order.invoiced · order.in_progress · order.ready_to_pick_up · order.in_transit · order.delivered · order.cancelled · order.returned · order.closed · order.invalidated
| Field | JSON type | Nullable | Description |
|---|---|---|---|
OrderId | string | No | Order identifier |
TenantId | string | No | Tenant identifier |
StoreId | string | No | Store identifier |
SellerId | string | Yes | Seller identifier when the order belongs to a seller |
CustomerId | string | Yes | Customer identifier |
OrderNumber | string | No | Order number |
OrderGroup | string | Yes | Order group identifier |
State | string | No | Current order lifecycle state |
FulfillmentStatus | string | No | Current fulfillment status |
ReturnStatus | string | No | Current return status |
CurrencyCode | string | No | Currency code |
SubTotal | number (decimal) | No | Order subtotal |
Shipping | number (decimal) | No | Shipping amount |
Tax | number (decimal) | No | Tax amount |
Total | number (decimal) | No | Order total |
Email | string | Yes | Customer email |
FirstName | string | Yes | Customer first name |
LastName | string | Yes | Customer last name |
CancelReason | string | Yes | Cancellation reason |
TrackingCode | string | Yes | Tracking number |
TrackingUrl | string | Yes | Tracking URL |
CarrierName | string | Yes | Carrier name |
OrderDate | string (date-time) | No | Order date |
OccurredOn | string (date-time) | No | Event occurrence date |