Corteksa

Events

CRM — Events

Every record mutation emits an internal domain event (via EventEmitter2, from RecordEventsService). These are server-side — audit, real-time, workflow, and webhook listeners consume them. To receive them in your own app, subscribe to a webhook (the public record.* events map from these).

Record events

Names are defined in shared/events/data.events.ts:

EventFires whenKey payload fields
data.createdA record is createdobjectSlug, dataSlug, recordId, newData, enrichedRecord
data.updatedA record is updatedoldData, newData, changedRelations
data.deletedA record is deletedobjectSlug, dataSlug, recordId
data.bulk_deletedBulk deleteaffectedSlugs, affectedCount
data.bulk_updatedBulk updateaffectedSlugs, affectedCount
data.bulk_assignedBulk reassignaffectedSlugs, affectedCount

Every payload also carries tenant/workspace context (tenantDatabaseName, workspaceId), the actor (adminId, adminName), and a sourceType.

sourceType — who caused the write

manual (a human in the UI) · automation (a workflow) · system · api (an API key or OAuth token) · ai (the assistant). Credential-originated writes are auto-tagged api and stamped with sourceApiKeyId / sourceOauthClientId, so the webhook fanout can skip echoing an event back to the credential that made it (loop prevention).

oldData / newData carry only the changed fields — a listener treats an absent key as "unchanged" (the webhook tracked-fields logic relies on this). enrichedRecord is the fully-transformed row (same shape as the HTTP response) so the real-time listener can push a ready-to-render record without a refetch.

Consumers (server-side)

ListenerReacts toEffect
Audit changelogall data.* (unless cacheOnly / skipChangelog)Writes before/after changelog
DataEventsGateway (records/realtime/)all data.*Pushes the live record to the workspace room
Cache observersall data.*Invalidate record/list caches
Workflow enginedata.created / data.updatedFire record-created / field-changed triggers
Webhook fanoutdata.*record.*Deliver to subscribed external endpoints

Realtime (to the UI)

The DataEventsGateway broadcasts record changes over Socket.IO to the workspace/tenant room, so a list or board updates live. Subscribe there instead of polling GET /object/data/:objectSlug.

Besides the data.* events above, two events exist only on the socket. Both answer the same problem — a write on object A moves a value on object B, whose watchers nothing else would tell — and they differ in what they can carry:

EventFires whenCarries
data.lookup_staleA record that other objects mirror was writtenobjectSlug, staleFieldsslugs only
data.rollup_updatedA child write caused a rollup recompute to settleobjectSlug, records[{ slug, values }]values included

A lookup value is per-reader: it mirrors a field on another object, so it is resolved through the reader's own A/G/M/D rights there and cannot go to a shared room — the client re-reads the named slugs. A rollup is aggregated once and stored on the parent's own column, so every reader of that record gets the identical number from the HTTP read path; broadcasting it is parity with the read contract, and the client patches the cell with no refetch.

data.rollup_updated fires only for the child-changed path. A rollup back-fill (new field over existing data) rewrites every record and is deliberately silent.

Next

On this page