Skip to main content

Overview

Events play a crucial role in Serverless Workflow by facilitating communication and coordination between different components and services. They enable workflows to react to external stimuli, paving the way for event-driven architectures and real-time processing scenarios. Events are essentially messages that convey information about a specific occurrence or action, allowing workflows to respond dynamically to changes in their environment.
Events in Serverless Workflow adhere to the CloudEvents specification, ensuring interoperability and compatibility with event-driven systems.

CloudEvents Standard

CloudEvents is a specification for describing event data in a common way. This standardization allows workflows to seamlessly interact with various event sources and consumers across different platforms and environments.

CloudEvent Structure

A CloudEvent contains standardized attributes:
{
  "specversion": "1.0",
  "type": "com.example.order.created",
  "source": "https://api.example.com/orders",
  "id": "A234-1234-1234",
  "time": "2024-03-09T12:00:00Z",
  "datacontenttype": "application/json",
  "data": {
    "orderId": "12345",
    "customerId": "67890",
    "total": 99.99
  }
}
specversion
string
required
CloudEvents specification version (e.g., “1.0”)
type
string
required
Event type identifier (e.g., “com.example.order.created”)
source
string
required
Context in which the event occurred (URI)
id
string
required
Unique identifier for the event
time
string
Timestamp when the event occurred (ISO 8601 format)
datacontenttype
string
Content type of the data attribute
data
any
The event payload containing domain-specific information

Emitting Events

The emit task allows workflows to publish events to event brokers or messaging systems. This capability enables workflows to broadcast notifications about various events, facilitating event-driven architectures.

Basic Event Emission

emitOrderCreated:
  emit:
    event:
      type: order.created
      source: https://api.example.com/orders
      data:
        orderId: ${ .orderId }
        customerId: ${ .customerId }
        total: ${ .total }
        timestamp: ${ now }
emit.event
object
required
The CloudEvent to emit

Emitting Multiple Events

do:
  - processOrder:
      call: orderService
      with:
        order: ${ .orderData }
  
  - emitOrderEvents:
      emit:
        event:
          type: order.processed
          source: https://api.example.com/orders
          data: ${ .processOrder.output }
  
  - notifyCustomer:
      emit:
        event:
          type: customer.notification
          source: https://api.example.com/notifications
          data:
            customerId: ${ .orderData.customerId }
            message: Your order has been processed

Emitting with Dynamic Properties

emitDynamicEvent:
  emit:
    event:
      type: ${ .eventType }
      source: https://api.example.com
      id: ${ .eventId }
      data: ${ .eventData }
      subject: ${ .eventSubject }
      datacontenttype: application/json

Complete Emission Example

document:
  dsl: '1.0.3'
  namespace: ecommerce
  name: order-processing
  version: '1.0.0'

do:
  - validateOrder:
      call: orderValidator
      with:
        order: ${ .orderData }
  
  - emitValidationComplete:
      emit:
        event:
          type: order.validated
          source: https://api.example.com/orders
          data:
            orderId: ${ .orderData.orderId }
            valid: ${ .validateOrder.output.isValid }
  
  - processPayment:
      if: ${ .validateOrder.output.isValid }
      call: paymentService
      with:
        amount: ${ .orderData.total }
  
  - emitPaymentProcessed:
      emit:
        event:
          type: payment.processed
          source: https://api.example.com/payments
          data:
            orderId: ${ .orderData.orderId }
            paymentId: ${ .processPayment.output.paymentId }
            status: ${ .processPayment.output.status }

Use Cases for Emitting Events

Order Lifecycle Events

do:
  - createOrder:
      call: orderService
  
  - emitCreated:
      emit:
        event:
          type: order.created
          source: https://api.example.com/orders
          data: ${ .createOrder.output }
  
  - processOrder:
      call: processor
  
  - emitProcessed:
      emit:
        event:
          type: order.processed
          source: https://api.example.com/orders
          data: ${ .processOrder.output }
  
  - shipOrder:
      call: shippingService
  
  - emitShipped:
      emit:
        event:
          type: order.shipped
          source: https://api.example.com/orders
          data: ${ .shipOrder.output }

System Monitoring Events

do:
  - performHealthCheck:
      call: healthChecker
  
  - emitHealthStatus:
      emit:
        event:
          type: system.health.checked
          source: https://monitoring.example.com
          data:
            status: ${ .performHealthCheck.output.status }
            services: ${ .performHealthCheck.output.services }
            timestamp: ${ now }

Data Update Notifications

do:
  - updateRecord:
      call: databaseService
      with:
        action: update
        data: ${ .recordData }
  
  - emitUpdateNotification:
      emit:
        event:
          type: data.updated
          source: https://api.example.com/records
          subject: ${ .recordData.id }
          data:
            recordId: ${ .recordData.id }
            updatedFields: ${ .updateRecord.output.changes }
            updatedBy: ${ .userId }
            timestamp: ${ now }

Listening for Events

The listen task provides a mechanism for workflows to await and react to external events. It enables workflows to subscribe to specific event types or patterns and trigger actions based on incoming events.

Basic Event Listening

waitForOrder:
  listen:
    to:
      any:
        - with:
            type: order.created
            source: https://api.example.com/orders
listen.to
object
required
Defines the events to listen for using any or all semantics

Listening for Any Event

Matches when any one of the specified events is received:
waitForNotification:
  listen:
    to:
      any:
        - with:
            type: user.registered
            source: https://api.example.com/users
        - with:
            type: user.updated
            source: https://api.example.com/users
        - with:
            type: user.deleted
            source: https://api.example.com/users
listen.to.any
array
Array of event filters. The task completes when any one matching event is received.

Listening for All Events

Matches when all of the specified events have been received:
waitForApprovals:
  listen:
    to:
      all:
        - with:
            type: approval.manager
            source: https://api.example.com/approvals
        - with:
            type: approval.finance
            source: https://api.example.com/approvals
        - with:
            type: approval.legal
            source: https://api.example.com/approvals
listen.to.all
array
Array of event filters. The task completes only when all matching events have been received.

Event Filtering

You can filter events by multiple attributes:
listenForSpecificOrder:
  listen:
    to:
      any:
        - with:
            type: order.updated
            source: https://api.example.com/orders
            subject: ${ .orderId }  # Only events for this specific order
            data:
              status: shipped  # Only when status is 'shipped'

Listen with Timeout

waitForPayment:
  listen:
    to:
      any:
        - with:
            type: payment.received
            source: https://payments.example.com
  timeout:
    after:
      minutes: 5
If the listen task times out before receiving a matching event, it raises a timeout error that can be caught and handled.

Complete Listening Example

document:
  dsl: '1.0.3'
  namespace: fulfillment
  name: order-fulfillment
  version: '1.0.0'

do:
  - createOrder:
      call: orderService
      with:
        order: ${ .orderData }
  
  - waitForPaymentAndInventory:
      listen:
        to:
          all:
            - with:
                type: payment.confirmed
                source: https://payments.example.com
                subject: ${ .createOrder.output.orderId }
            - with:
                type: inventory.allocated
                source: https://inventory.example.com
                subject: ${ .createOrder.output.orderId }
      timeout:
        after:
          minutes: 10
  
  - fulfillOrder:
      call: fulfillmentService
      with:
        orderId: ${ .createOrder.output.orderId }
        payment: ${ .waitForPaymentAndInventory.output[0] }
        inventory: ${ .waitForPaymentAndInventory.output[1] }

Use Cases for Listening

Waiting for User Action

do:
  - sendVerificationEmail:
      call: emailService
      with:
        recipient: ${ .userEmail }
        template: verification
  
  - waitForVerification:
      listen:
        to:
          any:
            - with:
                type: user.verified
                source: https://api.example.com/users
                subject: ${ .userId }
      timeout:
        after:
          hours: 24
  
  - activateAccount:
      call: accountService
      with:
        userId: ${ .userId }
        action: activate

Coordinating Distributed Transactions

do:
  - initiateTransaction:
      call: transactionCoordinator
      with:
        transactionId: ${ .txId }
  
  - waitForParticipants:
      listen:
        to:
          all:
            - with:
                type: transaction.prepared
                source: https://service-a.example.com
                subject: ${ .txId }
            - with:
                type: transaction.prepared
                source: https://service-b.example.com
                subject: ${ .txId }
            - with:
                type: transaction.prepared
                source: https://service-c.example.com
                subject: ${ .txId }
      timeout:
        after:
          seconds: 30
  
  - commitTransaction:
      call: transactionCoordinator
      with:
        transactionId: ${ .txId }
        action: commit

Real-Time Data Processing

do:
  - waitForSensorData:
      listen:
        to:
          any:
            - with:
                type: sensor.reading
                source: https://iot.example.com
                data:
                  temperature: { $gt: 100 }  # Temperature above threshold
  
  - triggerAlert:
      call: alertingService
      with:
        severity: high
        message: Temperature threshold exceeded
        reading: ${ .waitForSensorData.output.data }

Event Correlation

Event correlation allows workflows to wait for and match multiple related events before proceeding.

Correlation Patterns

Pattern: All Events Required

waitForAllApprovals:
  listen:
    to:
      all:
        - with:
            type: approval.received
            source: https://approvals.example.com
            data:
              approver: manager
        - with:
            type: approval.received
            source: https://approvals.example.com
            data:
              approver: finance
        - with:
            type: approval.received
            source: https://approvals.example.com
            data:
              approver: hr
The all correlation waits for every specified event to be received before the task completes.

Pattern: First Event Wins

waitForAnyResponse:
  listen:
    to:
      any:
        - with:
            type: response.received
            source: https://service-a.example.com
        - with:
            type: response.received
            source: https://service-b.example.com
        - with:
            type: response.received
            source: https://service-c.example.com
The any correlation completes as soon as the first matching event is received.

Pattern: Event Sequence

do:
  - waitForStepOne:
      listen:
        to:
          any:
            - with:
                type: process.step1.complete
                source: https://api.example.com
  
  - waitForStepTwo:
      listen:
        to:
          any:
            - with:
                type: process.step2.complete
                source: https://api.example.com
  
  - waitForStepThree:
      listen:
        to:
          any:
            - with:
                type: process.step3.complete
                source: https://api.example.com
  
  - finalizeProcess:
      call: finalizationService

Pattern: Complex Correlation

waitForOrderCompletion:
  listen:
    to:
      all:
        - with:
            type: payment.success
            source: https://payments.example.com
            subject: ${ .orderId }
        - with:
            type: inventory.reserved
            source: https://inventory.example.com
            subject: ${ .orderId }
        - with:
            type: shipping.scheduled
            source: https://shipping.example.com
            subject: ${ .orderId }
        - with:
            type: notification.sent
            source: https://notifications.example.com
            subject: ${ .orderId }

Event-Driven Workflow Patterns

Pattern: Event-Sourced Workflow

document:
  dsl: '1.0.3'
  namespace: banking
  name: account-manager
  version: '1.0.0'

schedule:
  on:
    events:
      - with:
          type: account.created
          source: https://banking.example.com

do:
  - storeAccountCreated:
      emit:
        event:
          type: account.event.stored
          source: https://eventstore.example.com
          data: ${ $workflow.input[0] }
  
  - processAccountSetup:
      call: setupService
      with:
        accountData: ${ $workflow.input[0].data }
  
  - emitSetupComplete:
      emit:
        event:
          type: account.setup.complete
          source: https://banking.example.com
          data: ${ .processAccountSetup.output }

Pattern: Saga with Event Orchestration

document:
  dsl: '1.0.3'
  namespace: travel
  name: booking-saga
  version: '1.0.0'

do:
  - bookFlight:
      call: flightService
      with:
        booking: ${ .flightDetails }
  
  - emitFlightBooked:
      emit:
        event:
          type: flight.booked
          source: https://travel.example.com
          data: ${ .bookFlight.output }
  
  - bookHotel:
      try:
        call: hotelService
        with:
          booking: ${ .hotelDetails }
      catch:
        errors: {}
        do:
          - compensateFlight:
              call: flightService
              with:
                action: cancel
                bookingId: ${ .bookFlight.output.id }
          - emitFlightCancelled:
              emit:
                event:
                  type: flight.cancelled
                  source: https://travel.example.com
                  data: ${ .compensateFlight.output }
        then: end
  
  - emitHotelBooked:
      emit:
        event:
          type: hotel.booked
          source: https://travel.example.com
          data: ${ .bookHotel.output }
  
  - emitBookingComplete:
      emit:
        event:
          type: booking.complete
          source: https://travel.example.com
          data:
            flight: ${ .bookFlight.output }
            hotel: ${ .bookHotel.output }

Pattern: Event-Driven State Machine

document:
  dsl: '1.0.3'
  namespace: workflow
  name: order-state-machine
  version: '1.0.0'

schedule:
  on:
    events:
      - with:
          type: order.created
          source: https://api.example.com

do:
  - waitForStateTransition:
      listen:
        to:
          any:
            - with:
                type: order.paid
                subject: ${ $workflow.input[0].data.orderId }
            - with:
                type: order.cancelled
                subject: ${ $workflow.input[0].data.orderId }
  
  - processBasedOnState:
      switch:
        - when: ${ .waitForStateTransition.output.type == "order.paid" }
          then: processPaidOrder
        - when: ${ .waitForStateTransition.output.type == "order.cancelled" }
          then: processCancelledOrder
  
  - processPaidOrder:
      do:
        - fulfillOrder:
            call: fulfillmentService
        - emitFulfilled:
            emit:
              event:
                type: order.fulfilled
                source: https://api.example.com
                data: ${ .fulfillOrder.output }
      then: end
  
  - processCancelledOrder:
      do:
        - refundOrder:
            call: refundService
        - emitRefunded:
            emit:
              event:
                type: order.refunded
                source: https://api.example.com
                data: ${ .refundOrder.output }

Pattern: Event Fan-Out/Fan-In

do:
  - emitNotificationRequests:
      fork:
        branches:
          - emailNotification:
              emit:
                event:
                  type: notification.email.requested
                  source: https://notifications.example.com
                  data: ${ .notificationData }
          
          - smsNotification:
              emit:
                event:
                  type: notification.sms.requested
                  source: https://notifications.example.com
                  data: ${ .notificationData }
          
          - pushNotification:
              emit:
                event:
                  type: notification.push.requested
                  source: https://notifications.example.com
                  data: ${ .notificationData }
  
  - waitForAllConfirmations:
      listen:
        to:
          all:
            - with:
                type: notification.email.sent
                subject: ${ .notificationId }
            - with:
                type: notification.sms.sent
                subject: ${ .notificationId }
            - with:
                type: notification.push.sent
                subject: ${ .notificationId }
      timeout:
        after:
          minutes: 5

Event Best Practices

1

Use meaningful event types

Event types should clearly describe what happened. Use hierarchical naming like domain.entity.action (e.g., order.payment.completed).
2

Include context in events

Always include sufficient context in event data so consumers can process events without additional lookups.
3

Set appropriate timeouts

When listening for events, always configure timeouts to prevent workflows from waiting indefinitely.
4

Handle missing events

Implement error handling for cases where expected events don’t arrive within timeout periods.
5

Use event correlation carefully

When correlating multiple events, consider what happens if some events never arrive and implement appropriate fallbacks.
6

Version your event schemas

Include version information in event types or data to support schema evolution.
7

Emit events at meaningful points

Emit events at state transitions and important milestones to enable observability and integration.

Common Pitfalls

Pitfall 1: Missing Timeout on Listen

# Bad: No timeout - workflow waits forever
waitForEvent:
  listen:
    to:
      any:
        - with:
            type: user.action

# Good: Timeout prevents infinite waiting
waitForEvent:
  try:
    listen:
      to:
        any:
          - with:
              type: user.action
    timeout:
      after:
        hours: 24
  catch:
    errors:
      with:
        type: https://serverlessworkflow.io/spec/1.0.0/errors/timeout
    do:
      - handleTimeout:
          call: timeoutHandler

Pitfall 2: Insufficient Event Filtering

# Bad: Receives all order events
listen:
  to:
    any:
      - with:
          type: order.updated

# Good: Filters for specific order
listen:
  to:
    any:
      - with:
          type: order.updated
          subject: ${ .orderId }
          source: https://api.example.com/orders

Pitfall 3: Not Handling Event Correlation Failures

# Bad: No handling if all events don't arrive
listen:
  to:
    all:
      - with:
          type: approval.manager
      - with:
          type: approval.finance

# Good: Timeout with fallback
try:
  listen:
    to:
      all:
        - with:
            type: approval.manager
        - with:
            type: approval.finance
  timeout:
    after:
      days: 3
catch:
  errors:
    with:
      type: https://serverlessworkflow.io/spec/1.0.0/errors/timeout
  do:
    - escalateApproval:
        call: escalationService
  • Workflows - Learn about workflow lifecycle events
  • Scheduling - Use event-driven scheduling
  • Tasks - Understand emit and listen task types
  • Timeouts - Configure timeouts for event listening
  • Data Flow - Process event data in workflows

Build docs developers (and LLMs) love