SfSkills Domains Docs GitHub

Home / architect / subscription-management-architecture

subscription-management-architecture

Use when designing or evaluating Salesforce CPQ subscription lifecycle architecture: amendment flow, renewal automation, co-termination design, or billing integration at the contract level. Trigger keywords: amendment architecture, renewal automation, co-termination design, subscription ledger, large-scale amendment, billing schedule, swap pattern, SBQQ__Subscription__c. NOT for configuring amendment and renewal quotes - use admin/contract-and-renewal-management. NOT for bundle design or pricing-engine performance - use architect/cpq-architecture-patterns.

Open the skill folder on GitHub


Subscription Management Architecture

This skill activates when designing or reviewing the architecture of a Salesforce CPQ subscription lifecycle: how amendments flow through the system, how renewals are automated, how co-termination is designed without breaking proration, and how billing integration is triggered at contract activation. It is the architect-level counterpart to the contract-and-renewal-management admin skill — it focuses on system design choices and failure modes rather than step-by-step configuration.


Before Starting

Gather this context before working on anything in this domain:


Core Concepts

The CPQ Ledger Model: Amendments as Delta Records

Salesforce CPQ uses a ledger model for subscription state. When an amendment is processed, CPQ does not modify existing SBQQ__Subscription__c records. Instead, it creates new delta subscription records representing the change: a quantity increase creates a new record with the positive delta, a quantity decrease creates a record with the negative delta, and a product removal creates a zero-out record.

The active subscription state is derived by summing all subscription records for a given product on a contract, not by reading a single mutable record. This means:

Price Change Swap Pattern

Changing the price of an existing active subscription mid-contract is not directly supported by CPQ's amendment mechanism, because existing subscription lines are locked to their contracted price. The only supported architecture for a mid-contract price change is the swap pattern:

  1. In the amendment quote, set the quantity of the existing product line to zero (or reduce to zero) — this creates a negative delta subscription record zeroing out the original.
  2. Add a new product line for the same product at the new price — this creates a new subscription record at the updated price with the new start date.
  3. Co-termination applies to both records, ensuring the new line ends on the same date as the rest of the contract.

The swap pattern has a billing implication: if Salesforce Billing is active, the zero-out and the new line each generate billing schedule actions. The architecture must account for credit memos against the original line and new invoices against the replacement line.

Preserve Bundle Structure vs Combine Subscription Quantities

CPQ has two overlapping settings that control how bundle product subscriptions are managed during amendments:

Critical conflict: when both settings are active simultaneously, Preserve Bundle Structure is silently ignored. Bundle child subscriptions are collapsed into single records, destroying the parent-child relationship in the subscription data. This produces incorrect renewal quotes (child products reprice independently of bundle logic) and breaks any entitlement or billing system that relies on bundle membership. The correct design is to choose one model: either preserve bundles OR combine quantities, not both.

Legacy vs Large-Scale Amendment Service Modes

CPQ provides two service modes for amendment and renewal processing:

Legacy mode (synchronous):

Large-Scale mode (asynchronous batch):

The service mode must be decided at architecture time. Retrofitting synchronous contracts to async processing mid-deployment requires testing every downstream integration and trigger.

Co-termination Design and Start Date Immutability

Co-termination forces all subscription lines on a contract to share the same end date — the earliest-ending subscription on the contract. The co-termination start date (SBQQ__CoTerminationDate__c on the Contract) is the anchor for all proration calculations during amendments.

Immutability rule: Once SBQQ__CoTerminationDate__c (or SBQQ__SubscriptionStartDate__c on individual subscriptions) is set on an active contract, editing it post-sale corrupts all proration calculations for subsequent amendments. The proration formula (remaining days / total days × price) is calculated against the original start date. If the start date is changed after subscriptions are generated, CPQ recalculates proration against the new date but does not reprocess historical billing — resulting in over-charging or under-charging.

Treat subscription start dates and co-termination anchors as write-once fields. Any architecture that allows post-activation edits to these fields (via Flow, Apex trigger, or direct field edit) must be rejected.

Renewal Automation Configuration

Renewal automation is controlled by two fields on the Contract object:

Both fields must be true for the full auto-renewal flow. Setting only SBQQ__RenewalForecast__c creates the Opportunity but not the Quote — this is the correct pattern when renewal negotiations require human review before pricing.

Renewal quotes reprice at current price book prices by default, not contracted prices. To lock renewal pricing to a specific price, create SBQQ__ContractedPrice__c records linking the Account, Product, and price before the renewal quote is generated.

Billing Integration at Contract Level

Salesforce Billing generates blng__BillingSchedule__c records when a Contract is activated. The integration point is the Contract Status field: the billing engine listens for Status = 'Activated' to trigger schedule generation.

Architecture requirements:


Common Patterns

Pattern: Mid-Contract Price Change (Swap Pattern)

When to use: A customer negotiates a new unit price for a product already on an active contract. The price must change starting from a specific date, not at renewal.

How it works:

  1. Initiate an amendment from the active Contract record.
  2. On the amendment quote, locate the existing subscription line for the product.
  3. Set the quantity on that line to 0 — this generates a negative delta SBQQ__Subscription__c record zeroing out the original.
  4. Add a new product line for the same product, entering the new agreed unit price as an override or via a Price Rule.
  5. Calculate the quote. CPQ applies co-termination to the new line, prorating it from the amendment effective date to the co-termination date.
  6. Approve and activate. Two new SBQQ__Subscription__c records are created: one zero-quantity close-out and one new-price record.

Why not direct price edit: CPQ locks existing subscription line prices on amendment quotes. Attempting to override the price directly on an existing line will be rejected or silently reverted. The swap pattern is the only path that CPQ's pricing engine honors.

Pattern: Large-Scale Async Amendment

When to use: The contract has 1,000+ active SBQQ__Subscription__c records and synchronous amendment generation times out or hits CPU limits.

How it works:

// Trigger async amendment via ContractManipulationAPI
Id contractId = '800xx000001234AAA';
SBQQ.ContractManipulationAPI.AmendmentContext ctx = 
    new SBQQ.ContractManipulationAPI.AmendmentContext();
ctx.contractId = contractId;
// Setting amendmentStartDate explicitly; defaults to today if omitted
ctx.amendmentStartDate = Date.today();
SBQQ.ContractManipulationAPI.amend(ctx);

After calling amend(), poll for job completion:

List<AsyncApexJob> jobs = [
    SELECT Id, Status, NumberOfErrors
    FROM AsyncApexJob
    WHERE ApexClass.Name = 'AmendmentBatchJob'
    AND Status NOT IN ('Completed', 'Failed')
    ORDER BY CreatedDate DESC
    LIMIT 1
];

Once Status = 'Completed', the amendment quote is available on the Contract's related list.

Why not the Amend button: For 1,000+ lines, the synchronous path exhausts Apex CPU time limits (10,000 ms for synchronous). The async path distributes processing across batch chunks and does not block the user session.

Pattern: Renewal Forecast with Deferred Quote Generation

When to use: The org needs a renewal pipeline visible in forecasting before the renewal quote is negotiated, but the renewal price should not be locked automatically.

How it works:

  1. Set SBQQ__RenewalForecast__c = true on the Contract (or set in CPQ Settings as the default).
  2. Leave SBQQ__RenewalQuoted__c = false.
  3. On contract activation, CPQ creates a Renewal Opportunity with the renewal amount as a forecast, but no Quote is generated.
  4. When the account team is ready to negotiate, they manually initiate the Renew action from the Contract or the Renewal Opportunity, which generates the Renewal Quote at that time.

Why not auto-quote: Auto-generating renewal quotes immediately locks pricing at current list price. For enterprise accounts with negotiated renewals, this creates incorrect price anchors that must be manually corrected. The deferred pattern keeps the renewal pipeline visible without committing to a price prematurely.


Decision Guidance

SituationRecommended ApproachReason
Contract has fewer than ~200 subscription linesSynchronous (Legacy) amendment via Amend button or SBQQ.ContractManipulationAPI (sync)No async overhead; transaction completes immediately
Contract has 1,000+ subscription linesAsync Large-Scale amendment via SBQQ.ContractManipulationAPI.amend() with async contextSynchronous path will hit CPU time limits
Contract has 200–1,000 subscription linesTest in sandbox with production data volume before choosing modeGovernor limit behavior is sensitive to trigger complexity
Mid-contract price change neededSwap pattern: zero out existing line, add new line at new priceExisting subscription lines are locked to contracted price; direct edit is not supported
Bundle products on contract need amendmentVerify Preserve Bundle Structure enabled; disable Combine Subscription QuantitiesBoth settings active simultaneously silently destroys bundle parent-child relationships
Renewal pipeline needed but price not yet negotiatedSet RenewalForecast=true, RenewalQuoted=falseCreates Renewal Opportunity for forecasting without locking a price
Full renewal automation neededSet both RenewalForecast=true and RenewalQuoted=trueGenerates Opportunity and Quote automatically on contract activation
Billing schedules not generating after amendmentEnsure Contract Status is set to Activated AFTER async amendment job completesBilling engine triggers on Status=Activated; premature activation generates incomplete schedules
Co-termination anchor needs to changeReject the change; redesign the contract term structure insteadPost-sale edits to co-termination start date corrupt all proration calculations

Step-by-step instructions for an AI agent or practitioner working on this task:

  1. Establish subscription scale and service mode — Count active SBQQ__Subscription__c records on the contracts in scope. Decide at this step whether the architecture will use Legacy synchronous or Large-Scale async mode. This decision affects field behavior, monitoring requirements, and billing integration sequencing.
  2. Audit CPQ Settings for conflicting configurations — In Setup > Installed Packages > Salesforce CPQ > Configure, review: Preserve Bundle Structure, Combine Subscription Quantities, Co-Termination behavior, Default Renewal Term, and Auto Renew. If both Preserve Bundle Structure and Combine Subscription Quantities are enabled, resolve the conflict before proceeding with any bundle amendment design.
  3. Design amendment flow with ledger model in mind — Document all downstream consumers of SBQQ__Subscription__c data (triggers, integrations, reports). For each consumer, verify it aggregates by contract+product grouping rather than reading a single record as current state. Update any consumer that treats a single subscription record as the source of truth.
  4. Design co-termination anchors as write-once — Identify any Flows, Apex triggers, or processes that can write to SBQQ__SubscriptionStartDate__c or SBQQ__CoTerminationDate__c after contract activation. Remove or gate these. Document the co-termination anchor date in the contract template so it is set correctly at origination and never changed.
  5. Configure renewal automation — Set SBQQ__RenewalForecast__c and SBQQ__RenewalQuoted__c on the Contract (or in CPQ Settings defaults) based on the renewal negotiation model. If contracted renewal pricing is required, ensure SBQQ__ContractedPrice__c records are created for the relevant account-product combinations before the renewal quote is generated.
  6. Sequence billing integration correctly — If Salesforce Billing is installed, confirm the activation sequence: (a) CPQ processes quote → (b) subscriptions are fully written → (c) Contract Status set to Activated → (d) billing schedules generate. For async amendments, add a job completion check before activating the Contract.
  7. Validate with a sandbox amendment cycle — Run a full amendment-to-billing cycle in sandbox: initiate amendment, confirm delta subscription records are created (not existing records modified), validate co-termination dates, verify billing schedules generate after activation. Run check_subscription_arch.py against the metadata to catch configuration anti-patterns.

Review Checklist

Run through these before marking work in this area complete:


Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

  1. Ledger model misunderstanding breaks reporting and integrations — The most damaging misconception in CPQ subscription architecture is treating SBQQ__Subscription__c records as mutable state. After the first amendment, a product's "current" quantity and price are spread across multiple subscription records (original + delta). Any report, trigger, or integration that reads the most-recent subscription record as the current state will return incorrect data. The only correct pattern is to aggregate all subscription records for a given contract-product combination.
  2. Co-termination Start Date edits corrupt proration silently — If SBQQ__SubscriptionStartDate__c or SBQQ__CoTerminationDate__c is edited on an active subscription or contract after activation, CPQ recalculates proration in subsequent amendments using the new date but does not reprocess the proration that was already applied. This produces a silent discrepancy: the billing system and the CPQ subscription ledger diverge. The discrepancy is only discovered during a billing reconciliation, often quarters after the fact.
  3. Preserve Bundle Structure + Combine Subscription Quantities silently break bundle amendments — These two CPQ settings have no conflict warning in the UI. When both are enabled, CPQ processes Combine Subscription Quantities first, collapsing bundle child subscriptions into single records. Preserve Bundle Structure then has no effect because the bundle hierarchy has already been destroyed. Renewal quotes generated from these contracts will reprice bundle children independently rather than as part of a bundle, producing incorrect renewal pricing without any error message.
  4. Large-Scale async amendment uses different date fields than Legacy mode — In Legacy mode, the amendment effective date is read from SBQQ__SubscriptionStartDate__c on the Quote. In Large-Scale mode, it is read from SBQQ__AmendmentStartDate__c on the Contract. If an architect designs date logic for one mode and the deployment switches modes later (e.g., contract volume grows past the threshold), all amendment dates will come from the wrong field and proration will be calculated incorrectly.
  5. Billing schedules generate against incomplete data if Contract is activated before async job completes — In Large-Scale amendment mode, the amendment quote and subscription records are created asynchronously. If a downstream automation (Flow, Apex trigger) moves Contract Status to "Activated" immediately after calling the async amendment API, the billing engine may fire before subscriptions are fully written. The resulting blng__BillingSchedule__c records will be based on pre-amendment subscription data. This is not easily reversible.

Output Artifacts

ArtifactDescription
Amendment architecture decision recordDocuments chosen service mode (Legacy vs Large-Scale), scale threshold, and async monitoring approach
Subscription ledger aggregation patternCode or report definition that correctly aggregates SBQQ__Subscription__c delta records by contract+product
Swap pattern implementationAmendment configuration or script for mid-contract price changes using zero-out + new-line pattern
Co-termination design documentWrite-once field inventory and automation gate preventing post-activation start date edits
Renewal automation configurationSBQQ__RenewalForecast__c and SBQQ__RenewalQuoted__c settings with rationale
Billing integration sequencing checklistOrdered list of prerequisites for blng__BillingSchedule__c generation

Dependencies

Package files