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.
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
Create the Subscriber File
Create a file in src/subscribers/ with a default export function and configuration: src/subscribers/brand-created.ts
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" ,
}
Access Services and Modules
Resolve dependencies from the container: 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" ,
}
Execute Workflows
Trigger workflows in response to events: 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" ,
}
Subscriber Configuration
Single Event
export const config : SubscriberConfig = {
event: "brand.created" ,
}
Multiple Events
export const config : SubscriberConfig = {
event: [ "brand.created" , "brand.updated" , "brand.deleted" ],
}
With Context
Add metadata to identify the subscriber:
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:
src/subscribers/payment-webhook.ts
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:
src/subscribers/order-notifications.ts
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
src/subscribers/brand-sync.ts
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:
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 for more information about the event system.
Custom Events from Workflows
Emit custom events using workflow hooks:
src/workflows/brand/create-brand.ts
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:
src/subscribers/brand-created.ts
export const config : SubscriberConfig = {
event: "brandCreated" ,
}
Error Handling
Handle errors gracefully to prevent subscriber failures from affecting the main flow:
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
Create Workflows Build workflows triggered by subscribers
Scheduled Jobs Create time-based background tasks