Siren

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

FieldREST namePHP getterTypeDescription
IDidgetId()integerUnique identifier
OpportunityopportunityIdgetOpportunityId()integerThe opportunity this engagement is linked to
ProgramprogramIdgetProgramId()integerThe program under which this engagement was attributed
CollaboratorcollaboratorIdgetCollaboratorId()integerThe collaborator who receives credit
ScorescoregetScore()integerAttribution score representing the weight of this engagement
StatusstatusgetStatus()stringLifecycle status: active, pending, or complete
CreateddateCreatedgetCreatedDate()datetimeWhen the engagement was first recorded
ModifieddateModifiedgetModifiedDate()datetimeWhen 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:

FieldTypeDescription
collaboratorNamestringDisplay name of the collaborator who triggered this engagement
programNamestringName 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.

FieldTypeDescription
idstringUnique type identifier (e.g., referredSiteVisit, boundCouponUsed)
namestringHuman-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.

StatusDescription
activeThe engagement is live. The collaborator’s referral is currently being tracked against the linked opportunity. New engagements always start in this state.
pendingThe 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.
completeConversions 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 engagementId field.

REST endpoints

See the individual endpoint pages in the sidebar for full request and response details, or browse the all REST endpoints reference.