Skip to content

Send Events

For developers integrating a UI with an already-deployed engine. You capture visitor behavior with the RudderStack JavaScript SDK; RudderStack forwards it to the engine, which builds the user model. You do not run any infrastructure here, you only need two values from whoever deployed the system:

  • a Write Key
  • a Data Plane URL

(These come from the RudderStack source set up during deployment. See Deployment, RudderStack.)

Set up the RudderStack SDK

RudderStack is a customer-data pipeline. Its browser SDK buffers events client-side and ships them to the data plane, which fans them out to destinations (here, the engine's ingest webhook). Load the SDK and initialise it with your two values:

<script>
  !function(){var e=window.rudderanalytics=window.rudderanalytics||[];e.methods=["load","page","track","identify","reset","group","alias"];e.factory=function(t){return function(){var r=Array.prototype.slice.call(arguments);return r.unshift(t),e.push(r),e}};for(var t=0;t<e.methods.length;t++){var r=e.methods[t];e[r]=e.factory(r)}e.load=function(t,r,n){var o=document.createElement("script");o.src="https://cdn.rudderlabs.com/v3/rudder-analytics.min.js",o.async=!0;var a=document.getElementsByTagName("script")[0];a.parentNode.insertBefore(o,a),e._loadOptions=n||{},e._writeKey=t,e._dataPlaneUrl=r}}();

  rudderanalytics.load(WRITE_KEY, DATA_PLANE_URL);
</script>

Full SDK reference and framework packages (React, Vue, Next): RudderStack JavaScript SDK docs.

Identify the user

Recommendations are per visitor, so every event needs to be tied to a stable identity. Call identify once you know who the visitor is (a returning user_id, or the id assigned at survey time). Until then the SDK uses an anonymous id, and the engine still accepts it (anonymousId). Traits passed here seed the cold-start profile.

rudderanalytics.identify(userId, {
  nationality: "NL",
  age_range: "25-34",
  persona: "student"
});

Use the same userId later when you read recommendations.

Allowed events

The engine only understands the events defined in the Event catalog, the authoritative, versioned list of every allowed event name and its exact payload shape. Anything not in the catalog, or sent with the wrong shape, is ignored. The current set:

Domain Allowed events
Content CONTENT_VIEW_STARTED, CONTENT_VIEW_ENDED, CONTENT_VIEW_CHANGED, COLLECTION_VIEW_STARTED, COLLECTION_VIEW_ENDED, PANEL_VIEW_STARTED, PANEL_VIEW_ENDED
Survey SURVEY_PRESENTED, SURVEY_ANSWERED, SURVEY_SUBMITTED, SURVEY_DISMISSED
Input CONTENT_LOOKUP, POSITION_UPDATED
Session SESSION_STARTED, SESSION_STATE_CHANGED, SESSION_ENDED
Global GLOBAL_STARTED, GLOBAL_ENDED

Plus the RudderStack identify call (above), which the engine reads as IDENTIFY. The Event catalog is the source of truth; always check it for the exact per-event fields.

Track events

Emit one track call per visitor action. The examples below cover the events that most directly drive recommendations; see the Event catalog for the full list above and every field. Keep a single session_id per visit and reuse it across a view's start and end so the engine can pair them into dwell.

const SESSION = crypto.randomUUID();

// Visitor opens a story (the other items on screen are impressions)
rudderanalytics.track("CONTENT_VIEW_STARTED", {
  content: { content_id: "content_841" },
  context: {
    session_id: SESSION,
    candidates: [{ content_id: "content_842" }, { content_id: "content_843" }]
  }
});

// Visitor leaves the story
rudderanalytics.track("CONTENT_VIEW_ENDED", {
  content: { content_id: "content_841" },
  context: { session_id: SESSION },
  details: {
    reason: "next_button", dwell_seconds: 42.5,   // next_button|link|close_button|abandon
    request_id: REQUEST_ID                          // echo the rec's request_id (see below)
  }
});

// Visitor searches and clicks a result
rudderanalytics.track("CONTENT_LOOKUP", {
  details: { query_text: "Bergen-Belsen 1944", clicked_id: "content_841" }
});

// Visitor answers the survey (a rating answer feeds engagement)
rudderanalytics.track("SURVEY_ANSWERED", {
  answers: [{ question_id: "satisfaction", question_type: "rating", answer_value: 4 }]
});

The event names, the exact nested property shapes, and what each field becomes are in the Event catalog. Get those right and the engine does the rest.

Echo the request_id

/api/recommend returns a request_id with each recommendation list (see Consume Recommendations). When a visitor finishes an item that came from that list, echo the same request_id on the CONTENT_VIEW_ENDED track call, under details.request_id:

// store the request_id when you render a recommendation list
const { result } = await (await fetch(`${API}/api/recommend?user_id=${userId}`)).json();
const REQUEST_ID = result.request_id;

// ...then echo it when the visitor finishes a recommended item (see the ENDED example above)

This joins the served context (the feature vector the engine scored) to the outcome (dwell, end-reason), which is how the contextual bandit earns its reward signal. Without it, the learned ranker gets no feedback. Detail: Bandit / online learning.

Multi-tenancy

On a multi-tenant deployment, every event must carry X-Tenant-Id so it lands in the right client's slice (its own user models, events, and impressions). Configure the RudderStack webhook destination to send the header, the same id the UI uses when it reads recommendations. No id means the default tenant. See Multi-tenancy.

Next: Consume Recommendations.