Tracking Conversions

Track goals, revenue, and conversion events

Conversion tracking lets you record goal completions for visitors bucketed into experiences. The SDK evaluates the goal's configured triggering rules and sends conversion (and optionally transaction) events to the Convert tracking API.

Basic Conversion Tracking

Track a goal conversion by passing its unique key:

userContext.trackConversion('goal-key');
$context->trackConversion('goal-key');
ctx.trackConversion("goal-key")
context.track_conversion("goal-key")
await context.trackConversion("goal-key")
context.track_conversion("goal-key")

Conversion Attributes

You can pass additional attributes to control rule matching, attach revenue data, and configure tracking behavior:

userContext.trackConversion('goal-key', {
  ruleData: {
    action: 'buy'
  },
  conversionData: [
    { key: 'amount', value: 10.3 },
    { key: 'productsCount', value: 2 },
    { key: 'transactionId', value: 'transaction-unique-id' }
  ],
  conversionSetting: {
    forceMultipleTransactions: false
  }
});
use ConvertSdk\DTO\ConversionAttributes;
use ConvertSdk\DTO\GoalData;
use ConvertSdk\Enums\GoalDataKey;

$context->trackConversion('goal-key', new ConversionAttributes(
    ruleData: [
        'action' => 'buy',
    ],
    conversionData: [
        new GoalData(GoalDataKey::Amount, 10.3),
        new GoalData(GoalDataKey::ProductsCount, 2),
        new GoalData(GoalDataKey::TransactionId, 'transaction-unique-id'),
    ],
    conversionSetting: [
        'forceMultipleTransactions' => false,
    ],
));
import com.convert.sdk.core.model.GoalData
import com.convert.sdk.core.model.GoalDataKey
import kotlinx.serialization.json.JsonPrimitive

ctx.trackConversion(
    goalKey = "goal-key",
    goalData = listOf(
        GoalData(GoalDataKey.AMOUNT, JsonPrimitive(10.3)),
        GoalData(GoalDataKey.PRODUCTS_COUNT, JsonPrimitive(2)),
        GoalData(GoalDataKey.TRANSACTION_ID, JsonPrimitive("transaction-unique-id")),
    ),
    conversionSetting = mapOf("forceMultipleTransactions" to false),
)
# Ruby folds transaction data into the goal_data: keyword (snake_case keys for the
# eight platform GoalDataKeys) and the dedup override into force_multiple_transactions:.
# There is no separate ruleData argument on this surface.
context.track_conversion("goal-key", goal_data: {
  amount: 10.3,
  products_count: 2,
  transaction_id: "transaction-unique-id"
})
// GoalData is [GoalDataKey: GoalDataValue] — a dictionary literal with typed keys/values.
// There is no separate ruleData argument on this surface.
let data: GoalData = [
    .amount:        .double(10.3),
    .productsCount: .double(2),
    .transactionId: .string("transaction-unique-id"),
]
await context.trackConversion("goal-key", goalData: data)
# Python maps the monetary amount to revenue= and any other fields to a
# conversion_data dict of JSON primitives (int/float/str). There is no separate
# ruleData argument and no typed GoalDataKey enum on this surface:
context.track_conversion(
    "goal-key",
    revenue=10.3,
    conversion_data={
        "products_count": 2,
        "transaction_id": "transaction-unique-id",
    },
)

Attributes Reference

PropertyTypeDescription
ruleDataobject / arrayKey-value pairs used for goal rule matching. The conversion only fires if the rules match.
conversionDataarrayTransaction data entries (see below). When present, the SDK sends both a conversion event and a transaction event.
conversionSettingobject / arrayTracking behavior overrides. Supports forceMultipleTransactions (boolean).

conversionData Keys

KeyTypeDescription
amountnumberOrder value
productsCountnumberOrder quantity
transactionIdstring or numberUnique transaction identifier

The PHP, Ruby, and iOS SDKs also support customDimension1 through customDimension5 (custom_dimension_1custom_dimension_5 in Ruby) for additional custom data.

Revenue Tracking

To report revenue, include conversionData with your conversion. At minimum, pass amount and transactionId:

userContext.trackConversion('purchase-completed', {
  conversionData: [
    { key: 'amount', value: 99.99 },
    { key: 'productsCount', value: 3 },
    { key: 'transactionId', value: 'txn-abc-123' }
  ]
});
use ConvertSdk\DTO\ConversionAttributes;
use ConvertSdk\DTO\GoalData;
use ConvertSdk\Enums\GoalDataKey;

$context->trackConversion('purchase-completed', new ConversionAttributes(
    conversionData: [
        new GoalData(GoalDataKey::Amount, 99.99),
        new GoalData(GoalDataKey::ProductsCount, 3),
        new GoalData(GoalDataKey::TransactionId, 'txn-abc-123'),
    ],
));
import com.convert.sdk.core.model.GoalData
import com.convert.sdk.core.model.GoalDataKey
import kotlinx.serialization.json.JsonPrimitive

ctx.trackConversion(
    goalKey = "purchase-completed",
    goalData = listOf(
        GoalData(GoalDataKey.AMOUNT, JsonPrimitive(99.99)),
        GoalData(GoalDataKey.PRODUCTS_COUNT, JsonPrimitive(3)),
        GoalData(GoalDataKey.TRANSACTION_ID, JsonPrimitive("txn-abc-123")),
    ),
)
context.track_conversion("purchase-completed", goal_data: {
  amount: 99.99,
  products_count: 3,
  transaction_id: "txn-abc-123"
})
let data: GoalData = [
    .amount:        .double(99.99),
    .productsCount: .double(3),
    .transactionId: .string("txn-abc-123"),
]
await context.trackConversion("purchase-completed", goalData: data)
result = context.track_conversion(
    "purchase-completed",
    revenue=99.99,
    conversion_data={
        "products_count": 3,
        "transaction_id": "txn-abc-123",
    },
)
# track_conversion always returns a typed ConversionResult — inspect result.status
# (QUEUED / DEDUPLICATED / GOAL_NOT_FOUND) instead of catching exceptions.

When conversionData is present, the SDK sends two events: a conversion event and a transaction event containing the goal data.

Force Multiple Transactions

By default, each goal fires once per visitor per experience. Subsequent calls to trackConversion for the same visitor and goal are silently ignored (deduplication).

For recurring transactions such as subscription renewals, override deduplication by setting forceMultipleTransactions to true:

userContext.trackConversion('subscription-renewal', {
  conversionData: [
    { key: 'amount', value: 29.99 },
    { key: 'transactionId', value: 'renewal-456' }
  ],
  conversionSetting: {
    forceMultipleTransactions: true
  }
});
use ConvertSdk\DTO\ConversionAttributes;
use ConvertSdk\DTO\GoalData;
use ConvertSdk\Enums\GoalDataKey;
use ConvertSdk\Enums\ConversionSettingKey;

$context->trackConversion('subscription-renewal', new ConversionAttributes(
    conversionData: [
        new GoalData(GoalDataKey::Amount, 29.99),
        new GoalData(GoalDataKey::TransactionId, 'renewal-456'),
    ],
    conversionSetting: [
        ConversionSettingKey::ForceMultipleTransactions->value => true,
    ],
));
import com.convert.sdk.core.model.GoalData
import com.convert.sdk.core.model.GoalDataKey
import kotlinx.serialization.json.JsonPrimitive

ctx.trackConversion(
    goalKey = "subscription-renewal",
    goalData = listOf(
        GoalData(GoalDataKey.AMOUNT, JsonPrimitive(29.99)),
        GoalData(GoalDataKey.TRANSACTION_ID, JsonPrimitive("renewal-456")),
    ),
    conversionSetting = mapOf("forceMultipleTransactions" to true),
)
context.track_conversion("subscription-renewal",
  goal_data: { amount: 29.99, transaction_id: "renewal-456" },
  force_multiple_transactions: true)
await context.trackConversion(
    "subscription-renewal",
    goalData: [.amount: .double(29.99), .transactionId: .string("renewal-456")],
    forceMultipleTransactions: true
)
# Dedup is keyed by (visitor_id, goal_id) by goal identity — a differing revenue
# or conversion_data does NOT defeat it. force_multiple=True overrides dedup:
context.track_conversion(
    "subscription-renewal",
    revenue=29.99,
    conversion_data={"transaction_id": "renewal-456"},
    force_multiple=True,
)

Behavior Summary

ScenarioConversion EventTransaction Event
First trigger, no goal dataSentNot sent
First trigger, with goal dataSentSent
Repeat trigger, no forceNot sentNot sent
Repeat trigger, force=true, no goal dataNot sentNot sent
Repeat trigger, force=true, with goal dataNot sentSent

Note: When forceMultipleTransactions is enabled on a repeat trigger, only the transaction event is sent (revenue is accumulated). The conversion event itself is still deduplicated -- it only fires once per visitor per experience.