Skip to content

Performance

Performance is the measurement half of a distribution: what actually happened, recorded against a deployment, and rolled up per type, per deployment, per distribution.

await boomin.performance.events.create({
deployment: "dep_...",
enrollment: "enr_...", // which entity earned it — omit for unattributed measurement
type: "purchase",
valueMinor: 4999,
currency: "usd",
externalEventId: "order_1001",
});
const summary = await boomin.performance.summary({ distribution: "dist_..." });

Full parameter reference: performance.

The deployment names the channel, and from it Boomin derives the distribution and the program — you never send those, which is precisely why they cannot drift.

Which entity earned the event is the event’s own enrollment field. A deployment is a shared channel and names no entity, so the event carries its own attribution: the ?ref= link paths stamp enrollment themselves, and a first-party integration recording its own conversions passes it explicitly. Omit it for genuinely unattributed measurement (owned/paid channels).

Conversions arrive at your system with an attribution token — the code a entity shared. The codes minted for a channel’s entities are listed on the deployment:

for await (const dep of boomin.deployments.list({ distribution: "dist_..." })) {
console.log(dep.externalIds); // { promo_link_count: 12, codes: ["…", "…"] }
}

Build the map from attribution token to (deployment, enrollment) once at launch, cache it, and refresh it on deployment.created / deployment.activated webhooks.

One of idempotencyKey or externalEventId is required; without either you get performance_event_identity_required (422). (Raw HTTP bodies spell them idempotency_key / external_event_id.)

KeyUnique acrossUse when
externalEventId(provider, source)Your source system already has a stable id — a Shopify order, a Stripe charge
idempotencyKey(brand, source)It does not, and you are minting one

A replay answers 200 with duplicate: true rather than 201. Your retry loop is safe, and it can tell.

const result = await boomin.performance.events.create({ /* … */ });
if (result.duplicate) return; // already counted

Reward eligibility is resolved at the event’s occurredAt, not at ingestion time.

That matters because provider syncs arrive late. An event that happened while an enrollment or relationship was paused stays permanently ineligible for reward grants even if it lands days after the resume. Reading current status at ingestion would retroactively pay out a paused period on unpause — so the platform does not do that.

await boomin.performance.events.create({
deployment: "dep_...",
type: "purchase",
occurredAt: "2026-08-01T12:00:00Z", // when it actually happened
externalEventId: "shopify_1001",
});

Send a truthful occurredAt whenever you backfill. It defaults to now.

const summary = await boomin.performance.summary({ distribution: "dist_..." });
{
"object": "performance.summary",
"filters": { "distribution": "dist_...", "deployment": null },
"events": 412,
"valueMinor": 1937600,
"byType": [
{ "type": "purchase", "events": 388, "valueMinor": 1937600, "quantity": 402 },
{ "type": "signup", "events": 24, "valueMinor": 0, "quantity": 24 }
]
}

Filter by distribution, by deployment, or by neither for the brand-wide rollup. byType is ordered by event count descending.

To compare channels, iterate deployments and summarize each — the deployment is the channel, so a per-deployment summary is a per-channel (per program × slot) rollup:

const rows = [];
for await (const dep of boomin.deployments.list({ distribution: "dist_..." })) {
const s = await boomin.performance.summary({ deployment: dep.id });
rows.push({ deploymentKey: dep.deploymentKey, events: s.events, valueMinor: s.valueMinor });
}
rows.sort((a, b) => b.valueMinor - a.valueMinor);

A channel summary never splits by entity — per-entity attribution is each event’s enrollment. To rank entities, keep your own tally keyed on the enrollment you ingest (or that the ?ref= paths stamp); there is no per-enrollment rollup endpoint in this release.

ActivityWritten toRead by
Program activity outside any distributionProgram metric eventsQualification, tiers, rewards
Distribution executionPerformance eventsDeployment and distribution rollups

Distribution execution that is program-relevant also projects into the program spine — that is what projected: true on the ingestion response means. So a entity’s qualification keeps accruing whether the activity came through the evergreen referral rail or a launched distribution.

Nothing is ever migrated between the two. The projection is permanent, and the qualification evaluator always reads the program spine.

type is yours — up to 64 characters, no registry. purchase, signup, trial_started, booking, whatever your business measures. It is the grouping key in by_type, so keep it stable.

This is deliberately unlike the events feed, whose type vocabulary is closed because those types are Boomin’s, not yours.