Event Payloads Reference¶
This document provides comprehensive documentation for all event payloads that trigger Functions in Centrali.
Table of Contents¶
- Overview
- Accessing Event Data
- Event Types
- Payload Structures
- record_created
- records_bulk_created
- record_updated
- record_deleted
- record_restored
- record_reverted
- Failed Events
- Event Differences Table
- Common Patterns
Overview¶
When a record event occurs in Centrali, the system publishes an event payload containing all relevant information about the change. If you have an event-driven trigger configured for that event type and collection, your Function will receive this payload.
Event Flow¶
Record Operation (create/update/delete)
↓
Event Published to NATS
↓
Orchestrator Matches Triggers
↓
Your Function Receives Event Payload
Accessing Event Data¶
Event data is available in your function through the executionParams global variable. Static configuration from your trigger is available in triggerParams.
executionParams vs triggerParams¶
| Variable | Contains | Set By |
|---|---|---|
executionParams | Dynamic event data | System (from the event) |
triggerParams | Static configuration | You (in trigger metadata) |
Example: Accessing Event Data¶
async function run() {
// Event data from the record operation
const event = executionParams.event; // "record_created"
const workspaceSlug = executionParams.workspaceSlug;
const recordSlug = executionParams.recordSlug; // Collection slug
const recordId = executionParams.recordId;
const data = executionParams.data; // Record data
const timestamp = executionParams.timestamp;
// Static config from trigger creation
const emailTemplate = triggerParams.emailTemplate;
const notifyAdmin = triggerParams.notifyAdmin;
api.log({
message: `Processing ${event}`,
recordId,
collection: recordSlug
});
return { success: true };
}
Event Types¶
Centrali supports the following record events:
Per-Record Events¶
These events fire once per record. They are published by single-record operations and by batch multi-record operations.
| Event | Description | Trigger Condition |
|---|---|---|
record_created | New record created | After successful record creation |
record_updated | Record modified | After successful record update |
record_deleted | Record deleted | After successful deletion (soft or hard) |
record_restored | Deleted record restored | After restoring a soft-deleted record |
record_expired | Record expired via TTL | When a record's time-to-live expires |
record_reverted | Record reverted to previous version | After version rollback |
Aggregate Events (Bulk)¶
These events fire once per bulk operation with all affected record IDs. They are published by bulk multi-record operations.
| Event | Description | Trigger Condition |
|---|---|---|
records_bulk_created | Multiple records created in bulk | After successful bulk creation |
records_bulk_updated | Multiple records updated in bulk | After successful bulk update |
records_bulk_deleted | Multiple records deleted in bulk | After successful bulk deletion |
Failure Events¶
| Event | Description | Trigger Condition |
|---|---|---|
record_created_failed | Record creation failed | When create operation fails |
record_updated_failed | Record update failed | When update operation fails |
record_deleted_failed | Record deletion failed | When delete operation fails |
records_bulk_created_failed | Bulk record creation failed | When bulk create operation fails |
records_bulk_updated_failed | Bulk record update failed | When bulk update operation fails |
records_bulk_deleted_failed | Bulk record deletion failed | When bulk delete operation fails |
Bulk vs Batch: Which Events Fire?¶
Centrali provides two distinct paths for multi-record operations. The path you choose determines which events fire:
| HTTP API | Compute Function API | Events | |
|---|---|---|---|
| Bulk (aggregate) | POST /records/bulk | api.bulkCreateRecords() | 1 aggregate event (e.g., records_bulk_created) |
| Batch (per-record) | POST /records/batch | api.batchCreateRecords() | N per-record events (e.g., record_created × N) |
- Use bulk when your trigger function processes all records together (indexing, batch notifications, summary reports)
- Use batch when your trigger function processes each record individually (validation, per-record notifications, thumbnail generation)
Payload Structures¶
record_created¶
Triggered when a new record is successfully created.
Payload Structure:
{
event: "record_created",
workspaceSlug: string, // Your workspace identifier
recordSlug: string, // Collection slug (e.g., "orders", "customers")
recordId: string, // UUID of the created record
data: {
id: string, // Record UUID
workspaceSlug: string, // Workspace identifier
recordSlug: string, // Collection slug
data: { // Your record fields
[fieldName: string]: any
},
status: "active", // Record status
version: 1, // Always 1 for new records
createdAt: string, // ISO 8601 timestamp
updatedAt: string, // ISO 8601 timestamp
createdBy: string, // User ID who created the record
updatedBy: string // User ID who created the record
},
timestamp: string, // ISO 8601 timestamp of the event
createdBy: string // User ID who created the record
}
Real Example:
// executionParams for a new order
{
event: "record_created",
workspaceSlug: "acme-corp",
recordSlug: "orders",
recordId: "550e8400-e29b-41d4-a716-446655440000",
data: {
id: "550e8400-e29b-41d4-a716-446655440000",
workspaceSlug: "acme-corp",
recordSlug: "orders",
data: {
orderNumber: "ORD-2025-001",
customerId: "cust_abc123",
items: [
{ productId: "prod_123", quantity: 2, price: 29.99 },
{ productId: "prod_456", quantity: 1, price: 49.99 }
],
status: "pending",
total: 109.97,
shippingAddress: {
street: "123 Main St",
city: "New York",
zip: "10001"
}
},
status: "active",
version: 1,
createdAt: "2025-01-15T10:30:00.000Z",
updatedAt: "2025-01-15T10:30:00.000Z",
createdBy: "user_xyz789",
updatedBy: "user_xyz789"
},
timestamp: "2025-01-15T10:30:00.123Z",
createdBy: "user_xyz789"
}
Function Example:
async function run() {
const { recordId, data, recordSlug } = executionParams;
// Access record fields
const orderNumber = data.data.orderNumber;
const customerId = data.data.customerId;
const total = data.data.total;
api.log({
message: "New order received",
orderNumber,
total
});
// Send notification
await api.http.post("https://api.example.com/notify", {
type: "new_order",
orderId: recordId,
orderNumber,
total
});
return { success: true, orderNumber };
}
records_bulk_created¶
Triggered when multiple records are created in a single bulk operation. Unlike record_created, this event contains only record IDs, not the full record data. This is for performance reasons - bulk operations can create thousands of records.
Payload Structure:
{
event: "records_bulk_created",
workspaceSlug: string, // Your workspace identifier
recordSlug: string, // Collection slug (e.g., "orders", "customers")
structureId: string, // UUID of the collection
recordIds: string[], // Array of created record UUIDs
count: number, // Number of records created
timestamp: string, // ISO 8601 timestamp of the event
createdBy: string, // User ID who created the records
schemaDiscoveryMode: string // "strict" | "schemaless" | "auto-evolving"
}
Real Example:
// executionParams for bulk order creation
{
event: "records_bulk_created",
workspaceSlug: "acme-corp",
recordSlug: "orders",
structureId: "123e4567-e89b-12d3-a456-426614174000",
recordIds: [
"550e8400-e29b-41d4-a716-446655440001",
"550e8400-e29b-41d4-a716-446655440002",
"550e8400-e29b-41d4-a716-446655440003"
],
count: 3,
timestamp: "2025-01-15T10:30:00.123Z",
createdBy: "user_xyz789",
schemaDiscoveryMode: "strict"
}
Function Example:
async function run() {
const { recordIds, count, recordSlug, workspaceSlug } = executionParams;
api.log({
message: "Bulk records created",
collection: recordSlug,
count,
recordIds: recordIds.slice(0, 5) // Log first 5 IDs
});
// Optionally fetch full records if needed
// Note: For large batches, consider processing asynchronously
if (count <= 100) {
const records = await api.centrali.getRecordsByIds(recordSlug, recordIds);
// Process records...
}
// Send summary notification
await api.http.post("https://api.example.com/notify", {
type: "bulk_import_complete",
collection: recordSlug,
count,
timestamp: executionParams.timestamp
});
return { success: true, processedCount: count };
}
Important Notes: - Bulk events do NOT contain full record data - only IDs - Use api.centrali.getRecordsByIds() if you need full record data - Consider batch processing for large imports to avoid timeouts - Single record_created events are NOT fired for bulk operations
records_bulk_updated¶
Triggered when multiple records are updated in a single bulk operation. Contains only the affected record IDs, not full record data.
Payload Structure:
{
event: "records_bulk_updated",
workspaceSlug: string,
recordSlug: string,
structureId: string,
recordIds: string[], // Array of updated record UUIDs
count: number, // Number of records updated
timestamp: string,
updatedBy: string
}
Function Example:
async function run() {
const { recordIds, count, recordSlug } = executionParams;
api.log({
message: "Bulk records updated",
collection: recordSlug,
count
});
// Fetch updated records if needed
if (count <= 100) {
for (const id of recordIds) {
const record = await api.fetchRecord(id);
// Process each updated record...
}
}
return { success: true, processedCount: count };
}
records_bulk_deleted¶
Triggered when multiple records are deleted in a single bulk operation. Contains only the deleted record IDs.
Payload Structure:
{
event: "records_bulk_deleted",
workspaceSlug: string,
recordSlug: string,
structureId: string,
recordIds: string[], // Array of deleted record UUIDs
count: number, // Number of records deleted
timestamp: string,
deletedBy: string
}
Function Example:
async function run() {
const { recordIds, count, recordSlug } = executionParams;
api.log({
message: "Bulk records deleted",
collection: recordSlug,
count
});
// Clean up related data in external systems
await api.httpPost("https://api.example.com/cleanup", {
deletedIds: recordIds,
collection: recordSlug
});
return { success: true, cleanedUp: count };
}
record_expired¶
Triggered when a record is automatically deleted because its TTL (time-to-live) expired. Similar to record_deleted but indicates the deletion was automatic, not user-initiated.
Payload Structure:
{
event: "record_expired",
workspaceSlug: string,
recordSlug: string,
recordId: string,
data: { /* full record data at time of expiry */ },
timestamp: string,
deletedBy: string
}
Function Example:
async function run() {
const { recordId, recordSlug, data } = executionParams;
api.log({
message: "Record expired via TTL",
recordId,
collection: recordSlug
});
// Archive expired record data to external storage
await api.storeAsJSON(data, `expired/${recordSlug}/${recordId}.json`, {
folder: "archives"
});
return { success: true, archived: recordId };
}
record_updated¶
Triggered when an existing record is modified. Includes both the previous and current state.
Payload Structure:
{
event: "record_updated",
workspaceSlug: string,
recordSlug: string,
recordId: string,
data: {
before: { // Record BEFORE the update
id: string,
workspaceSlug: string,
recordSlug: string,
data: { [fieldName: string]: any },
status: string,
version: number, // Previous version number
createdAt: string,
updatedAt: string, // Previous update timestamp
createdBy: string,
updatedBy: string // Previous updater
},
after: { // Record AFTER the update
id: string,
workspaceSlug: string,
recordSlug: string,
data: { [fieldName: string]: any },
status: string,
version: number, // Incremented version
createdAt: string,
updatedAt: string, // New update timestamp
createdBy: string,
updatedBy: string // Current updater
}
},
timestamp: string,
updatedBy: string
}
Real Example:
// executionParams for an order status change
{
event: "record_updated",
workspaceSlug: "acme-corp",
recordSlug: "orders",
recordId: "550e8400-e29b-41d4-a716-446655440000",
data: {
before: {
id: "550e8400-e29b-41d4-a716-446655440000",
workspaceSlug: "acme-corp",
recordSlug: "orders",
data: {
orderNumber: "ORD-2025-001",
status: "pending",
total: 109.97,
shippingAddress: { street: "123 Main St", city: "New York", zip: "10001" }
},
status: "active",
version: 1,
createdAt: "2025-01-15T10:30:00.000Z",
updatedAt: "2025-01-15T10:30:00.000Z",
createdBy: "user_xyz789",
updatedBy: "user_xyz789"
},
after: {
id: "550e8400-e29b-41d4-a716-446655440000",
workspaceSlug: "acme-corp",
recordSlug: "orders",
data: {
orderNumber: "ORD-2025-001",
status: "shipped",
total: 109.97,
trackingNumber: "1Z999AA10123456784",
shippingAddress: { street: "123 Main St", city: "New York", zip: "10001" }
},
status: "active",
version: 2,
createdAt: "2025-01-15T10:30:00.000Z",
updatedAt: "2025-01-15T14:45:00.000Z",
createdBy: "user_xyz789",
updatedBy: "user_admin001"
}
},
timestamp: "2025-01-15T14:45:00.456Z",
updatedBy: "user_admin001"
}
Function Example - Detecting Changes:
async function run() {
const { recordId, data } = executionParams;
const { before, after } = data;
// Compare before and after to find changes
const previousStatus = before.data.status;
const newStatus = after.data.status;
if (previousStatus !== newStatus) {
api.log({
message: "Order status changed",
orderId: recordId,
from: previousStatus,
to: newStatus
});
// Send notification only when status changes to "shipped"
if (newStatus === "shipped" && before.data.status !== "shipped") {
const trackingNumber = after.data.trackingNumber;
await api.http.post("https://api.example.com/notify-customer", {
type: "order_shipped",
orderId: recordId,
trackingNumber,
email: after.data.customerEmail
});
}
}
return { success: true, statusChanged: previousStatus !== newStatus };
}
Function Example - Finding Changed Fields:
async function run() {
const { data } = executionParams;
const { before, after } = data;
// Find which fields changed
const changedFields = [];
for (const key in after.data) {
if (JSON.stringify(before.data[key]) !== JSON.stringify(after.data[key])) {
changedFields.push({
field: key,
from: before.data[key],
to: after.data[key]
});
}
}
api.log({
message: "Fields changed",
changes: changedFields
});
return { changedFields };
}
record_deleted¶
Triggered when a record is deleted (soft delete or hard delete).
Payload Structure:
{
event: "record_deleted",
workspaceSlug: string,
recordSlug: string,
recordId: string,
data: { // Complete record as it existed before deletion
id: string,
workspaceSlug: string,
recordSlug: string,
data: { [fieldName: string]: any },
status: string, // Status before deletion
version: number,
createdAt: string,
updatedAt: string,
createdBy: string,
updatedBy: string
},
timestamp: string,
deletedBy: string // User who deleted the record
}
Real Example:
// executionParams for a deleted order
{
event: "record_deleted",
workspaceSlug: "acme-corp",
recordSlug: "orders",
recordId: "550e8400-e29b-41d4-a716-446655440000",
data: {
id: "550e8400-e29b-41d4-a716-446655440000",
workspaceSlug: "acme-corp",
recordSlug: "orders",
data: {
orderNumber: "ORD-2025-001",
status: "cancelled",
total: 109.97,
cancellationReason: "Customer requested"
},
status: "active",
version: 3,
createdAt: "2025-01-15T10:30:00.000Z",
updatedAt: "2025-01-15T16:00:00.000Z",
createdBy: "user_xyz789",
updatedBy: "user_admin001"
},
timestamp: "2025-01-15T16:30:00.789Z",
deletedBy: "user_admin001"
}
Function Example:
async function run() {
const { recordId, data, deletedBy } = executionParams;
// Archive deleted record to external system
await api.http.post("https://api.example.com/archive", {
type: "deleted_order",
originalId: recordId,
orderNumber: data.data.orderNumber,
deletedBy,
archivedData: data.data,
deletedAt: executionParams.timestamp
});
api.log({
message: "Order archived after deletion",
orderNumber: data.data.orderNumber,
deletedBy
});
return { success: true, archived: true };
}
record_restored¶
Triggered when a soft-deleted record is restored.
Payload Structure:
{
event: "record_restored",
workspaceSlug: string,
recordSlug: string,
recordId: string,
data: {
before: { // Record in deleted/archived state
id: string,
workspaceSlug: string,
recordSlug: string,
data: { [fieldName: string]: any },
status: "archived", // Was archived/deleted
version: number,
createdAt: string,
updatedAt: string,
createdBy: string,
updatedBy: string
},
after: { // Record after restoration
id: string,
workspaceSlug: string,
recordSlug: string,
data: { [fieldName: string]: any },
status: "active", // Now active again
version: number, // Version incremented
createdAt: string,
updatedAt: string, // Updated to restoration time
createdBy: string,
updatedBy: string // User who restored
}
},
timestamp: string,
restoredBy: string
}
Function Example:
async function run() {
const { recordId, data, restoredBy } = executionParams;
const { before, after } = data;
api.log({
message: "Record restored",
recordId,
restoredBy,
previousStatus: before.status,
newStatus: after.status
});
// Re-sync restored record with external system
await api.http.post("https://api.example.com/sync", {
action: "restore",
recordId,
data: after.data
});
return { success: true };
}
record_reverted¶
Triggered when a record is reverted to a previous version.
Payload Structure:
{
event: "record_reverted",
workspaceSlug: string,
recordSlug: string,
recordId: string,
data: {
before: { // Record before revert
id: string,
data: { [fieldName: string]: any },
version: number, // Higher version number
// ... other fields
},
after: { // Record after revert (restored to older version's data)
id: string,
data: { [fieldName: string]: any },
version: number, // New version (incremented, not the old version number)
// ... other fields
}
},
timestamp: string,
revertedBy: string,
revertedToVersion: number // The version number that was restored
}
Function Example:
async function run() {
const { recordId, data, revertedBy } = executionParams;
const { before, after } = data;
api.log({
message: "Record reverted to previous version",
recordId,
revertedBy,
fromVersion: before.version,
toVersion: after.version
});
return { success: true };
}
Failed Events¶
Failed events are triggered when a record operation fails. They include the error information.
record_created_failed¶
{
event: "record_created_failed",
workspaceSlug: string,
recordSlug: string,
data: { [fieldName: string]: any }, // Data that was attempted to be created
timestamp: string,
createdBy: string,
error: string // Error message
}
records_bulk_created_failed¶
{
event: "records_bulk_created_failed",
workspaceSlug: string,
recordSlug: string,
count: number, // Number of records that were attempted
timestamp: string,
createdBy: string,
error: string // Error message
}
record_updated_failed¶
{
event: "record_updated_failed",
workspaceSlug: string,
recordSlug: string,
recordId: string,
data: { [fieldName: string]: any }, // Data that was attempted
timestamp: string,
updatedBy: string,
error: string // Error message
}
record_deleted_failed¶
{
event: "record_deleted_failed",
workspaceSlug: string,
recordSlug: string,
recordId: string,
timestamp: string,
deletedBy: string,
error: string // Error message
}
Function Example - Handling Failed Events:
async function run() {
const { event, recordSlug, error, timestamp } = executionParams;
// Log failure to monitoring system
await api.http.post("https://api.example.com/alerts", {
type: "operation_failed",
event,
collection: recordSlug,
error,
timestamp,
severity: "warning"
});
api.logError({
message: `${event} failed`,
error,
collection: recordSlug
});
return { logged: true };
}
Event Differences Table¶
| Field | record_created | record_updated | record_deleted | record_restored | record_reverted |
|---|---|---|---|---|---|
data structure | Full record | { before, after } | Full record | { before, after } | { before, after } |
Initial version | 1 | Varies | Varies | Varies | Varies |
version changes | N/A | Incremented | N/A | Incremented | Incremented |
| Previous data available | No | Yes (in before) | No (is current) | Yes (in before) | Yes (in before) |
| User field | createdBy | updatedBy | deletedBy | restoredBy | revertedBy |
| Can detect changes | N/A | Yes | N/A | Yes | Yes |
Common Patterns¶
Pattern 1: Conditional Processing Based on Changed Fields¶
async function run() {
const { event, data } = executionParams;
// Only process updates where specific fields changed
if (event === "record_updated") {
const { before, after } = data;
// Only proceed if 'status' field changed
if (before.data.status === after.data.status) {
api.log({ message: "Status unchanged, skipping" });
return { skipped: true, reason: "no_status_change" };
}
}
// Process the event...
return { success: true };
}
Pattern 2: Combining Trigger Params with Event Data¶
async function run() {
const { recordId, data, event } = executionParams;
// Static config from trigger
const webhookUrl = triggerParams.webhookUrl;
const includeFullRecord = triggerParams.includeFullRecord;
const payload = {
event,
recordId,
timestamp: executionParams.timestamp
};
if (includeFullRecord) {
payload.record = event === "record_updated" ? data.after : data;
}
await api.http.post(webhookUrl, payload);
return { success: true };
}
Pattern 3: Handling Multiple Event Types in One Function¶
async function run() {
const { event, recordId, data } = executionParams;
switch (event) {
case "record_created":
await handleCreated(recordId, data);
break;
case "record_updated":
await handleUpdated(recordId, data.before, data.after);
break;
case "record_deleted":
await handleDeleted(recordId, data);
break;
default:
api.log({ message: "Unknown event type", event });
}
return { success: true, event };
}
async function handleCreated(recordId, record) {
api.log({ message: "New record", recordId });
// ... creation logic
}
async function handleUpdated(recordId, before, after) {
api.log({
message: "Record updated",
recordId,
versionBefore: before.version,
versionAfter: after.version
});
// ... update logic
}
async function handleDeleted(recordId, record) {
api.log({ message: "Record deleted", recordId });
// ... deletion logic
}
Related Documentation¶
- Triggers API - Creating and managing Triggers
- Function Code Guide - Writing function code
- Functions - Overview of Functions