This guide explains how to add new event handlers to your extension to receive and process platform events.
Event handlers allow your extension to react to changes in the SoftwareOne Marketplace Platform. When a business object (order, subscription, agreement, etc.) changes state, the platform can notify your extension by sending an event to a designated endpoint.
First, declare the event subscription in your meta.yaml file under the events section:
events:
- event: platform.commerce.order.status_changed
condition: "and(eq(status,Processing),eq(product.id,{{ settings.product_id }}))"
path: "/api/v1/events/orders"
task: trueThe event key follows a dotted notation pattern:
{src}.{module}.{object}.{action}
Components:
src: Alwaysplatformmodule: The platform module (e.g.,commerce,accounts,catalog)object: The business object type (e.g.,order,subscription,agreement)action: The event trigger, which can be:created— Object was createdupdated— Object was modifieddeleted— Object was removedstatus_changed— Object status changed<custom>— Any custom action defined by the platform
Examples:
platform.commerce.order.created
platform.commerce.subscription.status_changed
platform.accounts.agreement.updated
platform.catalog.product.deletedYou can optionally apply a filter using the condition field to receive only events matching specific criteria. The filter uses a function-based syntax and operates on the business object that triggered the event.
Syntax:
condition: "and(eq(field,value),ne(other_field,value))"Available functions:
eq(field, value)— Equal tone(field, value)— Not equal toand(expr1, expr2, ...)— Logical ANDor(expr1, expr2, ...)— Logical OR
Examples:
# Only orders with status "Processing" for a specific product
condition: "and(eq(status,Processing),eq(product.id,PRD-123-456))"
# Subscriptions that are either Active or Suspended
condition: "or(eq(status,Active),eq(status,Suspended))"
# Orders above a certain value
condition: "eq(highValue,true)"You can reference values from settings.yaml using Jinja2 template syntax:
condition: "eq(product.id,{{ settings.product_id }})"path: The endpoint path in your extension that will receive the event (must match the route in api.py)task: Set totrueif the platform should create a task to track event processing
Create a new POST endpoint in backend/app/api.py to receive the event:
@router.post("/events/orders")
async def process_order(event: Event):
# Your event processing logic here
print(f"Received order event: {event.object.id}")
print(f"Event type: {event.details.event_type}")
# Return appropriate response
return EventResponse.ok()The endpoint path must match the path specified in meta.yaml.
The platform sends event data as JSON matching the Event Pydantic model defined in backend/app/schema.py:
class Event(BaseSchema):
id: str # Unique message ID for correlation
object: Object # Information about the affected object
details: Details # Event metadata (type, timing)
task: Task | None = None # Optional task informationNested models:
class Object(BaseSchema):
id: str # Platform object ID
name: str # Object name
object_type: str # Object type (e.g., "Order", "Subscription")
class Details(BaseSchema):
event_type: str # The full event key (e.g., "platform.commerce.order.created")
enqueue_time: datetime # When platform received the event
delivery_time: datetime # When platform delivered to extension
class Task(BaseSchema):
id: str # Platform task ID if task tracking enabledExample event payload:
{
"id": "MSG-001",
"object": {
"id": "ORD-1234-5678",
"name": "Order for Product X",
"objectType": "Order"
},
"details": {
"eventType": "platform.commerce.order.status_changed",
"enqueueTime": "2026-03-05T10:30:00Z",
"deliveryTime": "2026-03-05T10:30:01Z"
},
"task": {
"id": "TSK-9876-5432"
}
}Your endpoint must return an EventResponse indicating how the platform should proceed:
class EventResponse(BaseSchema):
response: Literal["OK", "Delay", "Cancel"]
delay: int | None # Required when response is "Delay"Response options:
-
OK — Event processed successfully
return EventResponse.ok()
-
Reschedule — Retry the event after a specified delay (in seconds)
return EventResponse.reschedule(seconds=300) # Retry in 5 minutes
-
Cancel — Stop processing this event (no retries)
return EventResponse.cancel()
events:
- event: platform.commerce.subscription.status_changed
condition: "eq(status,Active)"
path: "/api/v1/events/subscriptions"
task: truefrom fastapi import APIRouter
from app.schema import Event, EventResponse
router = APIRouter(prefix="/api/v1")
@router.post("/events/subscriptions")
async def process_subscription_event(event: Event):
"""
Handle subscription status change events.
Only receives events for subscriptions that became Active (due to filter).
"""
subscription_id = event.object.id
event_type = event.details.event_type
task_id = event.task.id if event.task else None
print(f"Processing subscription {subscription_id}")
print(f"Event: {event_type}")
print(f"Task: {task_id}")
try:
# Your business logic here
# e.g., provision resources, send notifications, update external systems
# Success
return EventResponse.ok()
except TemporaryError:
# Retry in 5 minutes
return EventResponse.reschedule(seconds=300)
except PermanentError:
# Don't retry
return EventResponse.cancel()- Use filters to reduce noise and only receive relevant events
- Return appropriate responses:
- Use
OKfor successful processing - Use
Delayfor temporary failures (network issues, rate limits) or for long running process - Use
Cancelfor permanent failures (invalid data, business rule violations) or to just skip the event
- Use
- Log event IDs for troubleshooting and correlation with platform logs
- Enable task tracking (
task: true) for long-running or critical operations - Handle idempotency — the same event may be delivered multiple times
- Process quickly — long-running operations should be offloaded to background tasks
- Event not received: Check that the
eventkey matches the platform event name exactly - All events received: Verify your
conditionfilter syntax - Settings not interpolated: Ensure
settings.yamlcontains the referenced values - Path mismatch: Confirm the
pathin meta matches the route decorator in api.py
- Project Structure — Understanding the codebase layout
- meta.yaml — Event configuration reference
- schema.py — Complete model definitions
- api.py — API implementation examples