GET /v1/installations gives you the module_id
and pinned version_id, GET /v1/modules/{moduleId}/versions returns each
version’s manifest, and the emits[] entry for your event type carries the
JSON Schema for its payload. An installation stays on its pinned version until
you adopt a newer one, so read the pinned manifest rather than assuming the
newest catalog version.
The catalog
“Pays once per item” is the always-on same-content guard described in
XP: the first paid event claims that
item (
content_id, quiz_id, video_id) for the member, and a later event
for the same item is declined as content_already_earned - even if it
arrives under a fresh event_id. The repeatable types have no per-item
identity to claim, so the brakes there are the per-action daily and lifetime
award limits your organization configures in the console.
Payload contracts
Send these as thepayload of your event. The schemas are closed: an
undeclared field is rejected. Every event needs a member identity - your
member_external_id at the envelope level or a platform user_id in the
payload; an event with neither is recorded but earns nothing.
engagement.content.viewed
engagement.quiz.completed
engagement.video.completed
Completion is your claim; the measurement fields use Google Analytics 4’s
parameter names so a video player integration can forward what it already
emits.
engagement.profile.completed
A profile milestone, not a field-change event. No required payload fields.
engagement.venue.attended
Check-in evidence is yours: the platform validates the field shapes, stores
them and passes them through, but does not verify a geofence, QR value or
beacon signal.
engagement.reply.posted
One event per reply, so a member can have many.
Choose your event ids deliberately
event_id is the deduplication anchor: a repeat send of the same id credits
nothing a second time. Derive it from the identity of the moment, not at
random, and reuse it on retries:
- Profile completed -
organization_id + member_id. One milestone per member. - Venue attended -
organization_id + member_id + venue_id + fixture_id, or the UTC calendar date ofoccurred_atwhen there is no fixture. One check-in per member, venue and occasion. - Video completed -
organization_id + member_id + video_id. The platform’s same-content guard backs this up regardless of event ids. - Reply posted -
organization_id + reply_id. A different reply must get a different id.
engagement.content.viewed and
engagement.video.completed are different types: if you emit both for the
same asset and both are priced, both pay. The platform does not infer that
they describe the same content.