An event tells your plugin that something happened in the store. Commera records the event, commits, and then calls your handler in a background job. A handler cannot stop or change the action that caused the event. To stop an action, use a hook.
Register a handler
Add a commera_events dict to hooks.py. Each key is an event name, and each value is a list of handlers.
commera_events = {
"order_paid": ["my_app.orders.on_order_paid"],
"order_refunded": ["my_app.orders.on_order_refunded"],
}
bench migrate prints a warning and writes an Error Log for a key that is not an event name, for example order_payed.
The handler gets one argument, a CommeraEvent. Commera ignores its return value.
import frappe
from commera.sdk.events import CommeraEvent
def on_order_paid(event: CommeraEvent):
if frappe.db.exists("My Job", {"event_id": event.id}):
return
frappe.get_doc({"doctype": "My Job", "event_id": event.id, "sales_order": event.reference_name}).insert()
The first part of the dotted path must be your app's name. Commera uses it to find the plugin that owns the handler.
The CommeraEvent object
commera.sdk.events.CommeraEvent is a frozen dataclass.
| Field | Type | Value |
|---|---|---|
id |
str |
A stable ID. It is the same on each retry and on each repeat delivery |
name |
str |
The event name, for example order_paid |
reference_doctype |
str |
Sales Order for order events, Item for product and stock events |
reference_name |
str |
The name of the reference document |
data |
dict |
The event data, see below. {} when there is no data |
created_at |
datetime |
When Commera recorded the event |
The id of an order event is <order>-<event>, for example SAL-ORD-2026-00010-order_placed. An order event that can occur more than one time adds a key: <order>-<event>-<key>. A product or stock event is <event>-<key>. The item code is not in the id, so use event.reference_name.
Order events
Commera fires order events only for store orders. A store order is a Sales Order with Order Type set to Shopping Cart (commera.sdk.STORE_ORDER_TYPE). An order that staff make in Desk is not a store order.
| Event | When it fires | How often |
|---|---|---|
order_placed |
The shopper places the order. A cash on delivery (COD) order is still a draft Sales Order | Once |
order_paid |
Gateway orders: right after order_placed. COD orders: when the order is fully billed and every invoice is paid |
Once |
order_cancelled |
The Sales Order is cancelled, from the storefront, the dashboard or Desk | Once |
order_fulfilled |
The order status changes to Shipped, Delivered, Partially Returned or Returned, whichever comes first | Once |
order_delivered |
The order status changes to Delivered | Once |
order_returned |
The order status changes to Partially Returned or Returned | Once per status |
order_refunded |
A refund Payment Entry for the order is submitted | Once per Payment Entry |
Order data
Every order event carries a snapshot of the order, taken when the event fires.
| Key | Value |
|---|---|
customer |
The Customer name |
currency |
The order currency |
grand_total |
The order grand total |
payment_mode |
The checkout payment mode, for example COD |
docstatus |
0 draft, 1 submitted, 2 cancelled, when the event fired |
shipping_address |
The address the order ships to: its shipping address, else its billing address. None when it has neither |
shipping_address is a dict with the Address name and address_title, address_line1, address_line2, city, state, pincode, country and phone. get_order returns the same dict.
order_refunded adds payment_entry (the refund Payment Entry, also the key in event.id) and amount (the part of this refund that goes to this order, in the order currency, with tax and shipping).
order_returned adds status (Partially Returned or Returned) and partial (True for Partially Returned).
The snapshot can be old when your handler runs. For current values, call get_order.
When fulfilment events fire
Commera sets the order status from the order's Delivery Notes and Shipping Requests.
- A shipment status of Pickup Scheduled, In Transit, Out For Delivery, Undelivered or Lost sets Shipped. Commera fires
order_fulfilled. - A shipment status of Delivered sets Delivered. Commera fires
order_fulfilled, thenorder_delivered. - With no Shipping Request, a submitted Delivery Note sets Delivered.
- A drop-ship line (Supplier delivers to Customer) counts as delivered when its Purchase Order is received. An order with both kinds of lines moves only as far as its slower part.
- A return Delivery Note for part of the delivered quantity sets Partially Returned. For all of it, Returned. Commera fires
order_returned. - A shipment status of RTO sets Returned, and
order_returnedhaspartialset toFalse.
To move an order to Shipped from your plugin, call record_shipment.
When order_refunded fires
Commera fires order_refunded for each submitted Payment Entry of type Pay that it can link to a store order: a refund from the dashboard, the refund that a cancel pays out, or a Payment Entry that references the order, one of its invoices or its credit note. When one Payment Entry refunds more than one order, each order gets its own event.
Product and stock events
These events fire only for items that are on the storefront.
| Event | When it fires | event.data |
|---|---|---|
product_updated |
A product changes. One event per product for each transaction | item_code (the template Item), changed (a sorted list) |
inventory_changed |
Stock changes in the store warehouse (Commera Settings > Ecommerce Warehouse) | warehouse, actual_qty, available_qty |
changed holds one or more of these values:
| Value | What changed |
|---|---|
details |
The product Item or one of its size Items was saved |
options |
An option was saved without a publish change, or sizes were added or removed |
price |
A selling Item Price of the product or a size was added, changed or deleted |
published |
An option was published or unpublished |
A new product fires nothing until you publish it. changed tells you what to read again, not the new values.
Commera records these events only when at least one plugin handles them.
Delivery rules
- After commit. Commera queues the delivery after the transaction commits. If the action rolls back, the event does not exist.
- Background job. Each delivery runs on the
defaultqueue. Do not usefrappe.local.request, a shopper session or a cart cookie. - User. The handler runs as
commera-plugins@commera.local. This user has every desk role, so permission checks pass. Documents that your handler makes show this user as the owner. - Lanes. Deliveries in one lane run one at a time, oldest first. For order events, each plugin has one lane per order: your
order_placedhandler finishes before yourorder_paidhandler starts. For item events, each plugin has one lane per item and event. Plugins never share a lane. - At least once. Commera never fires the same
event.idtwice, but it can deliver it twice. A worker can stop after your handler commits and before Commera marks the delivery done. - Commits. Commera commits after each handler that succeeds. Writes that you commit stay, even if the handler raises later. Commera rolls back the rest.
Retries
When a handler raises, Commera rolls back the uncommitted writes, writes an Error Log with the title commera_events["<event>"] hook failed: <handler>, and tries again later. The delays are 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. After the sixth failed attempt, the delivery is Failed.
A scheduled job starts the deliveries that are due, on each scheduler tick. The tick is 4 minutes by default, so a retry runs up to one tick after its delay. Make sure that the scheduler is enabled on the site, or retries do not run. A delivery that stays Running for more than 30 minutes counts as a failed attempt.
While a delivery waits for a retry, the later deliveries in its lane wait too. When it is Failed, they run.
See deliveries
- Settings > Plugins, then Open on your plugin: the Recent deliveries list shows each delivery with its event, order, status and time. The statuses are Queued, Sending, Retrying, Sent and Failed. Only a System Manager sees this list.
- On the order page in
/commera, a red alert says that your plugin couldn't process the order when one of its deliveries for that order is Failed. - In Desk, the Commera Event record of the event lists its deliveries with the status Queued, Running, Done or Failed.
A System Manager sees a Retry button on a Failed delivery, in the list and on the order page alert.
Retention
Commera keeps order events, because their rows stop an order event from firing twice. Log Settings deletes product_updated and inventory_changed events after 14 days, unless a delivery of the event is still waiting.