Skip to main content
Version: 2.0

Events

Lifecycle events are dispatched at key stages of workflow and activity execution to notify your application of progress, completion, or failures. These are standard Laravel events — register listeners in your EventServiceProvider or with Event::listen().

All V2 lifecycle events are dispatched after the durable state is committed to the database. This means listeners only observe events backed by committed truth — if a transaction rolls back, no event is dispatched.

Event Identity

Every V2 event carries durable identity fields:

FieldDescription
instanceIdThe workflow instance ID (stable across continue-as-new).
runIdThe specific execution run ID.
workflowTypeThe durable type key registered via #[Type('...')].
workflowClassThe PHP class name of the workflow.
committedAtISO 8601 timestamp of when the durable record was committed (wall-clock commit time).

Activity events additionally include:

FieldDescription
activityExecutionIdThe durable activity execution ID.
activityTypeThe durable type key of the activity.
activityClassThe PHP class name of the activity.
sequenceThe position of the activity within the workflow execution.
attemptNumberThe attempt number (starts at 1).

Workflow Events

WorkflowStarted

Dispatched when a workflow start is durably committed — meaning the first run has been created and the WorkflowStarted history event has been recorded.

use Workflow\V2\Events\WorkflowStarted;

Event::listen(WorkflowStarted::class, function (WorkflowStarted $event) {
Log::info('Workflow started', [
'instance_id' => $event->instanceId,
'run_id' => $event->runId,
'type' => $event->workflowType,
]);
});

This event also fires when a new run begins via continue-as-new.

WorkflowCompleted

Dispatched when a workflow run completes successfully.

use Workflow\V2\Events\WorkflowCompleted;

Event::listen(WorkflowCompleted::class, function (WorkflowCompleted $event) {
Log::info('Workflow completed', [
'instance_id' => $event->instanceId,
'run_id' => $event->runId,
]);
});

WorkflowFailed

Dispatched when a workflow run fails terminally.

Additional fields:

  • exceptionClass: The PHP exception class name.
  • message: The exception message.
use Workflow\V2\Events\WorkflowFailed;

Event::listen(WorkflowFailed::class, function (WorkflowFailed $event) {
Log::error('Workflow failed', [
'instance_id' => $event->instanceId,
'exception' => $event->exceptionClass,
'message' => $event->message,
]);
});

Activity Events

ActivityStarted

Dispatched when an activity task is claimed and execution begins.

use Workflow\V2\Events\ActivityStarted;

Event::listen(ActivityStarted::class, function (ActivityStarted $event) {
Log::info('Activity started', [
'activity' => $event->activityType,
'sequence' => $event->sequence,
'attempt' => $event->attemptNumber,
]);
});

ActivityCompleted

Dispatched when an activity completes successfully.

use Workflow\V2\Events\ActivityCompleted;

Event::listen(ActivityCompleted::class, function (ActivityCompleted $event) {
Log::info('Activity completed', [
'activity' => $event->activityType,
'execution_id' => $event->activityExecutionId,
]);
});

ActivityFailed

Dispatched when an activity fails terminally (all retries exhausted or non-retryable exception). Retryable failures that will be retried do not trigger this event.

Additional fields:

  • exceptionClass: The PHP exception class name.
  • message: The exception message.
use Workflow\V2\Events\ActivityFailed;

Event::listen(ActivityFailed::class, function (ActivityFailed $event) {
Log::error('Activity failed', [
'activity' => $event->activityType,
'exception' => $event->exceptionClass,
'message' => $event->message,
]);
});

Failure Events

FailureRecorded

Dispatched whenever a durable failure record is committed — for both workflow and activity terminal failures. This is the single hook for error-reporting integrations like Sentry or Bugsnag.

FieldDescription
failureIdThe durable failure record ID.
sourceKind"workflow_run" or "activity_execution".
sourceIdThe ID of the source (run ID or activity execution ID).
exceptionClassThe PHP exception class name.
messageThe exception message.
use Workflow\V2\Events\FailureRecorded;

Event::listen(FailureRecorded::class, function (FailureRecorded $event) {
// Report to Sentry, Bugsnag, etc.
report(new \RuntimeException(
"[{$event->sourceKind}] {$event->exceptionClass}: {$event->message}"
));
});

Timestamp Semantics

The committedAt field on all events represents commit time — the wall-clock time at which the durable record (history event or failure record) was written to the database. This is distinct from:

  • Workflow virtual time: The logical time inside the workflow execution (used by timers).
  • Attempt time: When a specific activity attempt started or finished.
  • Resume latency: How long after a timer was due the workflow actually resumed.

Commit time is the most useful timestamp for external integrations because it reflects when the state became durable and observable.

Lifecycle

A typical successful workflow lifecycle:

Workflow\V2\Events\WorkflowStarted
Workflow\V2\Events\ActivityStarted
Workflow\V2\Events\ActivityCompleted
Workflow\V2\Events\WorkflowCompleted

A workflow lifecycle with a terminal activity failure:

Workflow\V2\Events\WorkflowStarted
Workflow\V2\Events\ActivityStarted
Workflow\V2\Events\ActivityFailed
Workflow\V2\Events\FailureRecorded (source: activity_execution)
Workflow\V2\Events\WorkflowFailed
Workflow\V2\Events\FailureRecorded (source: workflow_run)

V1 Compatibility Events

V2 workflows also dispatch the legacy V1 event classes alongside V2 events. This means apps migrating from V1 to V2 continue to receive the events they already listen for — no listener changes required.

The following V1 events are dispatched automatically whenever their V2 counterpart fires:

V2 EventV1 Compatibility Event
Workflow\V2\Events\WorkflowStartedWorkflow\Events\WorkflowStarted
Workflow\V2\Events\WorkflowCompletedWorkflow\Events\WorkflowCompleted
Workflow\V2\Events\WorkflowFailedWorkflow\Events\WorkflowFailed
Workflow\V2\Events\ActivityStartedWorkflow\Events\ActivityStarted
Workflow\V2\Events\ActivityCompletedWorkflow\Events\ActivityCompleted
Workflow\V2\Events\ActivityFailedWorkflow\Events\ActivityFailed

V1 events use the V1 field names (workflowId, class, activityId, index, output) mapped from V2 durable identity:

  • workflowId is the V2 instanceId
  • activityId is the V2 activityExecutionId
  • index is the V2 sequence
  • output carries the exception class and message for failure events, or an empty string for completion events
  • arguments is always '[]' (V2 does not expose serialized arguments in lifecycle events)

StateChanged

The V1 Workflow\Events\StateChanged event is also dispatched as a compatibility adapter over V2 run-status transitions. It fires when a workflow starts (nullWorkflowRunningStatus), completes (WorkflowRunningStatusWorkflowCompletedStatus), or fails (WorkflowRunningStatusWorkflowFailedStatus).

use Workflow\Events\StateChanged;

Event::listen(StateChanged::class, function (StateChanged $event) {
// $event->initialState — the previous state (null for start)
// $event->finalState — the new state
// $event->model — the WorkflowRun model
// $event->field — 'status'
});

The model on a V2-dispatched StateChanged is the WorkflowRun Eloquent model, not the V1 stored workflow model. Listeners that access $event->model->id will get the run ID rather than the V1 stored workflow ID.

New V2 listeners

For new code, prefer the V2 events in Workflow\V2\Events — they carry richer durable identity (instance ID, run ID, workflow type, committed-at timestamp). The V1 compatibility events are provided for migration convenience.

Event Namespace

V2 events live in the Workflow\V2\Events namespace. Legacy V1 events in Workflow\Events are dispatched alongside V2 events as compatibility adapters, so existing V1 listeners continue to work without changes.