Code Examples

Complete Ruby examples for all SDK methods

Complete, copy-pasteable examples for every public method of the Convert Ruby SDK. The public API is snake_case; only the wire payloads are camelCase (translated internally). All examples assume a single CONVERT_SDK client built once at boot (see Initialization).

Build the client

require "convert_sdk"

# Fetch mode.
CONVERT_SDK = ConvertSdk.create(
  sdk_key:        ENV.fetch("CONVERT_SDK_KEY"),
  sdk_key_secret: ENV["CONVERT_SDK_KEY_SECRET"],
  environment:    "prod"
)

# Direct-data mode (no network fetch).
CONVERT_SDK = ConvertSdk.create(data: load_pre_fetched_config)

Subscribe to lifecycle events

Client#on(event, &block) subscribes to a SystemEvents value (or its matching string) and returns self. Deferred one-shot events replay to late subscribers.

CONVERT_SDK.on("ready") do |_payload, _err|
  # decisions are available from here on
end

CONVERT_SDK.on(ConvertSdk::SystemEvents::BUCKETING) do |payload, _err|
  # payload => { visitor_id:, experience_key:, variation_key: }
end

CONVERT_SDK.on(ConvertSdk::SystemEvents::CONVERSION) do |payload, _err|
  # payload => { visitor_id:, goal_key: }
end

Create a visitor context

create_context(visitor_id = nil, attributes = nil) returns a fresh, independent Context (or nil for a blank visitor id). Attributes accept symbol or string keys.

# Visitor id only.
context = CONVERT_SDK.create_context("visitor-123")

# Visitor id + initial attributes.
context = CONVERT_SDK.create_context("visitor-123", { country: "US", plan: "premium" })

Run a single experience

run_experience(key, attributes = nil) returns a BucketedVariation on a hit or a Sentinel on a miss — never raises, never a bare nil.

variation = context.run_experience("homepage-test")
case variation&.key
when nil          then render_default     # sentinel miss (key is nil)
when "treatment"  then render_treatment
else                   render_variation(variation.key)
end

Per-call attributes are merged over the context's attributes (per-call wins):

variation = context.run_experience("homepage-test", { country: "DE" })

Run all experiences

run_experiences(attributes = nil) returns an Array<BucketedVariation>misses are filtered out, so the array contains only variations the visitor was actually bucketed into.

context.run_experiences.each do |variation|
  activate(variation.experience_key, variation.key)
end

Evaluate a single feature

run_feature(key, attributes = nil) returns a frozen BucketedFeature (or an Array when several bucketed variations carry the feature). A miss is a DISABLED BucketedFeature, never a sentinel — branch on #status.

feature = context.run_feature("new-checkout")
if feature.status == ConvertSdk::FeatureStatus::ENABLED
  render_new_checkout(feature.variables["headline"])
else
  render_legacy_checkout
end

Evaluate all features

run_features(attributes = nil) returns an Array<BucketedFeature> — the full roster, every declared feature evaluated for this visitor (enabled + disabled).

context.run_features.each do |feature|
  toggle(feature.key, on: feature.status == ConvertSdk::FeatureStatus::ENABLED)
end

Track a conversion

track_conversion(goal_key, goal_data: nil, force_multiple_transactions: false) records a conversion, deduplicated per visitor per goal. Returns self.

# Bare conversion.
context.track_conversion("signup")

# With revenue / transaction data (snake_case goal-data keys).
context.track_conversion(
  "purchase",
  goal_data: { amount: 49.99, products_count: 3, transaction_id: "tx-42" }
)

The eight accepted goal_data keys are listed in Return Types & Sentinels. Unknown keys are rejected and debug-logged.

Force a repeat transaction (bypass dedup)

By default a goal records once per visitor. For renewals or repeat purchases, bypass the dedup check:

context.track_conversion(
  "purchase",
  goal_data: { amount: 49.99, transaction_id: "tx-43" },
  force_multiple_transactions: true
)

force_multiple_transactions: true enqueues the repeat transaction without re-marking the goal.

Suppress tracking for a single call

Pass enable_tracking: false in the per-call attributes hash to evaluate but not report (e.g. a consent-denied flow). Bucketing, sticky persistence, and the bucketing lifecycle event still fire — only the outbound network enqueue is skipped.

variation = context.run_experience("homepage-test", { enable_tracking: false })
context.run_experiences({ enable_tracking: false })

The global tracking: false config switch always wins over a per-call true. See Tracking Control.

Force a variation for a preview link

set_preview(experience_id:, variation_id:) forces one variation of one experience on this context, bypassing audiences, locations, environment, experience and variation status, traffic filters, stored decisions, and bucketing for that experience. Returns self. It is synchronous — when the previewed experience is not in the installed config (a draft or paused target), the SDK fetches just that experience on demand.

ConvertSdk.parse_preview_param(value) parses a preview link's {experienceId}.{variationId} value. It is pure and never raises: a two-element Array of strings on success, nil for a missing, malformed, or non-numeric value — so the guard falls straight through to normal decisioning.

# `raw` is whatever your app read from the preview link / request / cookie.
if (pair = ConvertSdk.parse_preview_param(raw))
  context.set_preview(experience_id: pair[0], variation_id: pair[1])
end

# The previewed experience now returns the forced variation.
variation = context.run_experience("homepage-test")

An unresolvable experience or an unknown variation id clears preview and logs a warn — the next run_experience decides exactly as if set_preview had never been called. A blank experience_id/variation_id is rejected the same way but leaves an earlier successful preview in place. Nothing raises. Preview state lives on this one Context instance; two contexts never share it.

A preview-active context leaves zero trace: no tracking events are enqueued, no bucketing or conversion lifecycle event fires, track_conversion is a full no-op, and no visitor state is written — for every experience on that context, not just the previewed one. Other experiences still decide and render normally. See Tracking Control and QA & Preview.

Set default report-segments

set_default_segments(segments) attaches default report-segments for the visitor (filtered to the platform report keys), merged into the visitor's stored segments. Returns self. Supply the wire keys (visitorType, customSegments, …).

context.set_default_segments({ "visitorType" => "new", "customSegments" => ["beta"] })

Run custom segments

run_custom_segments(segment_keys, attributes = nil) evaluates named custom segments against the visitor's properties and attaches matching ids under customSegments in StoreData. Returns a propagated RuleError sentinel, or nil.

context.run_custom_segments(["high-value", "returning"])

# With per-call rule data.
context.run_custom_segments(["high-value"], { ruleData: { lifetime_value: 1250 } })

See Segments.

Update visitor properties (sticky)

update_visitor_properties(properties) merges sticky properties into both the in-memory attributes (so a later decision on this context sees them immediately) and the store (so a later context for the same visitor sees them — stickiness). Returns self.

context.update_visitor_properties({ plan: "premium", account_age_days: 120 })

See Visitor Context.

Read persisted visitor data

get_visitor_data returns the visitor's stored, string-keyed StoreData, or the empty shape { "bucketing" => {}, "segments" => {}, "goals" => {} } when nothing is stored.

data = context.get_visitor_data
data["bucketing"] # sticky variation assignments
data["segments"]  # attached report-segments
data["goals"]     # converted goals (dedup state)

Look up a config entity

get_config_entity(key, entity_type) looks up a config entity by key. entity_type is one of :experience, :feature, :goal (symbol or string). Returns the frozen entity hash, or nil on a miss (debug-logged).

experience = context.get_config_entity("homepage-test", :experience)
feature    = context.get_config_entity("new-checkout", :feature)
goal       = context.get_config_entity("purchase", :goal)

Flush queued events

flush(reason = nil) (alias release_queues) delivers queued events synchronously and returns self. An empty queue is a no-op. Long-running servers also drain via the background flush timer; short-lived processes must flush explicitly before exit.

CONVERT_SDK.flush
CONVERT_SDK.flush("shutdown")   # optional human-readable reason (logged)
CONVERT_SDK.release_queues       # the frozen-name alias — same path

Re-arm after a fork

postfork explicitly re-arms the client after a fork — rarely needed (automatic Process._fork detection covers the common runtimes). Returns self. See Fork Safety & Runtime Recipes.

# e.g. after Process.daemon, or in a worker-boot hook for belt-and-braces
CONVERT_SDK.postfork

Next steps


Did this page help you?