Event and data reference
Every event the Conviro_AiChat module sends to Conviro, what triggers it, when it is not sent, every field, how it is retried, and what Conviro does with it. Plan §21, and §20 items 8–10 (catalog data, order data, event reference).
Sources: the module's observers (AiChat/Observer/*, AiChat/etc/events.xml), AiChat/Service/Event/*, AiChat/Service/Inventory/*, AiChat/Service/Sync/*; the backend's apps/api/src/modules/magento/magento-event-ingest.service.ts and magento-commerce-sync.service.ts. Read 2026-09-24 on feat/magento-10-10 (d3472ce2); updated 2026-09-25 for revenue attribution (514d99b0, f9e583f6) and release gate R3 (ceaaac4d API, 8791cdc4 module: customer.deleted, the trimmed order.placed, payment assist, the cart link). Behaviour marked measured was measured on the local Magento 2.4.7-p4 test stack only (see Supported versions).
Contents: Delivery model · Envelope · Common rules · Retry and dead letter · Idempotency · Catalog events · Inventory · Customer events · Order events · Cart and payment · Operational events · Identity events (1.5.0) · Plan §21 coverage · Conviro → Magento webhooks · Catalog data reference · Order data reference
Delivery model
- A Magento observer (or a sync job) builds an envelope and inserts one row into the local table
conviro_event_outbox— inside Magento's own database transaction where there is one. No network call happens in the storefront, checkout or admin request. - The cron job
conviro_deliver_events(every minute, one process at a time via a lock) claims up to 50 due rows at a time for up to 50 seconds and POSTs each one tohttps://api.conviro.io/v1/api/magento/events, signed. - Conviro answers; the module's
RetryPolicydecides what happens to the row.
Details (signature format, claim tokens, locks) are in Architecture.
Envelope
Built by Service/Event/EventFactory.php (ENVELOPE_VERSION = 1):
{
"id": "3f0c6a8e-6f1e-4c2e-9d61-0f4c1c9e2b7a",
"type": "order.placed",
"version": 1,
"occurredAt": "2026-09-24T10:15:02Z",
"store": {
"baseUrl": "https://shop.example.com",
"websiteId": 1,
"storeId": 1,
"storeCode": "default",
"locale": "en_US",
"currency": "EUR"
},
"subject": { "type": "order", "id": "000000123" },
"data": { "...": "per event type, below" },
"meta": { "extensionVersion": "1.4.0", "magentoVersion": "2.4.7-p4" }
}
| Field | Type | Meaning |
|---|---|---|
id | string (UUID) | Event id from Magento's IdentityGeneratorInterface. The idempotency key. Must equal the X-Conviro-Event-Id header (else HTTP 400). |
type | string | Event type, ^[a-z][a-z_]*(\.[a-z_]+)+$, 3–64 chars. |
version | int | Envelope version, 1. |
occurredAt | string | UTC, ISO 8601, second precision — when the envelope was built (queue time, not delivery time). |
store.baseUrl | string | Base URL of the store view, no trailing slash. |
store.websiteId / store.storeId | int | Magento website / store view id. Store 0 (admin scope) is reported as the default store view. |
store.storeCode | string | Store view code. |
store.locale | string | general/locale/code of the store view. |
store.currency | string | The store view's current (display) currency code. |
subject.type / subject.id | string | What the event is about: product/SKU, order/order number (increment id), customer/customer id, cart/quote id, sync_job/job id, store/test. |
data | object | Per type, below. |
meta.extensionVersion | string | setup_version of Conviro_AiChat from etc/module.xml. |
meta.magentoVersion | string | Magento's product version. |
If the store context cannot be read, store holds only storeId (or is empty).
Common rules for every event
An event is not queued when any of these holds (EventPublisher::allows):
- the module is disabled for that store view (
conviro_aichat/general/enabled), or - no Conviro API key is saved (never connected, or disconnected), or
- the event is store-scoped — type starts with
order.,customer.,cart.,checkout.orpayment.— and its store view is not selected in the setup wizard (step 4). Product and inventory events are catalog-wide and are never filtered by the store selection;customer.deletedis never held back by it either (the customer's orders may come from another, selected store view).
Each type below adds its own switch (a Sync Settings toggle). Toggles are read for the store view of the event, so a website-level override works (Configuration reference).
EventPublisher::publish() never throws: if the INSERT fails, the event is lost and a warning is logged ([Conviro] Could not queue …) — a failed sync event never fails a checkout, registration or product save.
Retry and dead letter
Service/Event/RetryPolicy.php, same for every type:
| Conviro's answer | Outbox status | What happens next |
|---|---|---|
| 2xx | sent | Done. Kept 7 days (health numbers), then purged. |
| no response (DNS, TLS, timeout), 408, 409, 425, 429, 5xx | pending with a later next_attempt_at | Retried after +30 s, +2 min, +10 min, +30 min, +2 h, +8 h (a larger Retry-After, up to 1 h, is honoured). After the 7th failed attempt: dead. |
| 401, 403 | blocked | Not retried. The rest of the claimed batch is held back too. Saving the Conviro configuration (or any successful re-registration) puts blocked events back to pending. |
| any other 4xx (e.g. 400 invalid envelope) | dead | Not retried; the same bytes cannot succeed. Retry failed in the admin re-queues it by hand. |
The back-off adds up to 38,550 s (about 10.7 hours) plus up to a minute of cron granularity per attempt. dead, dropped and repaired rows are purged 30 days after they were queued (conviro_purge_outbox, daily 03:23). Repair failed items (a sync job) re-queues or rebuilds dead and dropped events — see Data sync.
Ordering: the worker claims rows in id order, so events usually arrive in queue order, but a retried event can arrive after later ones. Conviro's order store applies state, status and totals only from an event at least as new (occurredAt) as the last one applied.
Idempotency
- Module: the envelope is stored once and sent byte for byte; a retry reuses the same
id. A claimed batch that a crashed worker never finished becomes claimable again after 300 s. - Conviro:
MagentoEventIngestServicekeepsmag:evt:<tenant>:<event id>in Redis: an id alreadydone(kept 7 days) answers 2xxduplicatewith no side effect; an id another replica is processing (claim kept 5 minutes) answers 409, which the module retries. A handler that throws releases the claim and answers 5xx. For the event types whose handlers act on the world —order.placed(order attribution, cart-recovery cancellation),cart.updated(recovery scheduling),customer.created(lead),checkout.started(checkout attribution) andpayment.failed(the payment-assist offer) — the claim is a database row (magento_event_receipts, migration 0227; security review R3): it holds with Redis down, so a redelivery is still a duplicate; a database error answers 5xx and the module retries. Receipts are kept 7 days. Every other type keeps the Redis gate, which fails open without Redis; those handlers are idempotent by key (KB upsert by SKU, commerce tables upserted by the store's ids). - Coalesced types:
inventory.updated(per SKU) andsync.job.progress(per job) replace a still-pendingrow's payload instead of queuing another; the row gets a new envelope id. A row a worker already claimed is left alone and a new row is added.
Catalog events
Switch: Sync Settings › Sync Products to KB (conviro_aichat/sync/product_sync, default off; the setup wizard's step 5 Catalog box switches it on). Read for the product's store (admin saves = store 0 = default).
product.created / product.updated
| Trigger | catalog_product_save_after (Observer/ProductSave.php): product.created for a new product, else product.updated. Also sent by Re-sync catalog and the catalog step of the initial sync (data.resync = true), and by Repair failed items. |
| Not sent | Product sync off; product without SKU; payload build failed (the save still succeeds). Changes that do not save the product model — Magento's catalog import and the Update attributes mass action — do not dispatch catalog_product_save_after (Magento core behaviour; not tested here): run Re-sync catalog afterwards. |
| Subject | product / SKU |
| Store scope | Catalog-wide; the envelope names the store of the save. Never filtered by store selection. Re-sync builds payloads for the default store view only. |
| Conviro effect | Upserts the SKU's knowledge-base entry (one per SKU, text capped at 6,000 chars; see Catalog data reference) and drops cached live reads for the SKU in every store view. A re-sync (resync: true) whose KB text did not change skips the cache scan. enabled: false deactivates the SKU's KB entry at once. |
| AI context | Yes — the KB entry is a KNOWLEDGE_BASE source. Price and stock the assistant states come from live store reads, which win over the KB (Trust layer). |
| Analytics | Counted in the store's event stats (last event, events today) and the ingest SLIs. |
data for an enabled product (Service/Event/ProductPayloadBuilder.php):
| Field | Type | PII class | Meaning |
|---|---|---|---|
sku | string | public catalog | SKU. |
enabled | bool | public catalog | true. |
typeId | string | public catalog | Magento product type (simple, configurable, …). |
name | string | public catalog | Product name (store of the save). |
price | number | public catalog | The price attribute, base currency, not the storefront final price. |
stockQty | number | merchant confidential | Legacy stock item quantity. On an MSI store this is the default source's count, not the salable quantity. |
isInStock | bool | public catalog | Legacy stock item flag. |
shortDescription | string | public catalog | strip_tags of the short description. |
description | string | public catalog | strip_tags of the description. |
urlKey | string | public catalog | URL key. |
productUrl | string | public catalog | Storefront URL, with utm_source/utm_medium/utm_campaign appended when Tag product links with UTM is on (source defaults to the store code). |
attributes | array of {label, value} | public catalog | Up to 30 attributes that are visible on the storefront or used in listings, with labels and option values resolved by Magento's source models; values ≤ 200 chars; system, media, price, SEO, cost, manufacturer, country_of_manufacture and date attributes excluded. |
isSalable | bool | public catalog | MSI salability. Present only when MSI answered. |
salableQty | number | null | merchant confidential | MSI salable quantity (null for composite types). Present only with isSalable. |
stockSource | "msi" | — | Present only with isSalable. |
children | array | public catalog | Configurable products only: up to 50 children {sku (≤64), options {label: value} (≤6, label ≤40, value ≤60), salable (bool|null), price (number|null)}. |
resync | bool | — | true when sent by a re-sync job. |
data for a disabled product: { "sku": "…", "enabled": false } only.
product.deleted
| Trigger | catalog_product_delete_after (Observer/ProductDelete.php); also Repair failed items when a failed product event's SKU no longer exists. |
| Not sent | Product sync off for the product's store; product without SKU. |
| Subject | product / SKU. Envelope store = default store view. |
data | sku (string), entityId (int, the product id). A repair-built delete carries sku only. |
| Conviro effect | Deactivates the SKU's KB entry and drops cached live reads. |
| AI context | Yes — the assistant stops offering the product. |
inventory.updated
| Trigger | (a) cataloginventory_stock_item_save_after when quantity, stock status, backorders or manage-stock changed; (b) an MSI source-item save (plugin on SourceItemsSaveInterface: admin sources, MSI REST API, MSI imports); (c) Magento's catalog import — SKUs of stock rows are remembered per bunch and queued on catalog_product_import_finish_before; (d) Re-sync inventory and the initial sync (resync: true); (e) Repair failed items. |
| Not sent | Product sync off at default scope (this event is not per website); storefront orders (reservations never emit); the stock item save that happens inside a product save (the product event carries the stock — one product save = one event, measured); an MSI source save that happens after an admin product save is still sent with the fresh salable quantity. |
| Coalescing | Per SKU: while an inventory.updated for the SKU waits in the outbox, a newer change replaces its payload. |
| Subject | product / SKU. Envelope store = default store view. |
| Conviro effect | Rewrites only the "In Stock" line of the SKU's active KB entry and drops cached live reads (a re-sync whose line did not change skips the cache scan). |
| AI context | Yes (KB stock line). Live stock reads remain authoritative. |
data (Service/Inventory/InventoryPayloadBuilder.php):
| Field | Type | PII class | Meaning |
|---|---|---|---|
sku | string | public catalog | SKU. |
isInStock | bool | public catalog | Legacy stock item flag. Absent when the product has no stock item. |
qty | number | merchant confidential | Legacy stock item quantity. Absent without a stock item. |
backordersAllowed | bool | merchant confidential | Backorders setting > 0. Absent without a stock item. |
manageStock | bool | merchant confidential | Manage Stock. Absent without a stock item. |
isSalable | bool | public catalog | MSI salability (only when MSI answered). |
salableQty | number | null | merchant confidential | MSI salable quantity (only with isSalable). |
stockSource | "msi" | "legacy" | — | Which stock system answered. |
resync | bool | — | Present (true) when sent by a re-sync. |
Customer events
customer.created
| Trigger | customer_register_success (Observer/CustomerRegister.php), which Magento's storefront registration controller dispatches. Accounts created in the admin, through the APIs or by import are not expected to dispatch it (not tested here). |
| Not sent | Create Leads on Registration off (conviro_aichat/sync/customer_lead, default on); store view not selected. |
| Subject | customer / customer id |
| Conviro effect | Creates a lead (leads: name = first + last name, e-mail, customFields.source = magento, Magento customer id) unless the workspace already has a lead with that e-mail; and upserts a commerce_customers row (source = live, e-mail stored only as an HMAC hash). Leads appear in the Conviro Leads inbox and in the unanswered-lead monitors. |
| AI context | No. |
| Field | Type | PII class | Meaning |
|---|---|---|---|
customerId | int | customer PII (identifier) | Magento customer id. |
email | string | customer PII | Account e-mail. |
firstName / lastName | string | customer PII | Name. |
groupId | int | merchant confidential | Customer group. |
customer.updated
| Trigger | customer_save_after_data_object (Observer/CustomerUpdated.php) — every save through the customer repository: admin customer form, the shopper's account edit, REST, imports that use the repository. |
| Not sent | A new customer (that is customer.created); a save that changed neither the e-mail (compared trimmed, case-insensitive) nor the customer group — a name, password or address change sends nothing; customer data not shared (Create Leads on Registration off for the website and the customer import not chosen in wizard step 5); store view not selected. Coalesced: saves before delivery stay one event with the latest values. |
| Subject | customer / customer id |
| Conviro effect | Upserts the commerce_customers row only (e-mail as the keyed hash). No lead, notification, automation or flow. |
| AI context | No. |
| Field | Type | PII class | Meaning |
|---|---|---|---|
customerId | int | customer PII (identifier) | Customer id. |
email | string | customer PII | Hashed by Conviro on arrival, never stored raw. |
groupId | int | merchant confidential | Customer group. |
Measured (attribution.sh, c3374a58): not on creation, not for a name change, one event for a group change, nothing while customer data is not shared or for an unselected store view, delivered signed without a name.
customer.deleted
| Trigger | customer_delete_after (Observer/CustomerDeleted.php) — the customer model's delete event, inside the deletion's database transaction, for every path that deletes a customer (admin form and mass delete, REST / GraphQL through the repository). A deletion that rolls back queues nothing. |
| Also, locally | Before queuing, the module drops this customer's undelivered outbox rows (pending, blocked, dead): its own customer.* events, and every order.*, cart.updated, checkout.started and payment.failed whose payload carries its e-mail. They become dropped with the payload emptied; a row a worker is sending at that moment is delivered and then removed at Conviro by the deletion. |
| Not sent | No customer data of the store view is shared with Conviro at all (Send Order Events, Create Leads on Registration, the customer import, confirmed cart recovery and confirmed payment assist all off) — a deletion is never the first time Conviro hears of a customer. Not held back by the store-view selection. |
| Subject | customer / customer id |
| Conviro effect | MagentoCustomerDeletionService, one transaction: deletes the commerce_customers row of (this store, customer id); removes the e-mail hash from this store's commerce_orders, commerce_checkouts and commerce_payment_failures rows with that hash (a waiting payment-assist offer is withdrawn); anonymises the lead this store's customer.created made for that e-mail (name, e-mail, phone, company, job title, notes, city, country cleared; the row stays with custom_fields.anonymizedBecause = magento.customer.deleted; a lead of another Magento customer id or from a form is left alone); deletes the cart-recovery rows of that e-mail (an unsent recovery e-mail is never sent). Audit log magento.customer.deleted with the counts and the customer id — never the e-mail. Conversations and contacts are not touched: use Conviro's GDPR tools for those (see Privacy). |
| Field | Type | PII class | Meaning |
|---|---|---|---|
customerId | int | customer PII (identifier) | The deleted customer's id. |
email | string | customer PII | Used in memory to find the hashed rows; never stored. Omitted when the account had none. |
Measured on the local stack (privacy-deletion.sh): the customer's own customer.updated and the order / cart / payment events carrying its e-mail dropped with empty payloads, another shopper's order left pending, one customer.deleted with exactly customerId and email, delivered signed; none while no customer data is shared; sent although the store view was not selected. The backend half is covered by apps/api payment-assist.pg.spec.ts on Postgres (what is deleted, nulled, anonymised and left alone).
customer.synced (historical)
| Trigger | The Import customers job — only when Customers is ticked in wizard step 5 (off by default). |
| Not sent | Customers not ticked (the job refuses to start and stops if unticked); store view not selected; customer without e-mail. Admin-created accounts (store 0) count as the default store view. |
| Subject | customer / customer id |
| Conviro effect | commerce_customers row only (source = historical). Never a lead, notification, automation, e-mail or flow. |
| Field | Type | PII class | Meaning |
|---|---|---|---|
customerId | int | customer PII (identifier) | Customer id. |
email | string | customer PII | Hashed by Conviro on arrival (HMAC-SHA256), never stored raw. |
groupId / websiteId | int | merchant confidential | Group, website. |
createdAt | string | customer PII | Account creation time (Magento UTC Y-m-d H:i:s). |
historical | bool | — | true. |
Order events
Switch: Sync Settings › Send Order Events (conviro_aichat/sync/order_events, default on), read for the order's store view. Store-scoped: nothing is sent for an order of an unselected store view.
Answers about orders in chat do not come from these events: the assistant reads the order live from Magento. The events keep Conviro's commerce_orders table current (history, and later revenue attribution) and stop cart recovery.
order.placed
| Trigger | sales_model_service_quote_submit_success — the order exists (1.3.x listened to the checkout success page instead). Measured with storefront checkout; Magento's admin order creation uses the same quote submission (core behaviour, not tested here). |
| Not sent | Toggle off; store view not selected. Orders created by code that bypasses Magento's quote submission (some importers) are not seen (not tested). |
| Conviro effect | Cancels any scheduled cart-recovery e-mail for the customer e-mail; upserts commerce_orders (source = live); computes the order's revenue attribution (commerce_orders.attribution, see Privacy). |
Exactly these fields — each one read by a Conviro consumer (release gate R3):
| Field | Type | PII class | Meaning / stored by Conviro? |
|---|---|---|---|
orderId | string | order PII (identifier) | Order number (increment id). Stored. |
entityId | int | order PII (identifier) | Order entity id. Stored. |
status / state | string | order PII | Stored. |
grandTotal | number | order PII | Order currency. Stored. |
currency | string | — | Order currency code. Stored. |
customerEmail | string | customer PII | Stored only as an HMAC hash; used raw once to cancel cart recovery and to match the order to a conversation's e-mail (attribution), in memory. |
isGuest | bool | order PII | Stored. |
itemCount | int | order PII | Visible items. Stored. |
createdAt | string | order PII | Order creation time (Magento UTC). Stored as placed_at. |
attribution | {visitorId?, linkIds?} | pseudonymous ids | Only for a storefront order (frontend / REST / GraphQL), with Revenue attribution on and, under Cookie Restriction Mode, the shopper's consent: the chat visitor id and up to five followed Conviro link ids from the shopper's first-party cookies. Used to match; the visitor id is not stored. |
Until 1.4.0 the event also carried the billing first / last name and phone, the line items (name, SKU, quantity, row total) and the customer id. No Conviro consumer read them (abandoned-cart cancellation, commerce_orders, attribution, live order lookup and leads were all checked), so they are no longer sent. Measured (privacy-deletion.sh): a real order's event carries exactly the ten fields above (no attribution from the CLI), delivered signed.
order.processing / order.completed / order.canceled / order.status_changed
| Trigger | sales_order_save_after on an existing order whose state or status changed (Observer/OrderStatusChange.php). A state change to processing, complete or canceled gives order.processing, order.completed, order.canceled; any other change (other states such as holded or closed, or a status-only change) gives order.status_changed. |
| Not sent | New orders (they are order.placed); saves that change neither state nor status; changes written without saving the order model. |
| Conviro effect | Upserts commerce_orders (state, status, total only from an event at least as new as the last applied). |
| Field | Type | PII class |
|---|---|---|
orderId, entityId | string, int | order PII (identifier) |
oldState, state, oldStatus, status | string | order PII |
customerEmail | string | customer PII (hashed by Conviro) |
grandTotal, currency | number, string | order PII |
isGuest | bool | order PII |
createdAt | string | order PII |
order.shipped
| Trigger | sales_order_shipment_save_after for a new shipment. |
| Not sent | A re-save of an existing shipment — so tracking numbers added to a shipment after it was created are not sent (the assistant still reads tracking live). |
| Conviro effect | Upserts commerce_orders (last event time). |
| Field | Type | PII class |
|---|---|---|
orderId, entityId | string, int | order PII (identifier) |
shipmentId | string | order PII |
tracking | array {carrier, title, number} | order PII |
items | array {sku, qty} | order PII |
customerEmail | string | customer PII (hashed by Conviro) |
order.refunded / order.partially_refunded
| Trigger | sales_order_creditmemo_save_after for a new credit memo. order.refunded when the order is closed or total refunded ≥ total paid; else order.partially_refunded. |
| Conviro effect | Upserts commerce_orders. |
| Field | Type | PII class |
|---|---|---|
orderId, entityId | string, int | order PII (identifier) |
creditmemoId | string | order PII |
refundAmount, totalRefunded, totalPaid | number | order PII |
currency | string | — |
customerEmail | string | customer PII (hashed by Conviro) |
order.synced (historical)
| Trigger | The order-history job: orders of the selected store views with created_at inside the window fixed when the job starts (30 / 90 / 365 days, chosen in wizard step 5; Re-sync recent orders uses 30 when "none" was chosen). Also Repair failed items rebuilds a failed order event as order.synced. |
| Not sent | Send Order Events off; window "none" at setup (no history job); store view not selected. |
| Conviro effect | commerce_orders row with source = historical. Nothing live happens: no cart-recovery cancellation, notification or flow. |
| Field | Type | PII class |
|---|---|---|
orderId, entityId | string, int | order PII (identifier) |
state, status | string | order PII |
grandTotal (4 decimals), currency | number, string | order PII |
itemCount | int | null | order PII |
customerEmail | string | customer PII — hashed by Conviro, never stored raw |
isGuest | bool | order PII |
createdAt, updatedAt | string | order PII |
historical | bool | true |
No names, addresses, phones or line items.
Cart and payment events
Both are off by default and need two things: the toggle on for the store's website and the merchant's confirmation at default scope (Cart recovery and payment assist). Upgrading from 1.3.x holds a toggle that was already on until it is confirmed.
cart.updated
| Trigger | checkout_cart_save_after (Magento's storefront cart model). |
| Not sent | Toggle off or not confirmed; store view not selected; the quote has no customer e-mail (in practice guests before checkout) or no items. Cart changes made through REST/GraphQL cart APIs do not dispatch this Magento event (Magento core behaviour; not tested here). |
| Subject | cart / quote id |
| Conviro effect | If the workspace's cartRecovery capability is on and the cart total is at least 1: schedules one recovery e-mail per cart 60 minutes after the last cart event (every cart event pushes it back), stores the cart in magento_abandoned_carts (with the store view, its locale and the cart link) and finds or creates a contact for the e-mail. A later order.placed for the same e-mail cancels it. The e-mail is in the store view's language (see Cart recovery). |
| Field | Type | PII class |
|---|---|---|
cartId | int | customer PII (identifier) |
email | string | customer PII |
itemCount | int | order PII |
grandTotal, currency | number, string | order PII |
items | up to 10 {name, sku, qty, price} | order PII |
cartUrl | string | — (the recovery e-mail's link: Cart page URL (headless stores) for the store view when set, else the store view's own checkout/cart URL, built locally, no HTTP) |
checkout.started
| Trigger | The predispatch of the checkout route itself (Observer/CheckoutStarted.php, etc/frontend/events.xml: checkout_index_index — Luma checkout, Hyvä Checkout and one-step checkouts that keep that route — and onestepcheckout_index_index). Server-side, no checkout JavaScript. A headless checkout that never requests these pages sends nothing. |
| Not sent | Behind the same switch as cart.updated (cart recovery on and confirmed); store view not selected; an empty cart; a reload of the same checkout state in the same session (a changed cart reaching the checkout again is sent again). |
| Subject | cart / quote id |
| Conviro effect | One commerce_checkouts row per quote (e-mail as the keyed hash), with whether a Conviro conversation or product link preceded it. Nothing is ever sent to the shopper because of this event. Rows are deleted after 90 days. |
| Field | Type | PII class |
|---|---|---|
quoteId | int | customer PII (identifier) |
itemCount | int | order PII |
grandTotal, currency | number, string | order PII |
email | string | customer PII — only when the shopper already gave it (logged in, or the e-mail step done) |
attribution | {visitorId?, linkIds?} | pseudonymous ids — as on order.placed |
Measured (attribution.sh, c3374a58): only with cart recovery on and confirmed, once per checkout state, no e-mail for a guest who gave none, not for an unselected store view; observer p50 0.89 / p95 1.18 ms.
payment.failed
| Trigger | sales_model_service_quote_submit_failure — order placement threw (replaces 1.3.x's CheckoutFailure hook, which the default checkout never called). |
| Not sent | Toggle off or not confirmed; store view not selected; a payment declined before the order submission is attempted (e.g. inside a payment provider's own browser widget) never reaches Magento's quote submission. Since 1.4.1 also not when the order was already saved: Magento raises the same failure event for an exception thrown after the order exists (e.g. by another module's success observer) — that is not a failed payment, and the chat must not offer payment help. |
| Subject | cart / quote id |
| Conviro effect | Payment assist (PaymentAssistService): one commerce_payment_failures row per event (method code, total, currency, the error text with e-mail addresses and digit runs of six or more masked, the e-mail only as the keyed hash; deleted after 90 days). When attribution.visitorId names a conversation of the workspace, staff see a "A payment failed at checkout" event on that conversation in the inbox, and — only while the workspace's paymentAssist capability is on (the module's switch and its confirmation, sent on registration) — the chat widget offers the shopper help once, on their next page view, in the widget's language. The offer expires 24 hours after the failure; switched off meanwhile, it is withdrawn. Without a visitor id Conviro only records the failure. See Payment assist. |
| Field | Type | PII class |
|---|---|---|
cartId | int | customer PII (identifier) |
email | string | customer PII (hashed by Conviro, never stored raw) |
paymentMethod | string | order PII (method code, never card data) |
grandTotal, currency | number, string | order PII |
error | string | order PII — exception message, tags stripped, whitespace collapsed, ≤ 300 chars |
attribution | {visitorId?, linkIds?} | pseudonymous ids — only from a storefront request, with Revenue attribution on and, under Cookie Restriction Mode, the shopper's consent |
Measured on the local stack (payment-assist.sh, the failure event dispatched as QuoteManagement::submitQuote() does, on a real quote): nothing while off or not confirmed; the six fields from an admin / CLI request, plus the visitor and link ids from a storefront REST request; no visitor id without consent under Cookie Restriction Mode or with Revenue attribution off; delivered signed. The backend half (one offer under five parallel page views, withdrawn, expired) is apps/api payment-assist.pg.spec.ts.
Operational events
sync.job.progress
| Trigger | Every sync job (catalog, inventory, orders, customers, repair): once per batch and on every state change. Coalesced per job. |
| Subject | sync_job / Magento job id |
| Conviro effect | Upserts commerce_sync_jobs (counters only grow; a completed/failed job is never reopened by a late "running"). Shown in the Conviro dashboard's Magento card. |
data: jobId (int), type (catalog\|inventory\|orders\|customers\|repair), status (queued\|running\|completed\|failed), processed, failed, skipped, total (int), cursor (string), createdAt, startedAt, finishedAt (string\|null), lastError (≤ 300 chars, e-mail addresses replaced by [email]), and for repair rebuilt, requeued; for orders windowDays. No customer data.
test.ping
Sent by Send test event (data: {source: "admin"}, subject store/test) and by wizard step 7 ({source: "setup-wizard"}, subject store/setup-verify). Conviro accepts and counts it.
Unknown types
Conviro answers 2xx ignored to a type it does not handle, so a newer module never dead-letters against an older backend.
Identity events (1.5.0, unreleased)
1.5.0 (unreleased), read 2026-09-27 on feat/magento-1.5.0-customer-hub. Sent only by a store that has switched signed customer identity on at least once (Signed customer identity and My orders); a store that never did has no bindings and sends nothing.
identity.bindings_revoked
| Trigger | The module revoked signed-session bindings (Service/Identity/BindingRevocation.php): customer_logout (every live binding of that Magento session — Observer/Identity/CustomerLogout.php, frontend area), customer_delete_after (every live binding of the customer — Observer/Identity/CustomerDeletedBindings.php; Phase 5: right after the event is queued, every binding row of that customer is deleted), a password change or reset (every live binding of the customer — Plugin/Identity/RevokeBindingsOnPasswordChange.php), and a bootstrap for customer B on a session that still had customer A's bindings (A's). |
| Not sent | Nothing was revoked (no live binding matched). The store has no installation id (identity never switched on). The common rules above: no API key, module disabled for the store view. Not held back by the store-view selection, and not a personal-data event (it carries no customer data), so it is queued even while delivery is blocked. |
| Batching | At most 100 binding ids per event; more are split over several events. |
| Subject | identity_binding / the first binding id of the batch |
| Conviro effect | Revokes the Verified Customer Principals minted with those binding ids — only inside the workspace of the API key that delivered the event (ids of another workspace match nothing); unknown ids are a no-op, malformed ids are dropped, at most 100 are read. Reason mapping: customer_logout → customer_logged_out, customer_deleted → customer_deleted, account_switched → account_switched, anything else (e.g. password_changed) → binding_revoked. Idempotency: a database receipt (like order.placed), so a redelivery with Redis down is still a duplicate. |
| Latency | the outbox cron (every minute). Until it arrives, the binding is already revoked in Magento, so the next order read is refused anyway (403 binding_revoked), and the widget has usually dropped the verification itself on sign-out. |
| Field | Type | PII class | Meaning |
|---|---|---|---|
bindingIds | string[] (UUIDv4, 1–100) | pseudonymous ids (random) | The revoked bindings. |
reason | string | none | customer_logout | customer_deleted | password_changed | account_switched. |
No customer id, e-mail or session id. Not yet measured end to end (the identity-bridge.sh scenario that delivers it signed through the real outbox needs the Docker stack); unit tests cover the module side (BindingRevocationTest, PasswordChangeRevocationTest) and the Conviro ingest.
Plan §21 coverage
Every type of plan §21's baseline is emitted by 1.4.0: customer.updated and checkout.started (f9e583f6, above) were the last two. customer.deleted (release gate R3) goes beyond it.
Conviro → Magento webhooks
Conviro also calls the store: POST https://<store>/conviro/webhook/receive (Controller/Webhook/Receive.php). Conviro subscribes the store to these event types when it registers: lead.captured, lead.created, contact.created, session.started, plus webhook.test (Conviro's "send test", data.test = true).
The store verifies X-Webhook-Signature: t=<unix>,v1=<hex> (HMAC-SHA256 of "<t>.<raw body>" with the webhook secret, ±300 s), checks data.tenantId against its own tenant id, accepts only the known types, and records the delivery once (replay key = SHA-256 of "<t>.<verified HMAC>" — canonical, so re-spelling a captured header does not make it new again; table conviro_inbound_event, purged hourly after a day). Answers: 401 bad/missing signature, 400 malformed, 403 tenant mismatch, 422 unsupported event, 200 once (200 duplicate for a replay), 500 if processing threw (the replay key is released so Conviro's retry can apply it).
The store records these events in conviro_webhook_log (event name, direction in, HTTP status, error code — no payload); a request that fails the signature check is only counted — one rejected row per reason and minute, its hits column saying how many (1.4.0) — so forged POSTs cannot flood the table. It dispatches the Magento event conviro_webhook_received with conviro_event and conviro_data, for the merchant's own code to act on. The module itself takes no other action.
Catalog data reference
What Conviro keeps per product (one knowledge-base entry per SKU, built by apps/api/src/modules/magento/magento-product-kb-content.ts for both the event path and the nightly REST pull):
Product · SKU · Price · In Stock · Variants · Specifications · Summary · Description · URL, capped at 6,000 characters.
- Price is the
priceattribute labelled with the store's base currency (or unlabelled when it cannot be established). There is no "Special Price" line. The price a shopper pays is answered live fromproducts-render-infoin the shopper's store view (final or regular, display currency). - In Stock comes from MSI salability when the store has MSI, else the legacy stock item.
- Variants and Specifications are the blocks the module rendered with Magento's attribute labels; a product event stores them with the entry, and the nightly REST pull reuses them for that product because the integration's REST permissions cannot resolve attribute labels.
- The KB is keyed by SKU, not by store view: text is the default store view's (re-sync) or the saving store's (events).
The optional nightly REST pull (02:00 server time, only for workspaces with KB sync enabled) lists products with visibility 2, 3 or 4 and deactivates KB entries of products no longer listed — only after a complete listing.
Order data reference
| Where | What |
|---|---|
| Live, per question | Order status, totals, items and tracking are read from Magento's REST API when a shopper asks, after the shopper gives the order number and checkout e-mail (capability Order status & tracking lookup). Nothing from these reads is written to Conviro's order tables; results are cached briefly (see Privacy). |
commerce_orders | One row per (workspace, integration, order number): store code and id, entity id, state, status, grand total, currency, item count, customer_email_hash + email_hash_key_id, is_guest, placed_at, source (live | historical, never changed after insert), attribution (reserved, NULL), last_event_at. No name, address, phone, line items or raw e-mail. |
commerce_customers | One row per (workspace, integration, Magento customer id): store code/id, website id, group id, email_hash + key id, account creation time, source, last event. |