Commerce Events
The domain events your extension's transformers produce — constructor signatures and extension-specific guidance.
Last updated: April 9, 2026
Commerce Events
Commerce events are the domain events your extension produces when something happens in the integrated platform. This includes sales, refunds, and coupon applications. Your transformer returns one of these event instances (or null to skip), and Siren’s core handles everything downstream. See Event Bindings and Transformers for how to wire these into your Integration class.
All commerce events live in Siren\Commerce\Events\ and implement PHPNomad’s Event interface. Extensions never dispatch these events directly. They return them from transformer callbacks, and the framework broadcasts them.
What happens when a sale is detected?
SaleTriggered is the primary entry point for the entire conversion pipeline. When your transformer determines that an order should be tracked, it returns a SaleTriggered instance carrying the opportunity, the line items, and a reference back to the external order.
return new SaleTriggered(
$opportunity->getId(), // which affiliate referral led to this sale
$transactionDetails, // line items from your adapter (array of detail arrays)
'wc', // your extension's ID (matches Integration::getId())
$orderId, // external order ID for mapping back to the platform
'wc_order' // external type identifier for the mapping table
);
The $transactionDetails array comes from your adapter’s toArray() method. Each element represents a line item with a name, description, type, per-unit value in cents, quantity, and currency code. See the Adapters guide for the full format.
Once SaleTriggered broadcasts, Siren’s core creates the transaction, evaluates which programs apply, builds conversion records, and generates obligations. Your extension’s job ends at producing the event. See the SaleTriggered event reference for full payload and listener details.
The transformer is responsible for three checks before returning this event: locating the affiliate opportunity for the customer, verifying the order hasn’t already been processed (via the mapping table), and confirming the order has line items. If any check fails, return null.
How does Siren know when payment is confirmed?
TransactionCompleted fires when an order reaches its final approved state. This typically happens when a payment gateway confirms the charge. This is separate from SaleTriggered because many platforms create orders before payment clears.
return new TransactionCompleted($transaction);
The transformer for this event is simpler than the sale transformer. It looks up the existing Siren transaction by querying the mapping table for the external order ID, then wraps it in the event. If no mapping exists (the order was never tracked by Siren), return null.
When this event fires, Siren marks associated conversions as approved and advances obligations from draft to pending status, making them eligible for payout. See the TransactionCompleted event reference for full details.
What triggers a refund?
RefundTriggered fires when a completed order is reversed. This could be an explicit refund, a cancellation, or an admin trashing the order.
return new RefundTriggered($transaction);
Like TransactionCompleted, the transformer looks up the Siren transaction via the mapping table. If no mapping exists, the order was never tracked, so there’s nothing to refund. Return null.
When this event fires, Siren cancels all conversions tied to the transaction, marks the transaction as refunded, and adjusts incentive and distribution metrics. See the RefundTriggered event reference for full details. You typically bind this to multiple platform hooks to cover all the ways an order can be reversed:
RefundTriggered::class => [
['action' => 'woocommerce_order_status_completed_to_failed', 'transformer' => $refundCallback],
['action' => 'woocommerce_order_status_completed_to_cancelled', 'transformer' => $refundCallback],
['action' => 'woocommerce_order_status_completed_to_refunded', 'transformer' => $refundCallback],
['action' => 'wc-completed_to_trash', 'transformer' => $refundCallback],
],
How do coupon codes create engagements?
CouponApplied fires when a customer uses a coupon code during checkout. This event exists specifically to create an engagement that links the coupon code back to the collaborator who owns it.
return new CouponApplied(strtoupper($couponCode), $opportunity);
Two things to note: coupon codes are always uppercased for consistent matching, and this transformer uses CurrentUserOpportunity locators instead of VisitorOpportunity because the coupon is applied by the logged-in customer during checkout, not tied to a specific order.
When this event fires, Siren’s BoundCouponUsed engagement trigger strategy looks up the collaborator from the coupon code via the alias table, then creates engagements for every active program that collaborator participates in with the boundCouponUsed trigger enabled. See the CouponApplied event reference for full details.
How are subscription renewals tracked?
RenewalTriggered fires when a subscription renewal payment completes. This is the most complex commerce event because it needs to link back to the original transaction so commissions continue on recurring revenue.
return new RenewalTriggered(
$originalTransaction, // the Siren transaction from the initial purchase
$transactionDetails, // line items for the renewal order
$orderId, // external renewal order ID
'wc_order' // external type
);
The transformer must trace the renewal order back to the original subscription’s first order, then look up the Siren transaction for that original order via the mapping table. It also performs the standard duplicate check on the renewal order ID.
Renewal support is conditional. WooCommerce only adds the binding when WooCommerce Subscriptions is installed:
if (class_exists(WC_Subscription::class)) {
$triggers[RenewalTriggered::class] = [
['action' => 'woocommerce_subscription_renewal_payment_complete',
'transformer' => $renewalCallback],
];
}
See the RenewalTriggered event reference for full details.
What about leads and form submissions?
LeadTriggered is the non-monetary equivalent of SaleTriggered. It fires when a form submission or signup action occurs, carrying an opportunity ID and source identifier but no transaction details (leads have no monetary value at trigger time).
Form-based integrations like Gravity Forms use this event instead of SaleTriggered. The downstream pipeline creates conversions and obligations using the lead incentive type rather than sale-based incentive types. See the LeadTriggered event reference for full details.
For the full lifecycle of a tracked sale — from hook to conversion to payout — see the Architecture Overview. For complete event payload documentation, listener details, and the execution order across all event types, see the Events Reference.