Engagements
Attribution engagements — data model, status lifecycle, REST API, and PHP data access for bindings, scoring, and opportunity lookups.
Last updated: April 9, 2026
Engagements
An engagement represents a confirmed attribution event linking a collaborator to an opportunity within a specific program. When a visitor arrives through a collaborator’s referral link, uses a bound coupon code, or is manually attributed, Siren creates an engagement to record that the collaborator participated. Engagements sit upstream of conversions in the attribution pipeline: an engagement captures the collaborator’s involvement, and when the opportunity converts, the engagement feeds into the conversion record.
Each engagement carries a score that reflects its attribution weight. When multiple engagements exist for the same opportunity and program (for example, repeated visits), Siren merges them and accumulates the score. This merged score is what ultimately appears on any conversion produced from the engagement.
Engagements are read-only through the REST API. They are created and managed internally by the engagement trigger system in response to events like site visits, coupon usage, and manual attribution.
The engagement object
| Field | REST name | PHP getter | Type | Description |
|---|---|---|---|---|
| ID | id | getId() | integer | Unique identifier |
| Opportunity | opportunityId | getOpportunityId() | integer | The opportunity this engagement is linked to |
| Program | programId | getProgramId() | integer | The program under which this engagement was attributed |
| Collaborator | collaboratorId | getCollaboratorId() | integer | The collaborator who receives credit |
| Score | score | getScore() | integer | Attribution score representing the weight of this engagement |
| Status | status | getStatus() | string | Lifecycle status: active, pending, or complete |
| Created | dateCreated | getCreatedDate() | datetime | When the engagement was first recorded |
| Modified | dateModified | getModifiedDate() | datetime | When the engagement was last updated |
Extended fields (REST only, via ?fields=)
These fields are resolved dynamically by the REST API field resolver system and must be explicitly requested:
| Field | Type | Description |
|---|---|---|
collaboratorName | string | Display name of the collaborator who triggered this engagement |
programName | string | Name of the program this engagement belongs to |
The EngagementType object
Engagement types define the trigger strategies that can create engagements. Each type represents a different mechanism for detecting collaborator involvement.
| Field | Type | Description |
|---|---|---|
id | string | Unique type identifier (e.g., referredSiteVisit, boundCouponUsed) |
name | string | Human-readable display name |
The core trigger strategies are:
referredSiteVisit. Fires when a visitor arrives through a collaborator’s referral link. This is the most common engagement type.boundCouponUsed. Fires when a coupon code bound to a collaborator is applied to an order. Only available when a coupon-supporting integration is active.manual. Fires when a manager manually attributes a transaction to a collaborator.
Status lifecycle
Active
The collaborator's referral is currently being tracked against the linked opportunity.
Pending
The opportunity has triggered a conversion type and the engagement is waiting for conversion processing.
Complete
Conversions have been awarded. The engagement's attribution work is done.
| Status | Description |
|---|---|
active | The engagement is live. The collaborator’s referral is currently being tracked against the linked opportunity. New engagements always start in this state. |
pending | The opportunity has triggered a conversion type, and the engagement is waiting for conversion processing. Active engagements transition to pending when the associated opportunity completes for a matching conversion type. |
complete | Conversions have been awarded for this engagement’s opportunity and program. The engagement’s attribution work is done. This transition happens automatically when the ConversionsAwarded event fires. |
Accessing engagement data
# List engagements for a specific collaborator
curl -X GET "https://your-site.com/wp-json/siren/v1/engagements?collaboratorId=42&fields=id,score,status,programName" \
-H "Authorization: Bearer YOUR_TOKEN"
# Get a single engagement with extended fields
curl -X GET "https://your-site.com/wp-json/siren/v1/engagements/15?fields=id,score,status,collaboratorName,programName" \
-H "Authorization: Bearer YOUR_TOKEN" use Siren\Engagements\Core\Datastores\Engagement\Interfaces\EngagementDatastore;
class AttributionReport
{
protected EngagementDatastore $engagements;
public function __construct(EngagementDatastore $engagements)
{
$this->engagements = $engagements;
}
public function getActiveForOpportunity(int $opportunityId): array
{
return $this->engagements->andWhere([
['column' => 'opportunityId', 'operator' => '=', 'value' => $opportunityId],
['column' => 'status', 'operator' => '=', 'value' => 'active']
]);
}
} use Siren\Engagements\Core\Facades\Engagements;
$active = Engagements::andWhere([
['column' => 'opportunityId', 'operator' => '=', 'value' => $opportunityId],
['column' => 'status', 'operator' => '=', 'value' => 'active']
]);
$engagement = Engagements::getById(42); Siren is built on an event-driven architecture. Engagements are created automatically when attribution triggers fire. If you find yourself writing code that creates engagement records directly, consider whether your use case would be better served by registering a custom engagement trigger strategy so the normal pipeline handles it.
PHP domain methods
Retrieving bindings for an opportunity and program
getBindings returns all active engagements for a given opportunity and program combination. This is the primary method for inspecting which collaborators are currently credited within a specific program context. Results can be sorted and limited.
The method signature is getBindings(int $opportunityId, int $programId, string $orderBy = 'dateCreated', string $order = 'ASC', ?int $limit = null). Only active engagements are returned. The method filters by status = 'active' internally.
use Siren\Engagements\Core\Datastores\Engagement\Interfaces\EngagementDatastore;
class BindingInspector
{
protected EngagementDatastore $engagements;
public function __construct(EngagementDatastore $engagements)
{
$this->engagements = $engagements;
}
public function listBindings(int $opportunityId, int $programId): array
{
// Returns Engagement[] sorted by dateCreated ascending
return $this->engagements->getBindings($opportunityId, $programId);
}
public function getRecentBindings(int $opportunityId, int $programId): array
{
// Sort by dateCreated descending, limit to 5
return $this->engagements->getBindings(
$opportunityId,
$programId,
'dateCreated',
'DESC',
5
);
}
} use Siren\Engagements\Core\Facades\Engagements;
// All active bindings for this opportunity + program, oldest first
$bindings = Engagements::getBindings($opportunityId, $programId);
// Most recent 3 bindings
$recent = Engagements::getBindings($opportunityId, $programId, 'dateCreated', 'DESC', 3); Getting the newest binding
getNewestBinding returns the single most recently modified active engagement for an opportunity and program pair. This is useful when you need to know which collaborator was most recently attributed within a specific program context. Throws RecordNotFoundException if no active bindings exist.
try {
$newest = $this->engagements->getNewestBinding($opportunityId, $programId);
$collaboratorId = $newest->getCollaboratorId();
} catch (\PHPNomad\Datastore\Exceptions\RecordNotFoundException $e) {
// No active bindings exist for this combination
} use Siren\Engagements\Core\Facades\Engagements;
$newest = Engagements::getNewestBinding($opportunityId, $programId);
echo $newest->getCollaboratorId(); // The most recently credited collaborator Getting the oldest engagement
getOldestEngagement returns the earliest active engagement for an opportunity and program pair, sorted by creation date. This is the counterpart to getNewestBinding. Use it when first-touch attribution matters. Throws RecordNotFoundException if no active bindings exist.
try {
$oldest = $this->engagements->getOldestEngagement($opportunityId, $programId);
$firstTouch = $oldest->getCreatedDate();
} catch (\PHPNomad\Datastore\Exceptions\RecordNotFoundException $e) {
// No active bindings exist
} use Siren\Engagements\Core\Facades\Engagements;
$oldest = Engagements::getOldestEngagement($opportunityId, $programId);
echo $oldest->getCollaboratorId(); // The first collaborator attributed Counting active engagements
getActiveEngagementCount returns the number of active engagements for an opportunity and program pair. This is a lightweight way to check whether any attribution exists without loading full model instances.
$count = $this->engagements->getActiveEngagementCount($opportunityId, $programId);
if ($count > 0) {
// At least one collaborator is actively credited
} use Siren\Engagements\Core\Facades\Engagements;
$count = Engagements::getActiveEngagementCount($opportunityId, $programId); Finding a specific active engagement
getActiveEngagement retrieves a single active engagement by collaborator, program, and opportunity. Use this when you need to confirm that a specific collaborator currently holds attribution for a particular opportunity within a particular program.
The method signature is getActiveEngagement(int $collaboratorId, int $programId, int $opportunityId). Note that the parameter order is different here — collaborator comes first, unlike most other engagement methods where opportunity is first. Throws RecordNotFoundException if no matching active engagement exists.
use PHPNomad\Datastore\Exceptions\RecordNotFoundException;
try {
$engagement = $this->engagements->getActiveEngagement(
$collaboratorId,
$programId,
$opportunityId
);
// This collaborator is actively credited
$score = $engagement->getScore();
} catch (RecordNotFoundException $e) {
// No active engagement for this collaborator/program/opportunity combination
} use Siren\Engagements\Core\Facades\Engagements;
$engagement = Engagements::getActiveEngagement($collaboratorId, $programId, $opportunityId); Listing all engagements for an opportunity
getEngagementsForOpportunity returns every engagement associated with a given opportunity, regardless of status. This is broader than getBindings because it includes active, pending, complete, and cancelled engagements. This is particularly useful for building audit trails or attribution history views.
$allEngagements = $this->engagements->getEngagementsForOpportunity($opportunityId);
foreach ($allEngagements as $engagement) {
echo $engagement->getCollaboratorId() . ': ' . $engagement->getStatus();
} use Siren\Engagements\Core\Facades\Engagements;
$allEngagements = Engagements::getEngagementsForOpportunity($opportunityId);
// Filter completed engagements
$completed = array_filter($allEngagements, function ($e) {
return $e->getStatus() === 'complete';
}); Access control (REST)
All endpoints require authentication and are scoped to the current tenant (organization). Collaborators can only see their own engagements; administrators can see all records.
Relationships
- Opportunity (upstream). Every engagement is linked to an opportunity via
opportunityId. The opportunity represents the trackable event (such as a site visit or coupon usage) that the collaborator participated in. - Program. Every engagement belongs to a program via
programId. The program defines the rules under which the engagement was created, including which trigger strategies are enabled. - Collaborator. Every engagement is linked to a collaborator via
collaboratorId. The collaborator is the partner whose referral activity produced this engagement. - Conversion (downstream). When an opportunity converts, the system uses the engagement to create a conversion record. The conversion references the engagement via its
engagementIdfield.
REST endpoints
See the individual endpoint pages in the sidebar for full request and response details, or browse the all REST endpoints reference.