Event-driven ProcessFlow
Document information
- Canonical URL:
/docs/02_automations-and-processflow/18_event-driven-processflow - Version:
2026-06-09 - Tags:
processflow,events,guides
Event-driven ProcessFlow lets business events trigger automations without tight coupling between services. This guide covers the event payload contract, the Event Broker routing pattern, idempotent listeners, and how to emit and troubleshoot events from step code or the API. Use it when multiple workflows should react to the same event — for example a new user, completed order, or uploaded document.
Use a stable event payload contract
A stable contract keeps producers and listeners aligned as workflows evolve. At minimum, include event_type, tenant_id, and data. Add tracing fields such as event_id, correlation_id, source, and priority when you need deduplication, observability, or routing controls.
Use consistent event_type names such as user_created, contract_signed, or payment_failed so broker routing stays predictable. Keep data focused on the fields listeners need — smaller payloads are easier to validate, replay, and debug.
{
"event_type": "user_created",
"tenant_id": "<TENANT_ID>",
"event_id": "evt_01J7N0X9D2F4",
"correlation_id": "corr_22a14f",
"priority": "normal",
"source": "UserService",
"data": {
"user_id": "usr_9d12a8",
"email": "new.user@example.com",
"user_type": "standard"
}
}
When events pass through an Event Broker, wrap the payload in input_data.result so listeners read from process_input.result.
Design the Event Broker as a routing layer
An Event Broker process receives a normalized event, looks up matching listener process IDs, and queues each listener asynchronously. Source processes publish one event while downstream logic evolves independently. The broker is also the right place to standardize retries, dead-letter handling, and administrator notifications.
Keep the broker intentionally small: validate core fields, route events, and return a clear dispatch result. Heavy business logic belongs in listener processes so each consumer can change without affecting producers.
Emit events through ProcessFlow API calls
Emit events by calling your broker process with action=execute-process. For most publishing, use asynchronous mode so the source workflow does not wait for every listener to finish. From ProcessFlow step code, use the injected api service — do not scaffold custom fetch wrappers.
const eventBrokerProcessId = "<PROCESS_ID>";
const tenantId = String(tenant_id ?? "");
async function emitUserCreatedEvent(entityId: string) {
return await api.post("/api/v1/processflow?action=execute-process", {
process_id: eventBrokerProcessId,
input_data: {
result: {
event_type: "user_created",
tenant_id: tenantId,
event_id: `evt_${entityId}`,
correlation_id: `corr_${entityId}`,
priority: "normal",
data: {
user_id: entityId,
email: "new.user@example.com",
},
},
},
async: true,
});
}curl -X POST "https://<YOUR_DOMAIN>/api/v1/processflow?action=execute-process" \
-H "X-API-Key: <API_KEY>" \
-H "X-Tenant-ID: <TENANT_ID>" \
-H "Content-Type: application/json" \
-d '{
"process_id": "<PROCESS_ID>",
"input_data": {
"result": {
"event_type": "user_created",
"tenant_id": "<TENANT_ID>",
"event_id": "evt_<ENTITY_ID>",
"correlation_id": "corr_<ENTITY_ID>",
"priority": "normal",
"data": {
"user_id": "<ENTITY_ID>",
"email": "new.user@example.com"
}
}
},
"async": true
}'
{
"success": true,
"event_id": "evt_<ENTITY_ID>",
"signaled": 2,
"succeeded": 2,
"dlq": [],
"message": ["Processes triggered for event type: user_created"]
}
Build listeners for idempotent handling
Listener processes should validate event_type, read payload data from process_input.result, and handle duplicate delivery safely. Check event_id before running side effects so retries do not duplicate emails, records, or external calls.
type PlatformEvent = {
event_type: string;
event_id?: string;
data: { user_id?: string; email?: string };
};
async function handleUserCreatedEvent(
event: PlatformEvent,
repo: { hasProcessed(eventId: string): Promise<boolean>; markProcessed(eventId: string): Promise<void> }
) {
if (event.event_type !== "user_created") return { success: true, data: { skipped: true } };
const eventId = event.event_id ?? "";
if (eventId && (await repo.hasProcessed(eventId))) {
return { success: true, data: { duplicate: true } };
}
// Business action placeholder (for example, enqueue onboarding process)
if (eventId) await repo.markProcessed(eventId);
return { success: true, data: { processed: true, user_id: event.data.user_id ?? null } };
}When a listener cannot be queued or executed, capture the failure in a dead-letter structure and notify operators with enough context to reprocess safely.
Operate and troubleshoot event flows
Reliable event workflows depend on clear contracts, small payloads, and retriable handlers that return consistent outcomes. Monitor event volume, processing latency, and failure rates so queue backlog and handler regressions are visible early.
If events are not processed, verify the broker process ID and listener mapping table first. Confirm the incoming event_type exactly matches configured keys and inspect broker output for dlq entries. Most routing failures come from naming mismatch, inactive listener processes, or malformed payload structure.
Review admin notifications regularly for events with no listeners — these usually indicate missed onboarding for new event types.
Related documentation
The Event Broker pattern gives you decoupled workflows, cleaner ownership boundaries, and safer change management across ProcessFlow automations.