> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/medusajs/medusa/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Event Subscribers

> Learn how to create event subscribers in Medusa to react to domain events.

Event subscribers allow you to react to events in your Medusa application, such as order creation, product updates, or custom events from your workflows. They enable asynchronous, decoupled event-driven architectures.

## What is an Event Subscriber?

An event subscriber:

* Listens to one or more events from the event bus
* Executes asynchronously when events are triggered
* Has access to the dependency injection container
* Can trigger workflows, send notifications, or perform side effects
* Exports a configuration specifying which events to listen to

## Creating a Basic Subscriber

<Steps>
  <Step title="Create the Subscriber File">
    Create a file in `src/subscribers/` with a default export function and configuration:

    ```typescript title="src/subscribers/brand-created.ts" theme={null}
    import { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"

    export default async function brandCreatedHandler({
      event,
      container,
    }: SubscriberArgs<{ id: string }>) {
      const logger = container.resolve("logger")

      logger.info(`Brand created with ID: ${event.data.id}`)
    }

    export const config: SubscriberConfig = {
      event: "brand.created",
    }
    ```
  </Step>

  <Step title="Access Services and Modules">
    Resolve dependencies from the container:

    ```typescript theme={null}
    import { Modules } from "@medusajs/framework/utils"
    import { INotificationModuleService } from "@medusajs/framework/types"

    export default async function brandCreatedHandler({
      event,
      container,
    }: SubscriberArgs<{ id: string }>) {
      const notificationService = container.resolve<INotificationModuleService>(
        Modules.NOTIFICATION
      )

      await notificationService.createNotifications({
        to: "admin@example.com",
        channel: "email",
        template: "brand-created",
        data: {
          brand_id: event.data.id,
        },
      })
    }

    export const config: SubscriberConfig = {
      event: "brand.created",
    }
    ```
  </Step>

  <Step title="Execute Workflows">
    Trigger workflows in response to events:

    ```typescript theme={null}
    import { Modules } from "@medusajs/framework/utils"
    import { IWorkflowEngineService } from "@medusajs/framework/types"

    export default async function orderCreatedHandler({
      event,
      container,
    }: SubscriberArgs<{ id: string }>) {
      const workflowEngine = container.resolve<IWorkflowEngineService>(
        Modules.WORKFLOW_ENGINE
      )

      await workflowEngine.run("send-order-confirmation", {
        input: {
          order_id: event.data.id,
        },
      })
    }

    export const config: SubscriberConfig = {
      event: "order.created",
    }
    ```
  </Step>
</Steps>

## Subscriber Configuration

### Single Event

```typescript theme={null}
export const config: SubscriberConfig = {
  event: "brand.created",
}
```

### Multiple Events

```typescript theme={null}
export const config: SubscriberConfig = {
  event: ["brand.created", "brand.updated", "brand.deleted"],
}
```

### With Context

Add metadata to identify the subscriber:

```typescript theme={null}
export const config: SubscriberConfig = {
  event: "order.created",
  context: {
    subscriberId: "order-notification-handler",
  },
}
```

## Real-World Examples

### Payment Webhook Handler

This example shows processing payment webhooks and triggering workflows:

```typescript title="src/subscribers/payment-webhook.ts" theme={null}
import { processPaymentWorkflowId } from "@medusajs/core-flows"
import {
  IPaymentModuleService,
  ProviderWebhookPayload,
} from "@medusajs/framework/types"
import {
  Modules,
  PaymentActions,
  PaymentWebhookEvents,
} from "@medusajs/framework/utils"
import { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"

export default async function paymentWebhookHandler({
  event,
  container,
}: SubscriberArgs<ProviderWebhookPayload>) {
  const paymentService = container.resolve<IPaymentModuleService>(
    Modules.PAYMENT
  )

  const processedEvent = await paymentService.getWebhookActionAndData(
    event.data
  )

  if (!processedEvent.data) {
    return
  }

  // Skip unsupported actions
  if (
    processedEvent?.action === PaymentActions.NOT_SUPPORTED ||
    processedEvent?.action === PaymentActions.CANCELED ||
    processedEvent?.action === PaymentActions.FAILED
  ) {
    return
  }

  const workflowEngine = container.resolve(Modules.WORKFLOW_ENGINE)
  await workflowEngine.run(processPaymentWorkflowId, {
    input: processedEvent,
  })
}

export const config: SubscriberConfig = {
  event: PaymentWebhookEvents.WebhookReceived,
  context: {
    subscriberId: "payment-webhook-handler",
  },
}
```

### Send Notifications

Send notifications based on configurable events:

```typescript title="src/subscribers/order-notifications.ts" theme={null}
import { INotificationModuleService } from "@medusajs/framework/types"
import {
  ContainerRegistrationKeys,
  Modules,
  pickValueFromObject,
} from "@medusajs/framework/utils"
import { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"

type NotificationConfig = {
  event: string
  template: string
  channel: string
  to: string
  resource_id: string
  data: Record<string, string>
}

const handlerConfig: NotificationConfig[] = [
  {
    event: "order.created",
    template: "order-created-template",
    channel: "email",
    to: "order.email",
    resource_id: "order.id",
    data: {
      order_id: "order.id",
    },
  },
]

const configAsMap = handlerConfig.reduce(
  (acc: Record<string, NotificationConfig[]>, h) => {
    if (!acc[h.event]) {
      acc[h.event] = []
    }
    acc[h.event].push(h)
    return acc
  },
  {}
)

export default async function orderNotifications({
  event,
  container,
}: SubscriberArgs<any>) {
  const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
  const notificationService = container.resolve<INotificationModuleService>(
    Modules.NOTIFICATION
  )

  const handlers = configAsMap[event.name] ?? []
  const payload = event.data

  for (const handler of handlers) {
    try {
      await notificationService.createNotifications({
        template: handler.template,
        channel: handler.channel,
        to: pickValueFromObject(handler.to, payload),
        trigger_type: handler.event,
        resource_id: pickValueFromObject(handler.resource_id, payload),
        data: Object.entries(handler.data).reduce((acc, [key, value]) => {
          acc[key] = pickValueFromObject(value, payload)
          return acc
        }, {}),
      })
    } catch (err) {
      logger.error(`Failed to send notification for ${event.name}`, err.message)
    }
  }
}

export const config: SubscriberConfig = {
  event: handlerConfig.map((h) => h.event),
  context: {
    subscriberId: "order-notifications-handler",
  },
}
```

### Sync Data to External System

```typescript title="src/subscribers/brand-sync.ts" theme={null}
import { BRAND_MODULE } from "../modules/brand"
import { IBrandModuleService } from "../modules/brand/types"
import { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"

export default async function syncBrandToExternal({
  event,
  container,
}: SubscriberArgs<{ id: string }>) {
  const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
  const brandService = container.resolve<IBrandModuleService>(BRAND_MODULE)

  try {
    const brand = await brandService.retrieveBrand(event.data.id)

    // Sync to external system
    await fetch("https://external-api.com/brands", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(brand),
    })

    logger.info(`Synced brand ${brand.id} to external system`)
  } catch (error) {
    logger.error(`Failed to sync brand ${event.data.id}`, error)
  }
}

export const config: SubscriberConfig = {
  event: ["brand.created", "brand.updated"],
  context: {
    subscriberId: "brand-external-sync",
  },
}
```

## Event Data Types

Type your event data for type safety:

```typescript theme={null}
interface BrandCreatedEventData {
  id: string
  name: string
}

export default async function brandCreatedHandler({
  event,
  container,
}: SubscriberArgs<BrandCreatedEventData>) {
  // event.data is now typed
  const brandId = event.data.id
  const brandName = event.data.name
}
```

## Built-in Events

Medusa emits events for core entities:

* `order.created`, `order.updated`, `order.canceled`
* `product.created`, `product.updated`, `product.deleted`
* `customer.created`, `customer.updated`
* `payment.created`, `payment.captured`
* `fulfillment.created`, `fulfillment.shipped`

And many more. See the [Events concept](/concepts/events) for more information about the event system.

## Custom Events from Workflows

Emit custom events using workflow hooks:

```typescript title="src/workflows/brand/create-brand.ts" theme={null}
import { createHook, createWorkflow } from "@medusajs/framework/workflows-sdk"

export const createBrandWorkflow = createWorkflow(
  "create-brand",
  (input) => {
    const brand = createBrandStep(input)

    const brandCreated = createHook("brandCreated", {
      id: brand.id,
      name: brand.name,
    })

    return new WorkflowResponse(brand, {
      hooks: [brandCreated],
    })
  }
)
```

Subscribe to the hook:

```typescript title="src/subscribers/brand-created.ts" theme={null}
export const config: SubscriberConfig = {
  event: "brandCreated",
}
```

## Error Handling

Handle errors gracefully to prevent subscriber failures from affecting the main flow:

```typescript theme={null}
export default async function brandHandler({
  event,
  container,
}: SubscriberArgs<{ id: string }>) {
  const logger = container.resolve("logger")

  try {
    // Your logic here
  } catch (error) {
    logger.error(
      `Failed to process brand event for ${event.data.id}`,
      error.message
    )
    // Don't throw - let the subscriber complete
  }
}
```

## Best Practices

* Keep subscribers focused on a single responsibility
* Use workflows for complex business logic instead of putting it in subscribers
* Handle errors gracefully - don't let subscriber failures break the main flow
* Use typed event data for type safety
* Add context with `subscriberId` for easier debugging
* Use the logger for debugging and error tracking
* Consider idempotency when handling events (events may be delivered multiple times)
* Don't perform long-running operations synchronously - use workflows or background jobs

## Next Steps

<CardGroup cols={2}>
  <Card title="Create Workflows" icon="diagram-project" href="/development/workflows">
    Build workflows triggered by subscribers
  </Card>

  <Card title="Scheduled Jobs" icon="clock" href="/development/jobs">
    Create time-based background tasks
  </Card>
</CardGroup>
