# How to Observe the Execution of Medusa Workflows: 4 Monitoring Methods

> Learn to observe Medusa workflow execution using 4 monitoring methods. Subscribe to lifecycle events via Redis Pub/Sub, callbacks, or REST APIs for clear insights.

- Repository: [Medusa/medusa](https://github.com/medusajs/medusa)
- Tags: how-to-guide
- Published: 2026-05-19

---

**You can observe Medusa workflow execution by subscribing to lifecycle events emitted through the `WorkflowOrchestratorService`, which broadcasts via Redis Pub/Sub, in-process callbacks, and REST APIs consumed by the Admin UI.**

Medusa workflows handle complex, multi-step commerce operations through a durable execution engine powered by the **workflow orchestrator** in the `@medusajs/workflow-engine-redis` module. Each workflow run instantiates a **DistributedTransaction** that emits granular events—`onBegin`, `onStepBegin`, `onStepSuccess`, `onStepFailure`, and `onFinish`—enabling real-time observability. This article explores four concrete methods to monitor these executions based on the implementation in the `medusajs/medusa` repository.

## Understanding the Workflow Orchestration Architecture

The **WorkflowOrchestratorService** ([`packages/modules/workflow-engine-redis/src/services/workflow-orchestrator.ts`](https://github.com/medusajs/medusa/blob/main/packages/modules/workflow-engine-redis/src/services/workflow-orchestrator.ts)) manages the execution lifecycle and event distribution for all workflows. When you invoke `workflow.run()`, the orchestrator creates a **DistributedTransaction** and builds event handlers through the internal `buildWorkflowEvents` method.

All lifecycle events funnel through the private `notify` method, which performs two critical operations: it publishes JSON payloads to Redis on the `orchestrator:<workflowId>` channel (for asynchronous flows) and schedules `processSubscriberNotifications` to invoke local subscriber callbacks. The service maintains a static `subscribers` Map that persists listeners across multiple workflow runs unless explicitly cleared by the `onFinish` event, which automatically removes transaction-specific handlers to prevent memory leaks.

## Four Methods to Observe Workflow Execution

### 1. Programmatic Subscription Using `subscribe()`

The most direct approach calls `WorkflowOrchestratorService.subscribe()` with a `workflowId` and callback function. This registers a handler that receives `NotifyOptions` containing `eventType`, `step`, `result`, and `errors` whenever the workflow state changes.

### 2. Admin UI Monitoring

The Medusa Admin dashboard provides visual observability through the `WorkflowExecutionListTable` component ([`packages/admin/dashboard/src/routes/workflow-executions/workflow-execution-list/workflow-execution-list.tsx`](https://github.com/medusajs/medusa/blob/main/packages/admin/dashboard/src/routes/workflow-executions/workflow-execution-list/workflow-execution-list.tsx)). The UI polls the `/admin/workflows-executions` REST endpoint, which internally calls `listWorkflowExecutions()` on the orchestrator service to retrieve historical and active transaction states.

### 3. Redis Pub/Sub Channel Listeners

External services can monitor workflows by subscribing to the Redis channel `orchestrator:<workflowId>`. The orchestrator's `notify` method publishes execution events to this topic using the injected `redisPublisher`, allowing language-agnostic consumers to receive real-time updates without direct code integration.

### 4. Test Utilities with `waitWorkflowExecutions()`

Integration tests use the `waitWorkflowExecutions(container)` helper from `@medusajs/medusa-test-utils` ([`packages/medusa-test-utils/src/medusa-test-runner-utils/wait-workflow-executions.ts`](https://github.com/medusajs/medusa/blob/main/packages/medusa-test-utils/src/medusa-test-runner-utils/wait-workflow-executions.ts)). This utility repeatedly queries `listWorkflowExecutions()` until no transactions remain in a running state, ensuring test assertions execute only after background workflow processing completes.

## Practical Implementation Examples

### Subscribing to Workflow Events in Application Code

Attach a subscriber before triggering the workflow to capture every lifecycle event:

```typescript
import { Modules } from "@medusajs/framework/utils"
import { WorkflowOrchestratorService } from "@medusajs/workflow-engine-redis"
import { MedusaContainer } from "@medusajs/framework/types"

async function registerWorkflowObserver(container: MedusaContainer) {
  const orchestrator = container.resolve(
    Modules.WORKFLOW_ENGINE
  ) as WorkflowOrchestratorService

  orchestrator.subscribe({
    workflowId: "order.create",
    subscriber: async ({ eventType, step, result, errors }) => {
      console.log(`[${eventType}] Step: ${step?.id}`, { result, errors })
    },
  })
}

```

*Source:* `WorkflowOrchestratorService.subscribe` in [`packages/modules/workflow-engine-redis/src/services/workflow-orchestrator.ts`](https://github.com/medusajs/medusa/blob/main/packages/modules/workflow-engine-redis/src/services/workflow-orchestrator.ts)

### Synchronizing Tests with Background Execution

Prevent flaky tests by ensuring all workflow activity finishes before assertions:

```typescript
import { waitWorkflowExecutions } from "@medusajs/medusa-test-utils"

// After triggering workflows in your test...
await waitWorkflowExecutions(container)
// All executions have reached a terminal state

```

*Source:* [`packages/medusa-test-utils/src/medusa-test-runner-utils/wait-workflow-executions.ts`](https://github.com/medusajs/medusa/blob/main/packages/medusa-test-utils/src/medusa-test-runner-utils/wait-workflow-executions.ts)

### Monitoring via External Redis Consumers

Connect any Redis client to observe workflow events from outside the Medusa application:

```typescript
import Redis from "ioredis"

const redis = new Redis()
const channel = "orchestrator:order.create"

redis.subscribe(channel, () => {
  console.log(`Subscribed to ${channel}`)
})

redis.on("message", (channel, message) => {
  const { instanceId, data } = JSON.parse(message)
  const { eventType, step, result } = data
  console.log(`Event from ${instanceId}:`, { eventType, step, result })
})

```

The orchestrator publishes this payload in the `notify` method via `redisPublisher.publish` on lines 77-79 of the orchestrator service source.

## Summary

- **WorkflowOrchestratorService** emits lifecycle events (`onBegin`, `onStepBegin`, `onStepSuccess`, `onStepFailure`, `onFinish`) for every **DistributedTransaction**.
- Use `subscribe()` with a specific `workflowId` to receive in-process callbacks containing execution metadata.
- Monitor workflows externally by subscribing to the `orchestrator:<workflowId>` Redis channel.
- Integration tests should call `waitWorkflowExecutions()` to block until asynchronous workflow processing completes.
- The **static `subscribers` map** persists listeners across runs, but `onFinish` automatically cleans up transaction-specific handlers to prevent memory leaks.

## Frequently Asked Questions

### What lifecycle events does the Medusa workflow orchestrator emit?

The orchestrator emits `onBegin` when the transaction starts, `onStepBegin` before each step executes, `onStepSuccess` or `onStepFailure` upon step completion, and `onFinish` when the workflow reaches a terminal state. These events contain the step identifier, execution results, and error objects.

### How can an external service listen to Medusa workflow events without modifying the core application?

External services can subscribe to the Redis Pub/Sub channel `orchestrator:<workflowId>` where `<workflowId>` matches the workflow registration key. The orchestrator publishes JSON payloads to this channel for all asynchronous workflow executions, allowing polyglot consumers to receive real-time updates.

### Why do my workflow subscribers stop receiving events after a workflow completes?

The **WorkflowOrchestratorService** automatically clears transaction-specific subscribers when it emits the `onFinish` event. This design prevents memory leaks in long-running server processes. To observe subsequent runs, ensure your subscription logic handles re-registration or subscribes at the application bootstrap level rather than per-request.

### How do I verify that all background workflows have finished during integration testing?

Import `waitWorkflowExecutions` from `@medusajs/medusa-test-utils` and await it after triggering workflows in your test case. This helper polls `listWorkflowExecutions()` until no transactions remain in a running state, guaranteeing that all side effects have persisted before your test assertions execute.