Commerce events
Commerce publishes registered cross-pillar events over the internal event transport. This is an internal service contract, not a customer endpoint or direct Redis subscription interface.
Canonical envelope
{ "id": "<event-uuid>", "type": "commerce.order.fulfilled", "tenant_id": "<tenant-id>", "org_id": "<organization-id>", "causation_id": "<upstream-event-id>", "contact_hints": {"email": "buyer@example.com"}, "source": "commerce", "payload": {}, "created_at": "2026-07-10T12:00:00Z", "v": 1}| Field | Contract |
|---|---|
id | Event UUID; assigned at publish time when omitted |
type | Registered dotted type in {pillar}.{entity}.{action} form |
tenant_id | Backward-compatible tenant partition key |
org_id | Canonical analytics partition key; defaults to tenant_id for legacy publishers |
causation_id | Optional upstream request or event ID |
contact_hints | Optional CRM identity hints; resolution failure must not drop the event |
source | Publishing pillar or service |
payload | Event-specific JSON |
created_at | RFC 3339 timestamp; assigned at publish time when omitted |
v | Integer envelope version, currently 1 |
data, date-string version, trace_id, and attempt are not fields in the current shared event envelope.
Registered Commerce families
The executable registry includes these Commerce event groups:
| Group | Registered examples |
|---|---|
| Orders and payments | commerce.order.created, commerce.order.fulfilled, commerce.order.status_changed, commerce.order.completed, commerce.payment.received, commerce.refund.issued |
| Purchasing and costs | commerce.purchase_order.received, commerce.purchase_order.paid, commerce.purchase_order.updated, commerce.channel_fee.charged, commerce.shipping.charged, commerce.shipping.paid |
| Inventory and fulfillment | commerce.inventory.adjustment, commerce.inventory.changed, commerce.inventory.restocked, commerce.fulfillment.shipped, commerce.assembly.completed |
| Returns | commerce.return.requested, commerce.return.approved, commerce.return.received, commerce.return.restocked, commerce.return.refunded |
| Catalog and storefront | commerce.product.updated, commerce.brand.updated, commerce.storefront_form.submitted, commerce.customer.created |
| Integrations and messaging | commerce.sync.failed, commerce.channel_payout.received, commerce.contact_linkage.requested, commerce.message_send.requested |
| Reporting feeds | commerce.email_send.recorded, commerce.email_template.updated, commerce.email_flow.updated |
Publish-time validation rejects event types absent from shared/events/registry.go. Legacy unprefixed names are normalized only where the registry declares a legacy alias.
Reliability
New cross-domain publishers should append the canonical AuditCore event and enqueue the transport publication in the same PostgreSQL transaction. The shared outbox dispatcher:
- Claims due rows with
FOR UPDATE SKIP LOCKED. - Publishes them to Redis Streams.
- Records the Redis message ID.
- Retries failures with exponential backoff.
- Moves exhausted rows to
dead_letter.
Reliable consumers use an inbox key of (consumer_name, event_id). Processed duplicates are acknowledged without rerunning the handler; failed messages remain recoverable, and exhausted failures move to a dead-letter stream.
Some existing direct publishers remain during migration. The transactional outbox is the required pattern for new canonical cross-domain writes, not a guarantee that every historical publisher already uses it.
Versioning
The registry declares a minimum and maximum supported integer version per event type. If no range is specified, only version 1 is supported.
Within a version, changes must be backward compatible: add optional fields, preserve field meaning, and retain old fields until consumers are upgraded. Breaking changes require a new registry range plus dual-read or dual-publish handling. Reliable consumers reject unsupported versions into the dead-letter path.
There is no subscriber version-pinning negotiation in this transport contract.