Developer Guide
Integrations
3 Steps to Get Started
1. Insert the Churnkey JS Snippet
The following code will pull in the Churnkey client-side module and add it under the window.churnkey
namespace so that you can later initialize the offboarding flow for your customers. Place it in the HTML <head>
element.
<script>
!function(){
if (!window.churnkey || !window.churnkey.created) {
window.churnkey = { created: true };
const a = document.createElement('script');
a.src = 'https://assets.churnkey.co/js/app.js?appId=YOUR_APP_ID';
a.async = true;
const b = document.getElementsByTagName('script')[0];
b.parentNode.insertBefore(a, b);
}
}();
</script>
2. Server Side Authentication (HMAC)
Server side verification is in place to make sure all customer requests that Churnkey makes on your behalf are authorized. This is put in place by using a server side generated HMAC hash on the customer ID (or subscription ID if you’re using Paddle).
Concretely, before the Churnkey flow is triggered, you should send a request to your server which (a) verifies that the request is valid - typically using whatever authorization guards you already have in place for user actions and then (b) hashes that customer’s ID using the SHA-256 hashing function.
Below are snippet examples of how this hash can be generated in different backend languages.
const crypto = require("crypto");
const user_hash = crypto.createHmac(
"sha256",
API_KEY // Your Churnkey API Key (keep this safe)
).update(CUSTOMER_ID).digest("hex"); // Send to front-end
import crypto from "crypto";
import type { NextApiRequest, NextApiResponse } from "next";
type Data = {
userHash?: string;
error?: string;
};
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const { customerId } = req.body;
if (!customerId) {
return res.status(400).json({ error: "Missing customerId" });
}
const API_KEY = process.env.CHURNKEY_API_KEY || ""; // Your Churnkey API Key
const userHash = crypto
.createHmac("sha256", API_KEY)
.update(customerId)
.digest("hex");
return res.status(200).json({ userHash });
}
import hmac
import hashlib
email_hash = hmac.new(
API_KEY, # Your Churnkey API Key (keep safe)
CUSTOMER_ID, # Stripe Customer ID
digestmod=hashlib.sha256
).hexdigest() # Send to front-end
OpenSSL::HMAC.hexdigest(
"sha256",
API_KEY, # Your Churnkey API Key (keep safe)
CUSTOMER_ID # Stripe Customer ID
) #send to front-end
<?php
echo hash_hmac('sha256', CUSTOMER_ID, API_KEY); // Stripe Customer Id
?>
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
func main() {
hash := hmac.New(sha256.New, API_KEY)
hash.Write(CUSTOMER_ID)
hex.EncodeToString(hash.Sum(nil))
}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class Test {
public static void main(String[] args) {
try {
String secret = API_KEY; // API Secret
String message = CUSTOMER_ID; // Stripe Customer Id
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] hash = (sha256_HMAC.doFinal(message.getBytes()));
StringBuffer result = new StringBuffer();
for (byte b : hash) {
result.append(String.format("%02x", b));
}
System.out.println(result.toString());
}
catch (Exception e){
System.out.println("Error");
}
}
}
3. Linking your cancel button to your cancel flow
Once the HMAC hash has been generated, you can initialize and display the Churnkey offboarding flow by calling window.churnkey.init('show')
. Typically, you will attach an event listener to a "cancel" button.
Adding the authHash
Simply use the Server Side Authentication section above to implement an HMAC hash function and pass that value into the authHash
parameter.
document.getElementById('cancel-button').addEventListener('click', function () {
window.churnkey.init('show', {
subscriptionId: 'SUBSCRIPTION_ID' // recommended unless Paddle
customerId: 'CUSTOMER_ID', // required unless Paddle
authHash: 'HMAC_HASH', // required
appId: 'YOUR_APP_ID', // required
mode: 'live', // set to 'test' to hit test billing provider environment
provider: 'stripe', // set to 'stripe', 'chargebee', 'braintree', 'paddle'
record: true, // set to false to skip session playback recording
})
})
const customerId = user.customerId // get your customerId
const res = await fetch("/api/churnkey", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
customerId,
}),
});
if (!res.ok) {
console.error("Failed to fetch user_hash");
return;
}
const { userHash } = await res.json();
document.getElementById('cancel-button').addEventListener('click', function () {
window.churnkey.init('show', {
customerId: customerId, // required unless Paddle
authHash: userHash, // required
subscriptionId: 'SUBSCRIPTION_ID' // recommended unless Paddle
appId: 'YOUR_APP_ID', // required
mode: 'live', // set to 'test' to hit test billing provider environment
provider: 'stripe', // set to 'stripe', 'chargebee', 'braintree', 'paddle'
record: true, // set to false to skip session playback recording
})
})
Configuration Options
Enable/Disable Session Recording
By default, session recording is enabled. You can turn off session recording by setting the record
option to false in the above initialization code (step 3).
record: true
Live Mode vs Test Mode
Each mode corresponds to the billing provider (Stripe, Braintree, Chargebee, etc.) environment you're working in. When set to live
mode, Churnkey will look for a customer matching customerId
in your production environment, and changes made to that customer subscription will be applied in the production environment. If you're not ready for live data, you can set mode to test
and Churnkey will work with the test environment of your billing provider.
mode: 'live' // or 'test'
Customer ID vs Subscription ID
subscriptionId
and we’ll take it from there.Churnkey connects to your billing provider so that we can take actions on your behalf to update customer subscriptions. In particular, based on the outcome of your cancel flow, Churnkey can:
- Pause subscription(s)
- Discount subscription(s)
- Cancel subscription(s)
When initializing Churnkey, we recommend that, in addition to the customerId
parameter, you pass in subscriptionId
. This approach is useful when customers have multiple subscriptions and you want precise control over exactly which subscription is being paused, discounted, or canceled. If you don't support multiple subscriptions and have no intention of doing so, the approach described in the next section might be appropriate.
If you want Churnkey to make changes more broadly across the customer's entire billing record, you should omit the subscriptionId
parameter while still including the required customerId
parameter. This is useful when a customer has multiple plans and you want offers to apply to all current billing.
When just a customer ID is used, Churnkey will apply billing changes at the customer level, when possible. If the changes cannot be made at the customer level (e.g. Stripe does not allow customer-level pauses), the action will be taken across every active subscription the customer has.
Billing Actions using just Customer ID
Below are the default actions that Churnkey will take on your behalf if you pass only a customer ID as a configuration option.
Every active, delinquent, and past due subscription is set to cancel at the end of the subscription’s billing month
The coupon is applied directly to the Stripe customer
Stripe does not allow a pause to be applied to the customer. Instead each active subscription is set to pause. Churnkey uses Stripe's built-in pause feature which will update the subscription's pause_collection[behavior]
to mark_uncollectible
. The resumes_at
field will automatically be set by Churnkey depending on the length of the pause selected.
If you are using plan changes, please make sure to pass a subscription ID in addition to a customer ID.
Every customer subscription is set to cancel at the end of the subscription’s billing month
The discount is applied to all customer subscriptions.
A 100% discount is applied to all customer subscriptions for the selected duration.
Billing Actions using Customer ID + Subscription ID
Below are the default actions that Churnkey will take on your behalf if you pass both a customer ID and subscription ID as configuration options.
The specified subscription is set to cancel at the end of the billing month.
The coupon is applied to the specified subscription
The specified subscription is set to pause. Churnkey uses Stripe's built-in pause feature which will update the subscription's pause_collection[behavior]
to mark_uncollectible
. The resumes_at
field will automatically be set by Churnkey depending on the length of the pause selected.
The specific subscription is updated to the new plan. You can optionally set to not prorate changes
The specified subscription is set to cancel at the end of the billing month.
The discount is applied to the specified subscription.
A 100% discount is applied to the subscription for the selected duration.
Customer Attributes
When initializing the cancel flow, you can pass in customerAttributes
which can be used for customer segmentation.
- Define a new custom attribute in the “Flow Settings” tab of the Cancel Flows Builder page
- Example:
videosCreated
, which will represent the total number of videos a customer has created on our example platform - Use this custom attribute to create a new audience of a segmented cancel flow
- Example: create a segment which targets customers where
videosCreated
is more than 20. Tailor the copy to acknowledge that these customers have been power users and offer more generous temporary discounts - Pass in
videosCreated
undercustomerAttributes
when callingwindow.churnkey.init()
window.churnkey.init('show', {
customerId: 'CUSTOMER_ID', // required
authHash: 'HMAC_HASH', // required
appId: 'YOUR_APP_ID', // required
customerAttributes: {
videosCreated: 28 // data about the customer you provide
}
})
Customer Segmentation by Metadata
You can leverage your existing customer and subscription metadata for customer segmentation purposes.
Suppose you have a videosCreated
metadata field already set for all your customers.
- Set up a new custom attribute in the “Flow Settings” tab of the Cancel Flows Builder page. Here you are letting us know what fields you are using in your metadata: their names and the type of data they contain.
- Example:
videosCreated
, which will represent the total number of videos a customer has created on our example platform. - Use this custom attribute to create a new audience of a segmented cancel flow.
- Example: create a segment which targets customers where
videosCreated
is more than 20. Tailor the copy to acknowledge that these customers have been power users and offer more generous temporary discounts - All done! Whenever an end-customer opens the Embed, we will look up the field
videosCreated
in their customer metadata and subscription metadata. If we find it, we will use that value to evaluate your segment conditions.
Custom Callbacks
Churnkey provides two types of callbacks for hooking into the cancel flow: handler callbacks and listener callbacks.
Handler Callbacks
Handler callbacks are intended for when you want to handle changes to a customer's subscription instead of having Churnkey do it on your behalf. If a handler callback for a customer event is defined, Churnkey will not take action on your behalf when this event occurs.
Most handler type callbacks (with the exception of handleSupportRequest
) are JavaScript Promise objects. Calling resolve
will advance Churnkey's flow to a success state. Optionally, you can pass a message which will be shown to the customer. Calling reject
will advance Churnkey's to an error state. Again, you can optionally pass a message to show the customer.
{
// AVAILABLE NOW
handlePause: <Promise>,
handleCancel: <Promise>,
handleDiscount: <Promise>,
handleTrialExtension: <Promise>,
handleSupportRequest: <function>
handlePlanChange: <Promise<(customer, { plan })>,
}
Listener Callbacks
Listener callbacks are used to listen for events so that you can take appropriate action in your application. Unlike handler callbacks, if listener callbacks are defined, Churnkey will still take action on your behalf to update customer accounts in Stripe.
An example use case for the onCancel
listener callback would be initiate client-side, client-specific business logic which needs to take place when a customer cancels there account, such as limiting the features available to them. Often times, this same logic client-side logic can be implemented using Stripe webhooks, and it will typically just come down to what works best for your application.
{
// AVAILABLE NOW
onPause: <function(customer, { pauseDuration }>,
onCancel: <function(customer, surveyResponse)>,
onDiscount: <function(customer, coupon)>,
onTrialExtension: <function(customer, { trialExtensionDays })>,
onPlanChange: <function(customer, { planId })>,
}
Example callbacks for custom pause and cancellation handling
For example, if you want to implement your own pause logic (instead of using Stripe's default pausing mechanism) you can defined the handlePause
option. Similarly, you can use the handleCancel
option to implement your own cancellation. These callbacks will be fired in when your customer selects that they would like to pause or cancel their subscription, respectively. And, if the respective callbacks are defined, Churnkey will not attempt to take action on your behalf by interfacing with Stripe to pause or cancel the subscription.
window.churnkey.init('show', {
appId: 'YOUR_APP_ID',
customerId: 'STRIPE_CUSTOMER_ID',
authHash: 'HMAC_HASH,
handlePause: (customer, { pauseDuration }) => {
return new Promise(async (resolve, reject) => {
try {
//////////
// YOUR CUSTOM PAUSING FUNCTION GOES HERE
//////////
// Optionally, display a custom message by passing a `message` when resolving
// if you don't pass a message, a generic pause confirmation message will be shown
resolve({ message: `Account has been paused for ${pauseDuration} month${duration === 1 ? '' : 's'}` });
} catch (e) {
console.log(e);
reject({ message: 'Failed to pause account. Please reach out to us through our live chat.' });
}
});
},
handleCancel: (customer, surveyAnswer) => {
// customer is the Stripe customer object (string)
// surveyAnswer is the reason they're leaving (string)
return new Promise(async (resolve, reject) => {
try {
///////////
// CANCEL THE CUSTOMERS SUBSCRIPTION HERE
//////////
// Optionally, display a custom message by passing a `message` when resolving
// if you don't pass a message, the generic message below will be displayed
resolve({ message: 'Your account has been canceled. You will not be billed again' });
} catch (e) {
console.log(e);
reject({ message: 'Failed to cancel account. Please reach out to us through our live chat.' });
}
});
},
})
Example support request callback (connect with Intercom, Crisp, etc.)
The below example snippet implements handleSupportRequest
, triggering an Intercom chat. handleSupportRequest
is slightly different than the other handle-type callbacks in that it is a normal function, not a promise. This is intended as implementations of handleSupportRequest
should not require backend changes and should (in nearly all cases) be synchronous.
window.churnkey.init('show', {
appId: 'YOUR_APP_ID',
customerId: 'STRIPE_CUSTOMER_ID',
authHash: 'HMAC_HASH,
handleSupportRequest: customer => {
console.log(customer);
window.Intercom('showNewMessage', 'Attention: Offboarding Customer Needs Help ASAP.\n');
window.churnkey.hide();
},
})
All available callbacks
handlePause(customer, { pauseDuration }): <Promise>
handleCancel(customer, surveyResponse, freeformFeedback): <Promise>
handleDiscount(customer, coupon): <Promise>
handleTrialExtension(customer, { trialExtensionDays }): <Promise>
handlePlanChange(customer, { plan }): <Promise>
handleSupportRequest(customer): <function>
onCancel(customer, surveyResponse, freeformFeedback): <function>
onPause(customer, { pauseDuration }): <function>
onDiscount(customer, coupon): <function>
onTrialExtension(customer, { trialExtensionDays }): <function>
onPlanChange(customer, { planId }): <function>
onGoToAccount(sessionResults): <function>
onClose(sessionResults): <function>
onSessionComplete(sessionResults, customer): <function>
onError(message): <function>
Utility Functions
// used to reset internal state, useful for calling when a customer logs out
window.churnkey.clearState()
Custom Styling
Updating your cancel flow with custom styles
By default, the Churnkey widget uses some basic styles, but you can quickly override them with your own CSS by adding new rules with the parent #ck-app
selector. To avoid conflicts with your site's existing CSS, you should combine this top level parent selector with highly specific descendant selectors for the child element you want to change.
Example CSS Override
For simplicity, assume you want to modify the header text's appearance. The following CSS rule uses the top-level embed id and targets the specific element that needs styling.
#ck-app h1 {
color: black;
background: yellow;
font-style: italic;
}
Here's how that looks in Chrome dev tools.
CSS Classes Available
If you choose, you can easily overwrite the base styles on the Churnkey offboarding flow. Below is a list of classes that are available for custom styling with your own CSS.
# General Components
.ck-style # applies to everything
.ck-modal # the embed pop-up
.ck-background-overlay # the partially transparent overlay
.ck-step # a wrapper around all content
# specific steps in flow
.ck-pause-step
.ck-discount-step
.ck-contact-step
.ck-redirect-step
.ck-survey-step
.ck-freeform-step
.ck-confirm-step
# while a customer subscription is being modified
.ck-progress-step
# once flow is complete - after discount, pause, cancel
.ck-complete-step
# if error is shown at any point (hopefully customers will never see this)
.ck-error-step
# Step components
.ck-step-header
.ck-step-header-text
.ck-brand-image
.ck-brand-image-header
.ck-step-description-text
.ck-step-body
.ck-step-footer
# Button variations
.ck-button
.ck-text-button # custom color
.ck-black-text-button
.ck-primary-button # custom color
.ck-black-primary-button
.ck-gray-primary-button
Internationalization (i18n)
A 5-minute video overview on this topic:
I. Pre-Built Translations
In addition to English, we have pre-built support for French, Spanish, and German. You can simply pass in an i18n
parameter with lang
set to one of 'fr', 'de', 'sp', 'en'
and all of the buttons and pre-set text will be switched to the specified language.
window.churnkey.init('show', {
customerId: 'CUSTOMER_ID',
..., // additional Churnkey parameters
i18n: {
lang: 'fr', // 'fr', 'de', 'sp', 'en'
// specific overrides under messages (optional); all i18n phrases can be found in (III) below
messages: {
fr: {
next: 'Prochain',
back: 'Retour',
...
weReadEveryAnswer: 'Nous lisons chaque réponse'
}
}
}
})
II. Overriding Built-in Buttons and Offer Text
You can optionally pass in translations and overrides for some of Churnkey’s hard-coded text. This can be done by passing an i18n
object when initializing Churnkey or by changing these translations within Churnkey | Cancel Flows | Settings. Please note that i18n messages that are passed in JavaScript while initializing the Churnkey cancel flow embed take priority over those declared within the cancel flow settings.
Using cancel flow settings to override default English phrases
Using the i18n
parameter for overriding default English phrases
window.churnkey.init('show', {
i18n: {
messages: {
en: {
goToAccount: 'Return to Billing', // english override
},
}
}
})
Using i18n for translating into non-English languages
For complete translation, all phrases from the global list below (all i18n phrases) must be provided.
window.churnkey.init('show', {
i18n: {
lang: 'fr',
messages: {
fr: {
next: 'Prochain',
back: 'Retour',
...
weReadEveryAnswer: 'Nous lisons chaque réponse'
}
}
}
})
III. Creating Customer Segments for Different Languages
- Head to the Churnkey | Cancel Flows | Advanced Settings and create a custom attribute called “language”.
b. For each language that you offer your app in, create a segmented cancel flow to be shown to those customers. For instance, below is how you would create a segment for French users.
c. For each segment you create, translate the text in the cancel flow into the target language.
d. When initializing Churnkey in your web app, pass in the custom language attribute as applicable. This will ensure that the customer is shown the translated cancel flow that matches the segmented flow conditions.
window.churnkey.init('show', {
customerAttributes: {
language: 'fr'
},
i18n: {
lang: 'fr',
}
})
IV. All i18n Phrases
Below are all phrases that can be supplied. By default, Churnkey provides English, German, French, and Spanish translations.
en: {
next: 'Next',
back: 'Back',
nevermind: 'Go Back',
goToAccount: 'Go to Account',
getHelp: 'Something Wrong? Contact Us...',
declineOffer: 'Decline Offer',
confirmAndCancel: 'Confirm & Cancel',
pauseSubscription: 'Pause Subscription',
cancelSubscription: 'Cancel',
discountSubscription: 'Accept This Offer', // button to accept discount offer
claimOffer: 'Claim your limited-time offer', // shown above discount offers
discountOff: 'off', // e.g. "10% _off_"
discountFor: 'for', // e.g. "20% off _for_ 2 months"
discountForever: 'for life', // e.g. "10% off _for life_"
discountOneTime: 'your next renewal', // e.g. "10% off _your next renewal_"
day: 'Day | Days', // e.g. "1 day" "2 days"
month: 'month | months', // e.g. "1 month" "2 months"
year: 'year | years', // e.g. "1 year" "2 years"
error: 'Sorry, something went wrong', // generic error message
genericErrorDescription: 'Please contact us.', // generic error message
cancelNow: 'Cancel Subscription →', // button to cancel subscription immediately
applyingDiscount: 'Applying discount...', // shown while applying discount
applyingCancel: 'Cancelling subscription...', // shown while cancelling subscription
applyingResume: 'Resuming subscription...', // shown while resuming a paused subscription
applyingPause: 'Pausing subscription...', // shown while pausing subscription
discountApplied: 'Discount applied.', // shown when discount is applied
discountAppliedMessage: "We're so happy you're still here", // shown when discount is applied
pauseApplied: 'Subscription paused.', // shown when subscription is paused
pauseAppliedMessage: "You won't be billed until your subscription resumes", // shown when subscription is paused
pauseAppliedResumeMessage: 'Your subscription will resume on', // shown when subscription is paused
pauseScheduledMessage: 'Your subscription will be paused beginning', // shown when subscription is scheduled in the future
until: 'until',
cancelApplied: 'Subscription cancelled.', // shown when subscription is canceled
cancelAppliedMessage: "You won't be billed again", // shown when subscription is canceled
cancelAppliedDateMessage: 'Your subscription will end on', // shown when subscription is canceled
howLongToPausePrompt: 'Choose how long you want to pause...', // shown above the pause subscription prompt
whatCouldWeHaveDone: 'What could we have done better?', // shown above the feedback prompt
weReadEveryAnswer: 'We read every answer...', // shown as placeholder in the feedback prompt
applyingCustomerAction: 'This will just take a second.', // shown while applying customer action
loading: 'Loading...', // shown while loading
pauseWallCardPunch: 'Want access?',
pauseWallCta: 'Resume Subscription Now',
pauseWallCardHeading: 'Resume your subscription',
scheduledToReactivate: "It's scheduled to reactivate on",
resumeApplied: 'Subscription resumed', // shown when subscription is paused
resumeAppliedMessage: 'You will be billed at your next subscription renewal period.', // shown when subscription is paused
resumeNextChargeMessage: "Upon reactivation, you'll be charged at your original rate of ",
resumeNextChargeMessageWithoutAmount: "Upon reactivation, you'll be charged at your usual rate.",
resumeAccountDataInfo: 'Your account data is being kept safe for when your subscription resumes.',
subscriptionPauseNotice: 'Looks like your subscription is still paused',
failedPaymentNotice: 'Your account access is limited right now',
chargedMultipleTimeNotice:
"We've tried a number of times to charge your card on file, but it just hasn't worked out. You're not someone we want to lose, though 👇",
failedPaymentCardPunch: 'Update your card to restore access.',
resumeHey: 'Hey',
invoicePaidTitle: 'Invoice paid successfully',
logout: 'Logout →', // used on failed payment and pause wall
// ex You have a 20% off for 3 months discount valid until October 15. Your existing discount will be overriden upon accepting a new offer.
note: 'Note:', // shown before the discount note
discount: 'discount',
discountNoticeHeadline: "Note: you'll also lose an active discount.",
discountNoticePrepend: "If you cancel now, you'll lose your current",
discountOverride: "if you take this offer, you'll lose your current",
discountValidUntil: "It's good until", // shown on active discount
updateBilling: 'Update Card',
// Trial Extension
extendTrialCTA: 'Extend Trial By ',
extendTrialPunch: 'Your trial ends on ',
extendTrialOfferTitle: 'Trial Extension',
trialExtended: 'Trial extended.',
trialExtendedMessage: 'Your trial has been extended successfully',
applyingTrialExtension: 'Extending your trial',
// Plan Change
switchPlanCTA: 'Switch Plan',
changePlanHighlights: 'Highlights',
changePlanOfferPunch: 'Discounted Secret Plans',
planChanged: 'Plan changed.',
planChangedMessage: 'Your new plan is now in effect',
applyingPlanChange: 'Changing your plan...',
// Left Theme Step Tags
surveyStepTag: 'Your Feedback',
freeFormStepTag: 'Your Feedback',
finalConfirmation: 'Final Confirmation',
offerDiscountTag: 'Special Offer',
offerChangePlanTag: 'Consider Other Plans',
offerExtendTrialTag: 'Extend Your Trial',
offerRedirectTag: 'Let Us Help',
offerContactTag: 'Let Us Help',
offerPauseTag: 'Subscription Pause',
completeTag: 'Subscription Cancelled',
errorTag: 'An Error Occured',
offerAccepted: 'Offer Accepted',
}
On this page
- 3 Steps to Get Started
- 1. Insert the Churnkey JS Snippet
- 2. Server Side Authentication (HMAC)
- 3. Linking your cancel button to your cancel flow
- Configuration Options
- Enable/Disable Session Recording
- Live Mode vs Test Mode
- Customer ID vs Subscription ID
- Billing Actions using just Customer ID
- Billing Actions using Customer ID + Subscription ID
- Customer Attributes
- Customer Segmentation by Metadata
- Custom Callbacks
- Handler Callbacks
- Listener Callbacks
- Example callbacks for custom pause and cancellation handling
- Example support request callback (connect with Intercom, Crisp, etc.)
- All available callbacks
- Utility Functions
- Custom Styling
- Example CSS Override
- CSS Classes Available
- Internationalization (i18n)
- I. Pre-Built Translations
- II. Overriding Built-in Buttons and Offer Text
- III. Creating Customer Segments for Different Languages
- IV. All i18n Phrases