Installation

Add automatic Annual Upsell offers and customer-requested offers to your application
View Markdown

Annual Upsell uses the same Churnkey JavaScript library and HMAC authentication as Cancel Flows. If the Churnkey script is already loaded, you do not need to add it again.

This guide covers the Stripe integration. Before installing it, configure and publish your campaign and enable Annual Upsell in settings.

Load the Churnkey script and authenticate customers

  1. Load the Churnkey script once in your application.
  2. Generate an HMAC hash on your backend for the authenticated customer's Stripe customer ID.
  3. Wait for the Churnkey script to load before calling the methods below.

Keep your Churnkey API key on the server. Your backend should verify that the signed-in user can access the customer being signed; do not sign an arbitrary customer ID supplied by the browser.

Enable automatic offers

Call armAnnualUpsell after the customer is identified:

await window.churnkey.armAnnualUpsell({
  appId: 'YOUR_APP_ID',
  customerId: 'STRIPE_CUSTOMER_ID',
  authHash: 'SERVER_GENERATED_HMAC',
  provider: 'stripe',
  mode: 'live',
});

This checks eligibility and starts watching for the engagement moment. It does not immediately open an offer. Do not use forceCheck in your normal automatic integration.

OptionPurpose
appIdYour Churnkey App ID.
customerIdThe authenticated customer's Stripe customer ID.
authHashServer-generated HMAC for that customer.
providerUse 'stripe' for this integration.
modeBilling environment; 'live' is the default.
subscriptionIdOptional subscription to select when identifying the customer's subscription.
forceCheckRequests a display-only preview instead of arming the normal automatic flow.

For a customer with multiple subscriptions, pass the intended subscriptionId and verify the selected plan in your integration before enabling live offers.

Open an offer from your own button

After the Churnkey script has been initialized with the current customer's authentication, connect your button to:

async function showAnnualOffer() {
  await window.churnkey.triggerAnnualUpsell();
}

triggerAnnualUpsell reuses the current customer identity. It does not accept authentication parameters and should not be called before initialization.

This path skips the automatic engagement timing and engagement-based targeting. It still checks the campaign, audience, recurring plan, previous accepted offer, and offer availability. It does not open another Churnkey surface on top of one already visible.

By default, a recent automatic offer window can also prevent the manual offer from opening. If you intentionally want a customer-requested button to bypass that frequency restriction, use:

await window.churnkey.triggerAnnualUpsell({ skipCooldown: true });

This option does not bypass audience rules, previous acceptance, or expired or fully claimed offers. Neither method guarantees that an offer will open.

Respond to customer actions

Pass callbacks when you initialize the automatic flow:

await window.churnkey.armAnnualUpsell({
  appId: 'YOUR_APP_ID',
  customerId: 'STRIPE_CUSTOMER_ID',
  authHash: 'SERVER_GENERATED_HMAC',
  provider: 'stripe',
  mode: 'live',
  onAnnualUpsellAccept(customer, { planId }) {
    // Refresh subscription details from your backend.
  },
  onAnnualUpsellDismiss(customer) {
    // The customer declined the offer.
  },
});

Refresh subscription details from your backend after acceptance. The callback is a notification for your interface, not a replacement for your billing provider's subscription and payment records.

Preview the integration

With a published campaign and Annual Upsell enabled, add forceCheck: true to your armAnnualUpsell parameters for a display-only check. This skips automatic timing and engagement targeting. Audience conditions still apply.

The resulting script preview does not submit an accept or dismiss to the billing API. Remove forceCheck when testing the normal automatic behavior.

This display-only script preview is different from confirming a customer test in the dashboard, which can change a live subscription.

Handle logout and account changes

Clear the current offer when a customer logs out or switches accounts:

window.churnkey.clearAnnualUpsellState();

Before requesting another offer, initialize the Churnkey script with the new customer's complete authentication parameters. Clearing the offer does not replace those credentials. Avoid triggering offers while an account change is in progress.

Troubleshooting

If no offer appears, check:

  • The Churnkey script has loaded and your application calls armAnnualUpsell with the correct customer and HMAC.
  • Annual Upsell is enabled and the campaign is published.
  • The customer matches every audience condition and has an eligible subscription.
  • The current page is allowed and no other Churnkey offer or wall is visible.
  • The engagement moment has occurred. Arming is not an immediate display request.
  • The customer has not already accepted, reached an offer limit, or entered the campaign's holdout group.

After an ineligible check or dismissal, the Churnkey script can suppress another automatic check for that customer for 24 hours. For display-only diagnosis, use forceCheck; do not add it to the production automatic flow.

If accepting fails, inspect the subscription and invoice in your billing provider before retrying. Include the customer ID and time of the attempt when contacting Churnkey support.