Building a Cashback Campaign Engine: The Product Decisions Behind the Rules
Cashback sounds simple when described at a high level. A transaction happens, the system checks whether it matches the conditions of a campaign, and if it does, a reward is calculated. That description is technically correct, but it leaves out most of the decisions that make the product difficult.
Complexity starts when the engine has to support more than one campaign, different reward structures, customer-specific conditions, earning limits, and a B2B setup where part of the user communication happens through the customer organization. The work then becomes less about evaluating individual rules and more about deciding how those rules are allowed to interact, what the product can communicate with certainty, which exceptions deserve to become part of the model, and how much freedom should be given to the people configuring it.
That is what made the project interesting from a Product Management perspective. The most important decisions were rarely about the cashback formula itself. They were about shaping the behavior around it: keeping the engine flexible enough to support real business needs without turning it into something unpredictable, difficult to operate, or impossible to explain later.
This article looks at four areas where those decisions became especially visible: campaign interaction, user communication, customer-specific configuration, and governance.
When Campaigns Stop Being Independent
A single cashback campaign is relatively easy to reason about. It has eligibility criteria, a validity period, a reward calculation, and possibly an earning cap. A transaction either satisfies those conditions or it does not, so the expected outcome is usually clear.
The model becomes much more difficult as soon as the same transaction can qualify for more than one campaign. One campaign might offer 10% cashback above a certain threshold, another might add a fixed bonus, while a third might apply only to a specific customer segment. Individually, none of these rules are particularly complex. The difficulty comes from deciding what should happen when several of them are valid at the same time.
At that point, campaign settings stop being independent. Stacking, priority, incompatible combinations, and partially consumed earning caps can all affect the outcome of the same transaction, turning what initially looked like separate configuration options into one connected decision model.
The main approach was to make stacking and priority behavior configurable at campaign level. This allowed different campaigns to express different interaction rules without forcing one universal policy across the whole engine. Not every form of flexibility was left open, though. In areas where allowing every possible state would have made the outcome difficult to predict or operate, hard constraints were deliberately used instead.
Configurability was useful when it represented a real business decision that could vary between campaigns. Hard constraints were more appropriate where additional freedom would mainly have created ambiguity. That distinction made the engine easier to operate: Product decisions became easier to reason about before launch, financial outcomes were less ambiguous, and campaign behavior could be explained more consistently later.
This was also where my view of configuration started to change. A product is not necessarily stronger because it exposes more options. Every additional option creates another state that has to be understood, tested, maintained, and eventually explained. The useful question is whether that flexibility represents a meaningful business choice or simply transfers complexity into day-to-day operation.
Making Reward Logic Understandable
Another challenge was deciding how much of the campaign logic actually needed to be visible to the end user. The engine could contain eligibility conditions, caps, campaign interactions, and other rules, but exposing all of that information in the application would not necessarily have made the experience clearer.
Part of the user communication already happened through the customer organization before the user reached the application. The product therefore did not need to introduce the benefit from scratch. Instead, the in-app experience had to remain consistent with the context users had already received and avoid adding unnecessary layers of explanation.
The interface did not need to mirror the complexity of the campaign engine. Its role was to surface the information that helped users understand what had happened and what they could reasonably expect next. Potential cashback, for example, was not shown directly. Although the underlying system could evaluate conditions and remaining limits, turning that information into another customer-facing number would have introduced more ambiguity than value.
Simplicity depended on keeping three things aligned: the campaign logic, the communication already provided to the user, and the information shown in the application. If those contradicted each other, the experience would become harder to trust even if the underlying calculation remained correct.
This also changed how I think about transparency in financial products. Transparency does not require exposing every rule, condition, or internal state. It requires showing enough information for the user to understand the outcome without creating expectations the product cannot reliably support.
The useful distinction was between what the engine needed to know and what the user actually needed to see. Keeping those two layers separate allowed the campaign logic to remain expressive without pushing its complexity into the customer experience.
Keeping Customer-Specific Logic Contained
Another layer of complexity appeared when campaign behavior started to depend on customer-specific commercial arrangements. Some benefits needed to be enabled only for certain customer organizations, and in some cases the applicable conditions could vary depending on the agreement behind the benefit.
This introduced a new dimension into the product model. Campaign evaluation was no longer driven only by the transaction and campaign rules; customer-level context could also affect the outcome. That flexibility was necessary for the business, but it came with a risk. If every customer request was implemented directly in the shared logic, the core product would gradually become shaped by a collection of exceptions.
Get Talha Dayı’s stories in your inbox
Join Medium for free to get updates from this writer.
The approach was to keep customer-specific variation at configuration level wherever possible while protecting the core campaign behavior from unnecessary branching. Different commercial setups could still be supported, but each agreement did not automatically become a permanent assumption in the engine.
That boundary became more important over time. Commercial arrangements change, benefits are enabled or disabled, and terms that make sense for one customer may disappear entirely a year later. The product still needs to handle those transitions cleanly without leaving historical exceptions embedded in the core logic.
There is also a commercial side to this. More flexibility can make a product easier to adapt for individual customers, but too much customer-specific behavior can make it harder to understand, operate, and eventually sell.
I found it useful to separate two questions that can easily get mixed together: whether a customer needs a particular behavior today, and whether that behavior deserves to become a reusable product capability. The first can justify configuration. The second requires a much higher bar.
Keeping that distinction visible helped preserve commercial flexibility without allowing one-off requirements to silently redefine the product. Customer-specific behavior could still be supported where it added real value, while the parts of the engine expected to remain stable across customers stayed protected.
Enterprise products will inevitably need exceptions. The important part is giving those exceptions a clear home. If they are not deliberately contained, they eventually become part of the product model whether anyone intended that or not.
Governance When Change Has Consequences
As the number of campaigns, configurations, and interaction rules grows, campaign creation stops being the only thing that needs structure. Change itself starts to require product decisions.
A campaign can have an owner, eligibility conditions, limits, interaction rules, and customer-specific settings. If all of those can be edited freely at any point in time, it becomes much harder to understand why a transaction produced a certain result in the past. That is especially problematic in a financial product, where a rule change can affect both future outcomes and the ability to explain historical ones.
Campaign lifecycle therefore had to be treated explicitly. Creating, activating, modifying, and retiring a campaign could not all be handled as variations of the same edit action. Once a campaign had already been used to process real transactions, changes needed to preserve the context in which earlier decisions had been made.
Versioning provided one way to do that. Instead of allowing changes to silently overwrite the previous state, a new version could be created when the behavior of an active campaign changed. Earlier transactions could remain tied to the rule set that was valid at the time, while future transactions could be evaluated against the updated version.
The same principle applied to invalid states. Where a campaign combination or configuration should never be allowed, preventing that state from being created was preferable to relying on someone to remember the rule manually. That reduced corrective work later and made expected behavior more consistent.
Governance, in this sense, was not about adding an approval step to every change. It was about deciding which parts of the system needed protection from uncontrolled variation.
Traceability was another important part of that structure. If a user received a specific cashback amount, the outcome needed to be reconstructable later. That could depend on the campaign that applied, the version of the rules in effect at the time, eligibility, priority, a cap, or an exclusion. Those details did not need to be exposed directly to the user, but they did need to remain available internally.
At that point, governance became less of an operational concern and more of a product capability. Its value was not in controlling change for its own sake, but in allowing the product to evolve without losing the ability to explain what it had already done.
Deciding What Complexity the Product Should Own
Looking back, the difficult parts of the campaign engine were rarely about the cashback calculation itself. The harder decisions sat around it: how much variation should be allowed between campaigns, which information should remain internal, which customer differences deserved configuration, and how changes should be handled once real transactions had already been processed.
Each of those decisions added a boundary to the product. Stacking and priority were made configurable where campaign-specific variation was useful, while other areas were deliberately constrained. User-facing communication remained simpler than the underlying rule model. Customer-specific behavior was supported without automatically allowing it to reshape the shared logic, and campaign changes were given enough lifecycle and traceability to remain explainable over time.
Seen separately, those may look like unrelated product decisions. In practice, they were all answering the same question: how much complexity should the engine be allowed to own?
Almost any additional rule can be implemented given enough time. The harder part is understanding what happens after it becomes part of the product: who has to configure it, test it, explain it, and deal with the behavior it may create elsewhere.
The cost of flexibility often becomes visible only at that stage. A new configuration option may solve a real business need, but it also creates another state the product is expected to support reliably. A customer exception may unlock a commercial opportunity while becoming something the system has to carry long after the original agreement changes. A rule that looks harmless in isolation may become difficult once it starts interacting with the rest of the engine.
The most useful outcome of this work was a clearer way of deciding where configurability was worth the cost. Flexibility was kept where it represented a meaningful business choice, while boundaries were introduced where additional variation would mainly make the product harder to predict or operate.
That is what ultimately turned the campaign engine into more than a rules problem. The product needed to support change without becoming dependent on exceptions, and it needed to remain understandable as more campaigns, customers, and decisions accumulated over time.
A flexible product is not one that allows everything. It is one that is deliberate about where flexibility belongs.
That distinction eventually raised a second question beyond the scope of this article. Once the same campaign capability can serve more than one use case, the problem starts to shift from designing a feature to designing something other products may depend on. That is where platform thinking begins.









