Events Overview
EventFlow follows the Actor Model: every machine is an actor that communicates through asynchronous events. There is no direct method calling - only event emission and handling.
Core Concepts
Machines are isolated units that:
- Have their own state
- React to incoming events
- Emit events to other machines
- Never directly access another machine's state
API Events vs Internal Events
API Events (on ... (api))
Externally accessible events that can be triggered via HTTP API or external systems:
on :checkout from @customer (api)
// This can be triggered via API
order moves to #awaiting_paymentThe (api) suffix marks this event as a public endpoint.
Internal Events (on)
System-internal events that can only be triggered by other machines:
on :payment_success from @payment
// Only triggered by @payment machine
order moves to #paidNo (api) suffix - this event is internal to the system.
Receiving Events
Use on :event from @actor for internal events or on :event from @actor (api) for external:
machine: @order
scenario: checkout flow
// Public endpoint
on :checkout from @customer (api)
? cart is valid
emit :payment_request to @payment
order moves to #awaiting_payment
// Internal handlers
on :payment_success from @payment
order moves to #paid
emit :reserve_stock to @inventory
on :payment_failed from @payment
order moves to #failed
emit :checkout_failed to @customerThe from Clause
The from clause filters who can send the event:
// Accept from specific actor
on :payment_success from @payment
order moves to #paid
// Accept from any actor (no from clause)
on :status_update
update internal status
// Different handling based on source
on :approval from @manager
order moves to #manager_approved
on :approval from @director
order moves to #director_approvedSending Events
Use emit :event to @actor:
on :checkout from @customer (api)
? cart is valid
emit :payment_request to @payment
order moves to #awaiting_paymentEvents with Data
Events can carry data using the with: table:
on :add_item from @customer (api)
$items adds $item
$total increases by $item.price
emit :item_added to @customer with:
| field | value |
| item | $item |
| total | $total |Simple inline format for few fields:
emit :payment_request to @payment
with $order_id, $total, $currencyEvents with Data Validation
Add a validation column to the with: table to define validation rules:
emit :checkout to @order with:
| field | value | validation |
| order_id | $order_id | required, string, valid uuid |
| email | $email | required, string, valid email |
| total | $total | required, number, greater than 0 |
| coupon | $coupon | optional, string |Validation rules are comma-separated. Type rules (string, number) are validation rules like any other.
When validation fails, a :validation_failed event is automatically emitted with error details:
on :validation_failed
emit :checkout_error to @customer with:
| field | value | validation |
| errors | $errors | required |See Data Validation for complete validation documentation.
Event Routing and Instances
Events are routed to specific machine instances based on the conversation context.
First Event Creates Instance
When the first event in a conversation arrives, EventFlow creates a new instance (aggregate):
// First :checkout creates @order:abc123
on :checkout from @customer (api)
order moves to #processingAt this point:
- A new instance
@order:abc123is created - A conversation context is established
- The instance has its own isolated state and context
Subsequent Events Route to Existing Instance
After the first event, all related events in the same conversation are automatically routed to the same instance:
// Routes to existing @order:abc123 via conversation context
on :payment_success from @payment
order moves to #paidEventFlow handles this automatically - no correlation IDs needed in your code.
How Routing Works
Multiple Instances
Different customers create different instances, each processing independently:
Each instance maintains its own state and context - they never interfere with each other.
See Conversations for details on conversation context. See Instance Management for instance lifecycle details.
Pure Async Model
There is no request-response pattern. Communication is always:
emit- send event, don't waiton- handle event when it arrives
machine: @order
scenario: payment flow
on :checkout from @customer (api)
emit :payment_request to @payment
order moves to #awaiting_payment
// doesn't wait for response
on :payment_success from @payment
// handles response when it arrives
order moves to #paidNote: While machine-to-machine communication is purely async via events, API callers can receive structured HTTP responses via the
replykeyword. See Machine Responses for details on returning data to external callers.
Execution Model
Event handlers execute synchronously - all actions within a handler complete as a single unit.
Event emission is asynchronous - emit queues the event and continues without waiting for the target machine to process it.
on :checkout from @customer (api)
// ─── Runs synchronously (single transaction) ───
? cart is valid
$order_id becomes uuid()
order moves to #awaiting_payment
// ─── Queued for async processing ───
emit :payment_request to @payment
emit :analytics_event to @analyticsThis follows the Actor Model: actors process messages independently and communicate through asynchronous message passing.
Event Flow Diagram
Event Patterns
Request-Response Pattern (via Events)
machine: @order
on :checkout from @customer (api)
emit :payment_request to @payment
order moves to #awaiting_payment
on :payment_success from @payment
order moves to #paid
on :payment_failed from @payment
order moves to #payment_failedFan-Out Pattern
on :order_completed from @system (api)
emit :update_inventory to @inventory
emit :send_confirmation to @notification
emit :record_sale to @analytics
emit :update_loyalty to @customerFan-In Pattern
on :checkout from @customer (api)
emit :check_payment to @payment
emit :check_fraud to @fraud
emit :check_inventory to @inventory
on :payment_ok from @payment
$payment_ready becomes true
? all_checks_passed
order moves to #confirmed
on :fraud_ok from @fraud
$fraud_ready becomes true
? all_checks_passed
order moves to #confirmed
on :inventory_ok from @inventory
$inventory_ready becomes true
? all_checks_passed
order moves to #confirmedSaga Pattern
machine: @order_saga
on :start_order from @customer (api)
emit :reserve_inventory to @inventory
saga moves to #reserving_inventory
on :inventory_reserved from @inventory
emit :process_payment to @payment
saga moves to #processing_payment
on :payment_success from @payment
emit :confirm_inventory to @inventory
emit :order_confirmed to @customer
saga moves to #completed
// Compensating actions
on :payment_failed from @payment
emit :release_inventory to @inventory
emit :order_failed to @customer
saga moves to #failedBest Practices
Use Descriptive Event Names
// Good - clear intent
emit :payment_authorization_requested to @payment
emit :order_shipment_prepared to @warehouse
// Avoid - vague
emit :request to @payment
emit :update to @warehouseHandle All Responses
on :checkout from @customer (api)
emit :payment_request to @payment
order moves to #awaiting_payment
on :payment_success from @payment
order moves to #paid
on :payment_failed from @payment
order moves to #payment_failed
on :payment_timeout from @payment
order moves to #payment_timeoutDocument Event Data
emit :order_details to @warehouse
with:
| field | type | value |
| order_id | string | $order_id |
| items | array | $items |
| priority | string | "standard" |
| ship_by | date | $ship_date |Broadcasting Events
To send an event to all instances of a machine at once, use the broadcast syntax:
emit :reminder to all @order in #pending
with $deadlineBroadcasts are useful for:
- Sending notifications to all relevant instances
- Synchronizing multiple machines
- Triggering periodic checks across instances
See Broadcasting Events for full documentation on broadcast syntax, state filtering, and best practices.