Triggers¶
Overview¶
Triggers (function-triggers) define when and how your functions execute. A function contains the code; a trigger controls when that code runs.
Every function needs at least one trigger to execute. A single function can have multiple triggers — for example, the same order-processing function might run on new records (event-driven) and be invocable manually for retries (on-demand).
Trigger Types¶
Centrali supports five trigger types:
| Type | How It Fires | Best For |
|---|---|---|
| Event-driven | Automatically when records change | Workflows, notifications, data sync |
| Scheduled | On a time-based schedule | Reports, cleanup, periodic sync |
| On-demand | Manual API call or SDK invocation | Admin tools, testing, one-off tasks |
| HTTP | External HTTP request to a URL (fire-and-forget) | Webhooks from third parties (Stripe, GitHub, etc.) |
| Endpoint | HTTP request that returns function output | Custom APIs, form validators, data transformations |
Understanding Parameter Shapes
The #1 source of confusion with triggers is what data your function actually receives. The shape of executionParams is different for every trigger type — and for event-driven triggers, it varies by event. See the Trigger Parameters & Payload Shapes guide for complete examples of every shape.
Event-Driven Triggers¶
Event-driven triggers fire automatically when record events occur in your workspace. They are the most common trigger type.
Supported Events¶
Per-record events — fire once per record:
| Event | When It Fires |
|---|---|
record_created | After a new record is created |
record_updated | After an existing record is updated |
record_deleted | After a record is deleted (soft or hard) |
record_restored | After a deleted record is restored |
record_expired | When a record's TTL expires |
record_reverted | After a record is reverted to a previous version |
Aggregate events — fire once per bulk operation with all affected record IDs:
| Event | When It Fires |
|---|---|
records_bulk_created | After a bulk create operation |
records_bulk_updated | After a bulk update operation |
records_bulk_deleted | After a bulk delete operation |
There are also failure events (record_created_failed, record_updated_failed, record_deleted_failed, records_bulk_created_failed, records_bulk_updated_failed, records_bulk_deleted_failed) for error-handling workflows.
Bulk vs Batch: Choosing the Right Path¶
When working with multiple records, you choose between two paths that determine which events fire:
| Path | HTTP API | Compute Function API | Events | Best For |
|---|---|---|---|---|
| Bulk | POST /records/bulk | api.bulkCreateRecords() | 1 aggregate event | Processing all records together (indexing, batch notifications) |
| Batch | POST /records/batch | api.batchCreateRecords() | N per-record events | Processing each record individually (validation, per-record alerts) |
Both paths are available from both interfaces. Pick the path that matches your function's processing logic — not the interface you happen to be calling from.
How It Works¶
- You create a trigger linked to a function, specifying the event type and collection (via
recordSlug) - When a matching record event occurs, Centrali automatically executes your function
- The event data (record ID, record data, before/after values for updates) is passed as
executionParams
Example: Send notification on new order¶
curl -X POST "$CENTRALI_URL/data/workspace/$WORKSPACE/api/v1/function-triggers" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "notify-on-new-order",
"description": "Send Slack notification when a new order is created",
"functionId": "YOUR_FUNCTION_ID",
"executionType": "event-driven",
"triggerMetadata": {
"event": "record_created",
"recordSlug": "orders",
"params": {
"slackWebhookUrl": "https://hooks.slack.com/services/..."
}
}
}'
Accessing Event Data¶
In your function, the event payload is available via executionParams:
async function run() {
const { event, recordId, recordSlug, data } = executionParams;
if (event === "record_created") {
// data is the full record
api.log({ message: `New ${recordSlug} created`, recordId });
}
if (event === "record_updated") {
// data contains { before, after }
const statusChanged = data.before.data.status !== data.after.data.status;
if (statusChanged) {
api.log({ message: "Status changed", from: data.before.data.status, to: data.after.data.status });
}
}
return { success: true };
}
For full payload documentation, see the Event Payloads Reference.
When to Use Event-Driven Triggers¶
- Sending notifications when data changes
- Keeping derived data in sync (e.g., recalculating totals when line items change)
- Cascading updates across collections
- Audit logging
- Integrating with external systems in real time
Scheduled Triggers¶
Scheduled triggers run your function automatically based on time. There are three schedule types.
Interval¶
Run a function repeatedly every N seconds.
curl -X POST "$CENTRALI_URL/data/workspace/$WORKSPACE/api/v1/function-triggers" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "sync-inventory",
"functionId": "YOUR_FUNCTION_ID",
"executionType": "scheduled",
"triggerMetadata": {
"scheduleType": "interval",
"interval": 300
}
}'
Common intervals: 60 (every minute), 300 (5 minutes), 3600 (hourly), 86400 (daily).
Cron¶
Run on a cron schedule. Uses the standard 5-field format: minute hour day month weekday.
curl -X POST "$CENTRALI_URL/data/workspace/$WORKSPACE/api/v1/function-triggers" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "daily-report",
"functionId": "YOUR_FUNCTION_ID",
"executionType": "scheduled",
"triggerMetadata": {
"scheduleType": "cron",
"cronExpression": "0 9 * * 1-5",
"timezone": "America/New_York"
}
}'
| Expression | Description |
|---|---|
0 9 * * * | Every day at 9:00 AM |
0 9 * * 1-5 | Weekdays at 9:00 AM |
*/15 * * * * | Every 15 minutes |
0 0 1 * * | First day of each month at midnight |
Once¶
Run a single time at a specific datetime. The scheduledAt value must be in the future.
curl -X POST "$CENTRALI_URL/data/workspace/$WORKSPACE/api/v1/function-triggers" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "launch-campaign",
"functionId": "YOUR_FUNCTION_ID",
"executionType": "scheduled",
"triggerMetadata": {
"scheduleType": "once",
"scheduledAt": "2026-03-01T09:00:00Z",
"timezone": "America/New_York"
}
}'
Pause and Resume¶
Scheduled triggers can be paused and resumed without deleting them:
# Pause
curl -X PATCH "$CENTRALI_URL/data/workspace/$WORKSPACE/api/v1/function-triggers/TRIGGER_ID/pause" \
-H "Authorization: Bearer $TOKEN"
# Resume
curl -X PATCH "$CENTRALI_URL/data/workspace/$WORKSPACE/api/v1/function-triggers/TRIGGER_ID/resume" \
-H "Authorization: Bearer $TOKEN"
When to Use Scheduled Triggers¶
- Generating periodic reports
- Data cleanup and archival
- Syncing data with external systems
- Cache warming or invalidation
- Health checks and monitoring
On-Demand Triggers¶
On-demand triggers execute only when you explicitly invoke them via the API or SDK.
Creating and Executing¶
# Create the trigger with static configuration
curl -X POST "$CENTRALI_URL/data/workspace/$WORKSPACE/api/v1/function-triggers" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "manual-export",
"functionId": "YOUR_FUNCTION_ID",
"executionType": "on-demand",
"triggerMetadata": {
"params": {
"format": "csv",
"batchSize": 1000
}
}
}'
# Execute it with runtime parameters
curl -X POST "$CENTRALI_URL/data/workspace/$WORKSPACE/api/v1/function-triggers/TRIGGER_ID/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"recordType": "orders",
"startDate": "2026-01-01"
}'
Using the SDK¶
const result = await centrali.invokeFunction('manual-export', {
recordType: 'orders',
startDate: '2026-01-01'
});
Two Types of Parameters¶
On-demand triggers distinguish between static and runtime parameters:
triggerParams— Set when creating the trigger. Good for API keys, configuration, and defaults.executionParams— Passed at execution time. Good for per-invocation data like record IDs or filters.
async function run() {
// Static config from trigger setup
const format = triggerParams.format; // "csv"
const batchSize = triggerParams.batchSize; // 1000
// Runtime data from this specific execution
const recordType = executionParams.recordType; // "orders"
const startDate = executionParams.startDate; // "2026-01-01"
// ... process data
return { success: true };
}
When to Use On-Demand Triggers¶
- Admin tools and dashboards
- Data migrations
- Testing and debugging functions
- User-initiated actions (export, recalculate)
- Retry workflows
HTTP Triggers¶
HTTP triggers create a public URL that executes your function when called. This is ideal for receiving webhooks from third-party services.
Creating an HTTP Trigger¶
curl -X POST "$CENTRALI_URL/data/workspace/$WORKSPACE/api/v1/function-triggers" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "stripe-webhook",
"functionId": "YOUR_FUNCTION_ID",
"executionType": "http-trigger",
"triggerMetadata": {
"path": "/payments/stripe",
"params": {
"signingSecret": {
"value": "whsec_abc123",
"encrypt": true
}
}
}
}'
The trigger URL will be:
Accessing the HTTP Payload¶
The incoming request body is available as executionParams:
async function run() {
// The HTTP request body
const event = executionParams;
// Verify webhook signature using encrypted secret
const signature = event.headers?.['stripe-signature'];
const expectedSig = api.hmacSha256(triggerParams.signingSecret, JSON.stringify(event.body));
api.log({ message: 'Webhook received', type: event.body?.type });
return { success: true };
}
When to Use HTTP Triggers¶
- Payment gateway callbacks (Stripe, PayPal)
- CI/CD pipeline notifications (GitHub, GitLab)
- Form submissions from external sites
- Third-party service integrations
- IoT device events
Endpoint Triggers¶
Endpoint triggers turn your function into a custom API endpoint. Unlike HTTP triggers (fire-and-forget), endpoint triggers wait for the function to complete and return its output directly in the HTTP response.
How It Works¶
- You create a trigger with
executionType: "endpoint"and a unique path - Centrali generates an endpoint URL:
https://api.centrali.io/data/workspace/YOUR_WORKSPACE/api/v1/endpoints/YOUR_PATH - When a request hits that URL, the function executes synchronously and the response contains the function's return value
Creating an Endpoint¶
curl -X POST "$CENTRALI_URL/data/workspace/$WORKSPACE/api/v1/function-triggers" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "price-calculator",
"functionId": "YOUR_FUNCTION_ID",
"executionType": "endpoint",
"triggerMetadata": {
"path": "calculate-price",
"allowedMethods": ["POST"],
"timeoutMs": 10000,
"auth": { "mode": "bearer" }
}
}'
Invoking the Endpoint¶
curl -X POST "$CENTRALI_URL/data/workspace/$WORKSPACE/api/v1/endpoints/calculate-price" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "items": [{ "sku": "WIDGET-1", "quantity": 3 }] }'
The response body is whatever your function returns:
Returning Custom Status Codes and Headers¶
Your function controls the HTTP response using context.res.json():
export default async (context) => {
const { payload } = context.req;
if (!payload.items || payload.items.length === 0) {
return context.res.json({
statusCode: 400,
body: { error: "No items provided" }
});
}
const total = calculateTotal(payload.items);
return context.res.json({
statusCode: 200,
headers: { "X-Calculation-Version": "2" },
body: { total }
});
};
Configuration Options¶
| Field | Type | Default | Description |
|---|---|---|---|
path | string | (required) | URL path segment. Must be URL-safe (alphanumeric, hyphens, underscores). Unique per workspace. |
allowedMethods | string[] | ["POST"] | HTTP methods the endpoint accepts: GET, POST, PUT, DELETE, PATCH. |
timeoutMs | number | 30000 | Maximum execution time in milliseconds. Range: 1,000–30,000. |
auth | object | { mode: "bearer" } | Authentication mode. See below. |
params | object | {} | Static parameters passed to your function on every invocation. |
Authentication Modes¶
| Mode | Config | How to Call |
|---|---|---|
| Bearer (default) | { "mode": "bearer" } | Authorization: Bearer YOUR_JWT |
| API Key | { "mode": "apiKey" } | X-API-Key: GENERATED_KEY (returned on creation) |
| HMAC | { "mode": "hmac" } | X-Signature: HMAC_SHA256_SIGNATURE |
| Public | { "mode": "public" } | No authentication required |
| Multi-mode | { "modes": ["bearer", "apiKey"] } | Any configured mode succeeds |
Public Endpoints
Public endpoints are accessible without authentication. Only use this for truly public APIs. public cannot be combined with other modes.
When using API Key mode, the system generates a key automatically and returns it in the creation response. Store it securely — it cannot be retrieved again.
Error Responses¶
| Status | When |
|---|---|
| 401 | Authentication required but no valid credentials |
| 403 | Valid credentials but insufficient permissions |
| 404 | No endpoint found for this path, or trigger is disabled |
| 405 | HTTP method not in the endpoint's allowedMethods |
| 500 | Function threw an unhandled exception |
| 503 | No compute worker available (request is not queued) |
| 504 | Function exceeded its configured timeout |
Endpoint vs HTTP Trigger¶
| Endpoint | HTTP Trigger | |
|---|---|---|
| Response | Function output returned inline | 202 Accepted with execution ID |
| Execution | Synchronous (blocks until done) | Asynchronous (queued) |
| Timeout | 1–30 seconds (configurable) | Up to 5 minutes |
| No workers | Immediate 503 error | Queued for later |
| Auth | Bearer, API Key, HMAC, Public | HMAC signature validation |
| Best for | Custom APIs, validations, transformations | Webhooks, background processing |
When to Use Endpoint Triggers¶
- Building custom REST APIs backed by your functions
- Form validation or submission handlers
- Webhook responders that need to return a result
- Data transformation or calculation endpoints
- Any logic where the caller needs the function's output
Encrypted Parameters¶
Sensitive values like API keys and webhook secrets can be encrypted at rest. Add "encrypt": true to any parameter:
{
"triggerMetadata": {
"params": {
"publicSetting": "visible-in-api",
"apiKey": {
"value": "sk_live_abc123",
"encrypt": true
}
}
}
}
Encrypted parameters:
- Are stored using AES-256-GCM encryption
- Appear as encrypted objects in API responses (you cannot read the plaintext back)
- Are automatically decrypted before your function executes
- Are available as plain values in
triggerParams
Multiple Triggers Per Function¶
A single function can have multiple triggers. This is useful when the same logic needs to run in different contexts:
Function: "process-order"
├── Trigger 1: event-driven (record_created on "orders")
├── Trigger 2: on-demand (manual retry from admin panel)
├── Trigger 3: http-trigger (incoming webhook from partner API)
└── Trigger 4: endpoint (GET /order-status — returns order details inline)
Each trigger can have different triggerMetadata.params, so the same function can behave differently depending on how it was invoked. Check executionParams to determine the source:
async function run() {
if (executionParams.event) {
// Triggered by a record event
api.log({ message: 'Event-driven execution' });
} else if (executionParams.headers) {
// Triggered by HTTP webhook
api.log({ message: 'HTTP trigger execution' });
} else {
// On-demand or scheduled
api.log({ message: 'Manual/scheduled execution' });
}
return { success: true };
}
Choosing the Right Trigger Type¶
| Scenario | Trigger Type |
|---|---|
| "When a new user signs up, send a welcome email" | Event-driven (record_created) |
| "Every night at midnight, generate a report" | Scheduled (cron) |
| "Every 5 minutes, sync data from external API" | Scheduled (interval) |
| "Admin clicks 'Export' button in dashboard" | On-demand |
| "Stripe sends a payment confirmation" | HTTP trigger |
| "Run a one-time data migration next Tuesday" | Scheduled (once) |
| "When an order is updated, recalculate totals" | Event-driven (record_updated) |
| "Frontend calls an API to calculate shipping cost" | Endpoint |
| "External service needs a computed response" | Endpoint |
Best Practices¶
-
Name triggers descriptively — Use names like
notify-on-new-orderordaily-sales-report, nottrigger-1. -
Encrypt sensitive parameters — Always use
{ "value": "...", "encrypt": true }for API keys, secrets, and credentials. -
Use on-demand triggers for retry — Pair event-driven triggers with an on-demand trigger on the same function so you can manually retry failures.
-
Keep event-driven functions fast — They run on every matching event. Offload heavy processing to scheduled or on-demand triggers.
-
Use cron over interval for specific times — If you need "every day at 9 AM", use cron (
0 9 * * *). Interval-based triggers drift over time and don't respect timezones. -
Set timezones explicitly — Cron and once triggers default to UTC. Always set
timezoneif your schedule is business-hours dependent. -
Monitor execution logs — Check function runs regularly via the API or Console to catch failures early. See Monitoring & Debugging.
Related Documentation¶
- Trigger Parameters & Payload Shapes — Exact
executionParamsshape for every trigger type and event - Functions — Creating and managing functions
- Writing Functions — Function code guide and API reference
- Triggers API — Complete API endpoints reference
- Event Payloads Reference — Full event payload structures
- Function Re-run — Re-running previous executions
- Automations — Chaining multiple functions together