Skip to main content

Composable campaigns

Composable campaigns

Contents

Overview

Composable campaigns represent a modular approach to campaign configuration in Spaaza. Unlike traditional campaign types that bundle all configuration in a single entity, composable campaigns separate targeting, conditions, and rewards into independent, reusable components.

A fully configured composable campaign consists of:

  1. Campaign - The base campaign entity with type: "composable"
  2. Assigned Groups - Collections of product barcodes for targeting
  3. Restrictions - Rules that determine when the campaign applies
  4. Reward Methods - How rewards are calculated and distributed

This modular architecture provides several benefits: assigned groups can be reused across multiple campaigns, restrictions can be combined to create complex eligibility rules, and reward methods can be configured independently of the campaign's targeting logic.

Key concepts

Campaign context

The context field determines how and when the campaign fires. It is set once at creation time via the add-campaign endpoint and cannot be changed afterwards.

ContextDescription
basket(default) Campaign fires on the addBasket event when a basket is submitted. Used for cashback, vouchers, and basket-driven rewards.
interactionCampaign fires only when called via the interact-campaign endpoint. Used for scratch-card / competition-draw flows.
internalPlaceholder for future campaigns that are not driven by basket or interaction events. Currently accepts no restrictions.

The context also restricts which restriction types can be set on the campaign and its reward methods. See Allowed restriction types per context.

Assigned groups

Assigned groups define collections of product barcodes that can be used for campaign targeting. They are chain-scoped entities that can be reused across multiple campaigns.

FieldTypeDescription
namestringGroup name (max 255 characters, required for new groups)
typestringqualify (for use in restrictions) or redeem (for use in reward methods)
required_matchesintegerMinimum number of matching items required in the basket (default 0)
barcodesarrayArray of product barcode strings (duplicates not allowed)
excludes_barcode_matchesbooleanWhen true, the group matches items that are NOT in the barcode list (default false)

The type field determines how the assigned group is used:

  • qualify groups are used in restrictions to determine which baskets qualify for the campaign
  • redeem groups are used in reward methods to determine which items receive the reward

Restrictions

Restrictions define conditions under which a campaign applies. Multiple restrictions can be added to a campaign, and all restrictions must be satisfied for the campaign to activate.

Campaign-level restrictions are set inline when creating or updating a campaign via add-campaign or alter-campaign. The available campaign-level restriction types are:

TypeDescriptionConfiguration Fields
basket_itemRestrict by items in the basketassigned_groups (array of IDs), max_basket_items_considered
basket_total_valueRestrict by basket total valueminimum_basket_total_value, maximum_basket_total_value
businessRestrict by business locationbusiness_ids, business_formats, business_regions
currencyRestrict by transaction currencycurrencies (array of 3-letter ISO codes)
segmentRestrict by user segment membershipuser_segment_id, excluded_user_segment_id

The following restriction types are configured on reward methods (via alter-reward-method), not on campaigns directly:

TypeDescriptionConfiguration Fields
budgetSet a budget cap with optional time periodbudget (float), budget_period_quantity, budget_period_quantity_unit
reward_limitLimit how many times a user can earn from this reward methodquantity (integer, required), unit, scale
usage_costDeduct a cost from a wallet when a reward is issuedwallet_id (integer), cost (integer, >= 1)
win_chanceProbability that the reward method fires when eligiblemethod (string, required, must be "basic"), base_chance (float, required, between 0 and 1)

Reward limit units: year, month, week, day, hour, calendar_day.

Reward limit scale: Optional positive integer multiplier for the unit. For example, unit: "month" with scale: 3 applies the limit over a rolling 3-month period. When omitted, scale defaults to 1.

Usage cost: The usage_cost restriction deducts the configured cost amount from the user's balance in the specified wallet each time a reward is issued. If the user does not have sufficient balance, the reward method will not activate. The wallet_id must reference a valid wallet belonging to the same chain as the campaign.

Win chance: The win_chance restriction makes a reward method fire only some of the time, even when all other restrictions are satisfied. When the reward method is evaluated, a uniform random number between 0 and 1 is drawn; if it is less than or equal to base_chance, the reward method fires, otherwise it is skipped for that evaluation. base_chance: 0.05 means a 5% chance of firing, base_chance: 1 means it always fires (equivalent to no win_chance restriction), and base_chance: 0 means it never fires. The only supported method is currently "basic". For basket-context reward methods the roll is performed when the basket price is calculated, so the outcome quoted to the user carries through to the basket commit.

Budget limitation: Budget enforcement currently counts voucher amounts only. A budget restriction on a wallet_contribution reward method does not block rewards because UserPurchaseProgress contributions are not included in the budget balance. Use budget restrictions on voucher-issuing reward method types (instant_*, deferred_*, honour_voucher) for now.

Which restriction types are allowed depends on the campaign's context. See Allowed restriction types per context.

Restrictions can also be set inline when creating or updating a composable campaign via the add-campaign or alter-campaign endpoints. Pass a restrictions object keyed by restriction type:

{
"campaign_id": 100,
"restrictions": {
"currency": {
"currencies": ["EUR"]
},
"basket_item": {
"assigned_groups": [1]
}
}
}

When restrictions are set inline, they replace all existing restrictions on the campaign.

Allowed restriction types per context

The campaign's context determines which restriction types are valid. Attempting to set a restriction that is not allowed for the campaign's context returns parameter_invalid.

Campaign-level restrictions:

Restriction Typebasketinteractioninternal
basket_itemyesnono
basket_total_valueyesnono
currencyyesnono
businessyesnono
business_formatyesnono
business_regionyesnono
segmentyesyesno

Reward-method-level restrictions:

Restriction Typebasketinteractioninternal
basket_itemyesnono
currencyyesnono
businessyesnono
business_formatyesnono
business_regionyesnono
budgetyesyesno
reward_limityesyesno
usage_costyesyesno
win_chanceyesyesno

Reward methods

Reward methods define how rewards are calculated and distributed to users.

FieldTypeDescription
typestringThe reward method type (see Reward method types)
priorityintegerProcessing order (lower numbers are processed first)
usage_limitintegerMaximum number of times this reward method can issue rewards. null means no limit
valuefloatThe reward value. For percentage types, a decimal between 0 and 1 (e.g., 0.05 for 5%)
value_calculation_rulestringitems_value (default, matched items only), basket_value (entire basket), or fixed_value (flat per-matched-item reward, wallet_contribution only)
distribution_rulestringall_items, cheapest_item, or most_expensive
selectionstringItem selection order: high_to_low or low_to_high
recipient_wallet_idintegerThe wallet ID to credit (required for wallet_contribution type)
honour_codestringThe code shown to the user or scanned by staff (for honour_voucher type)
restrictionsobjectOptional restrictions on the reward method (see Reward method restrictions)
metadataobjectDisplay information: title, subtitle, description, image_url, notes, log_message

Reward method types

The type field on a reward method determines how the reward is calculated and delivered:

Instant voucher types - Create a voucher that is applied immediately to the current basket:

TypeDescription
instant_fixed_priceApplies a fixed price discount to matched items in the basket
instant_fixed_discountApplies a fixed amount discount to the basket
instant_percentageApplies a percentage discount to the basket

Deferred voucher types - Create a voucher that can be redeemed in a future transaction:

TypeDescription
deferred_fixed_priceCreates a voucher with a fixed price discount for a future purchase
deferred_fixed_discountCreates a voucher with a fixed amount discount for a future purchase
deferred_percentageCreates a voucher with a percentage discount for a future purchase

Other types:

TypeDescription
wallet_contributionCredits a wallet or points wallet with the calculated reward value
honour_voucherCreates a voucher with an honour code that can be validated externally (e.g., by staff)

Reward method restrictions

Reward methods can have their own restrictions. These include spend-context restrictions (evaluated when a deferred voucher is redeemed) as well as budget, reward limit, and usage cost restrictions that control how often and under what conditions the reward method can issue rewards.

The supported restriction types depend on the parent campaign's context (see Allowed restriction types per context). For basket context campaigns, the following are available:

TypeDescriptionConfiguration Fields
basket_itemRestrict by items in basketassigned_groups (array of IDs with type redeem)
businessRestrict by business locationbusiness_ids, business_formats, business_regions
business_formatRestrict by store formatbusiness_formats
business_regionRestrict by store regionbusiness_regions
currencyRestrict by currencycurrencies (array of 3-letter ISO codes)
budgetSet a budget cap with optional time periodbudget (float), budget_period_quantity, budget_period_quantity_unit
reward_limitLimit how many times a user can earn from this reward methodquantity (integer, required), unit, scale
usage_costDeduct a cost from a wallet when a reward is issuedwallet_id (integer), cost (integer, >= 1)
win_chanceProbability that the reward method fires when eligiblemethod (string, required, must be "basic"), base_chance (float, required, between 0 and 1)

For interaction context campaigns, only budget, reward_limit, usage_cost, and win_chance are available on reward methods.

Reward method restrictions are set via the restrictions field when creating or updating a reward method:

{
"campaign_id": 100,
"type": "deferred_percentage",
"priority": 1,
"restrictions": {
"currency": {
"currencies": ["EUR"]
},
"business": {
"business_ids": [10, 20]
},
"reward_limit": {
"quantity": 5,
"unit": "month"
},
"usage_cost": {
"wallet_id": 42,
"cost": 100
},
"win_chance": {
"method": "basic",
"base_chance": 0.05
}
},
"configuration": {
"value": 0.10,
"value_calculation_rule": "items_value",
"distribution_rule": "all_items"
}
}

Rewards handling behaviour

The rewards_handling_behaviour field on a composable campaign controls how its reward methods are evaluated. This field is set when creating or updating the campaign.

independent (default)

Each reward method is evaluated independently. All eligible reward methods are processed in priority order, and each one that matches issues its own reward. This is the standard behaviour for campaigns that should issue multiple rewards per transaction (e.g., cashback on different product groups).

random_draw

A single reward method is randomly selected from the eligible candidates. This enables competition-style campaigns where each interaction results in one randomly chosen prize. The flow is:

  1. Eligible reward methods are determined based on remaining capacity (usage_limit)
  2. One reward method is randomly selected from the eligible candidates
  3. The selected reward method issues its reward directly to the user
  4. If a usage_cost restriction is configured, the cost is deducted from the user's wallet

Composable campaigns with random_draw behaviour and context: "interaction" can be triggered via the interact-campaign endpoint without requiring a basket. This is useful for scratch-card or prize-draw style interactions where the user does not need to make a purchase.

Configuration flow

The typical setup order for a composable campaign is:

  1. Create a wallet (if one does not already exist) - This will be the destination for earned rewards
  2. Create the base campaign with type: "composable"
  3. Create assigned groups with the product barcodes that should qualify for or receive rewards
  4. Add restrictions referencing the assigned groups to define eligibility rules
  5. Configure the reward method to define how rewards are calculated and distributed

For competition draw campaigns, the setup order is:

  1. Create a wallet (if one does not already exist) - For points-based entry costs and/or wallet rewards
  2. Create the base campaign with type: "composable", context: "interaction", and rewards_handling_behaviour: "random_draw"
  3. Configure reward methods with usage_limit set to define the prize pool, and optionally add a usage_cost restriction on each reward method to require points per draw

Complete example

This example creates a "5% cashback on premium products" campaign.

Step 1: Create an assigned group for qualifying products

POST /internal/alter-assigned-group

{
"chain_id": 1,
"name": "Premium Products Qualify",
"type": "qualify",
"required_matches": 1,
"barcodes": ["PREMIUM_001", "PREMIUM_002", "PREMIUM_003"]
}

Response includes assigned_group_id (e.g., 1).

Step 2: Create the composable campaign with inline restrictions

POST /internal/add-campaign

{
"chain_id": 1,
"type": "composable",
"title": "Premium Products 5% Cashback",
"description": "Earn 5% cashback on all premium product purchases",
"active": true,
"is_contributor": true,
"restrictions": {
"basket_item": {
"assigned_groups": [1]
},
"currency": {
"currencies": ["EUR"]
}
}
}

Response includes campaign_id (e.g., 100).

Step 3: Configure the reward method with reward-method-level restrictions

POST /internal/alter-reward-method

{
"campaign_id": 100,
"type": "wallet_contribution",
"priority": 1,
"configuration": {
"value": 0.05,
"value_calculation_rule": "items_value",
"distribution_rule": "all_items",
"recipient_wallet_id": 42
},
"restrictions": {
"reward_limit": {
"quantity": 10,
"unit": "month"
}
},
"metadata": {
"title": "5% Cashback",
"description": "Earn 5% back on premium products"
}
}

Competition draw example

This example creates a competition draw campaign where users spend 100 points per draw and can win one of several prizes.

Step 1: Create the composable campaign with interaction context and random draw behaviour

POST /internal/add-campaign

{
"chain_id": 1,
"type": "composable",
"title": "Summer Prize Draw",
"description": "Spend 100 points for a chance to win prizes",
"active": true,
"context": "interaction",
"rewards_handling_behaviour": "random_draw"
}

Response includes campaign_id (e.g., 200).

Step 2: Add reward methods as prizes with usage limits and usage cost

POST /internal/alter-reward-method

{
"campaign_id": 200,
"type": "honour_voucher",
"priority": 1,
"usage_limit": 5,
"configuration": {
"honour_code": "GRAND_PRIZE",
"value": 0
},
"restrictions": {
"usage_cost": {
"wallet_id": 50,
"cost": 100
}
},
"metadata": {
"title": "Grand Prize",
"description": "You won the grand prize!"
}
}
POST /internal/alter-reward-method

{
"campaign_id": 200,
"type": "wallet_contribution",
"priority": 2,
"usage_limit": 100,
"configuration": {
"value": 50,
"recipient_wallet_id": 50,
"value_calculation_rule": "basket_value",
"distribution_rule": "all_items"
},
"restrictions": {
"usage_cost": {
"wallet_id": 50,
"cost": 100
}
},
"metadata": {
"title": "50 Bonus Points",
"description": "You won 50 bonus points!"
}
}

Step 3: Trigger the draw for a user

GET /interact-campaign?campaign_id=200&user_id=12345

See the interact-campaign endpoint documentation for full details.

Distribution rules

The distribution_rule in a reward method determines how the reward is allocated across basket items:

all_items

Distributes the reward proportionally across all matched items based on their value.

Example: A 20 EUR reward on items worth 50, 30, and 20 EUR results in a distribution of 10, 6, and 4 EUR respectively.

cheapest_item

Applies the entire reward to the cheapest matched item per threshold.

Example: A 20 EUR reward is applied entirely to the 20 EUR item.

most_expensive

Applies the entire reward to the most expensive matched item per threshold.

Example: A 20 EUR reward is applied entirely to the 50 EUR item.

Value calculation rules

The value_calculation_rule determines what value is used for percentage calculations:

items_value (default)

Calculates the reward based only on the value of matched items.

Example: A basket has 100 EUR total, but only 60 EUR of matched items. A 5% reward equals 3 EUR (5% of 60).

basket_value

Calculates the reward based on the entire basket value.

Example: A basket has 100 EUR total, with 60 EUR of matched items. A 5% reward equals 5 EUR (5% of 100).

fixed_value

Awards a flat amount per matched item, independent of item or basket prices. This is useful for stamp-card style rewards.

Currently only supported for wallet_contribution reward methods. Attempting to use fixed_value with other reward types is rejected at configuration time.

Example: A reward method with value: 5 and fixed_value rule. The basket has 2 matched items. Reward = 10 (5 per matched item × 2 items), regardless of item prices.

Endpoints reference

Campaign endpoints

EndpointMethodDescription
/internal/add-campaignPOSTCreate a new campaign (supports inline restrictions)
/internal/alter-campaignPOSTUpdate an existing campaign (supports inline restrictions)

Assigned group endpoints

EndpointMethodDescription
/internal/alter-assigned-groupPOSTCreate or update assigned group
/internal/get-assigned-groupGETGet single assigned group
/internal/get-assigned-groupsGETList assigned groups (paginated)
/internal/delete-assigned-groupDELETEDelete assigned group

Restriction endpoints

EndpointMethodDescription
/internal/alter-restrictionPOSTCreate or update restriction
/internal/get-restrictionGETGet single restriction
/internal/get-restrictionsGETList restrictions (paginated)
/internal/delete-restrictionDELETEDelete restriction

Reward method endpoints

EndpointMethodDescription
/internal/alter-reward-methodPOSTCreate or update reward method
/internal/get-reward-methodGETGet single reward method
/internal/get-reward-methodsGETList reward methods (paginated)
/internal/delete-reward-methodDELETEDelete reward method

Interaction endpoints

EndpointMethodDescription
/interact-campaignGETTrigger a campaign interaction (for interaction context composable campaigns with random_draw behaviour)

All internal endpoints require admin authentication with write access to the chain.