[{"data":1,"prerenderedAt":4746},["ShallowReactive",2],{"navigation":3,"search-sections":364,"\u002Fcancel-flows\u002Fversion-history":4245,"\u002Fcancel-flows\u002Fversion-history-surround":4743},[4,10,143,175,187,208,254,263,272],{"title":5,"path":6,"stem":7,"children":8},"Overview","\u002Fgetting-started","1.getting-started\u002F1.index",[9],{"title":5,"path":6,"stem":7},{"title":11,"path":12,"stem":13,"children":14,"page":104},"Cancel Flows","\u002Fcancel-flows","2.cancel-flows",[15,19,23,27,31,35,39,43,47,51,55,59,63,67,105,118],{"title":16,"path":17,"stem":18},"Quick Start Guide","\u002Fcancel-flows\u002Fquick-start-guide","2.cancel-flows\u002F1.quick-start-guide",{"title":20,"path":21,"stem":22},"Click-to-Cancel Compliance","\u002Fcancel-flows\u002Fclick-to-cancel","2.cancel-flows\u002F10.click-to-cancel",{"title":24,"path":25,"stem":26},"Structured Follow-Up Questions","\u002Fcancel-flows\u002Fstructured-follow-up-questions","2.cancel-flows\u002F11.structured-follow-up-questions",{"title":28,"path":29,"stem":30},"Cancel Flow Settings","\u002Fcancel-flows\u002Fcancel-flow-settings","2.cancel-flows\u002F12.cancel-flow-settings",{"title":32,"path":33,"stem":34},"Cancel Flow Structure","\u002Fcancel-flows\u002Fflow-configuration","2.cancel-flows\u002F2.flow configuration",{"title":36,"path":37,"stem":38},"Configuration Options","\u002Fcancel-flows\u002Ffurther-configuration","2.cancel-flows\u002F3.further-configuration",{"title":40,"path":41,"stem":42},"Custom Styling","\u002Fcancel-flows\u002Fcustom-styling","2.cancel-flows\u002F4.custom-styling",{"title":44,"path":45,"stem":46},"Flow History & Version Control","\u002Fcancel-flows\u002Fversion-history","2.cancel-flows\u002F5.version-history",{"title":48,"path":49,"stem":50},"Managed Email Flow","\u002Fcancel-flows\u002Femail-verified-cancel-flow","2.cancel-flows\u002F6.email-verified-cancel-flow",{"title":52,"path":53,"stem":54},"A\u002FB Testing","\u002Fcancel-flows\u002Fa-b-testing","2.cancel-flows\u002F7.a-b-testing",{"title":56,"path":57,"stem":58},"Testing","\u002Fcancel-flows\u002Fcancel-flow-testing","2.cancel-flows\u002F8.cancel-flow-testing",{"title":60,"path":61,"stem":62},"Pause Wall","\u002Fcancel-flows\u002Fpause-wall","2.cancel-flows\u002F9.pause-wall",{"title":64,"path":65,"stem":66},"Adaptive Offers","\u002Fcancel-flows\u002Fadaptive-offers","2.cancel-flows\u002Fadaptive-offers",{"title":68,"path":69,"stem":70,"children":71,"page":104},"Analytics","\u002Fcancel-flows\u002Fanalytics","2.cancel-flows\u002Fanalytics",[72,76,80,84,88,92,96,100],{"title":73,"path":74,"stem":75},"Boosted Revenue","\u002Fcancel-flows\u002Fanalytics\u002Faverage-boosted-revenue","2.cancel-flows\u002Fanalytics\u002F1.average-boosted-revenue",{"title":77,"path":78,"stem":79},"Save Rate","\u002Fcancel-flows\u002Fanalytics\u002Fsave-rate","2.cancel-flows\u002Fanalytics\u002F2.save-rate",{"title":81,"path":82,"stem":83},"Reactivation Rate","\u002Fcancel-flows\u002Fanalytics\u002Freactivation-rate","2.cancel-flows\u002Fanalytics\u002F3.reactivation-rate",{"title":85,"path":86,"stem":87},"Sessions","\u002Fcancel-flows\u002Fanalytics\u002Fsession-outcomes","2.cancel-flows\u002Fanalytics\u002F4.session-outcomes",{"title":89,"path":90,"stem":91},"Cancellation Trends","\u002Fcancel-flows\u002Fanalytics\u002Fcancellation-trends","2.cancel-flows\u002Fanalytics\u002F5.cancellation-trends",{"title":93,"path":94,"stem":95},"Feedback AI","\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai","2.cancel-flows\u002Fanalytics\u002F6.feedback-ai",{"title":97,"path":98,"stem":99},"Response Flow","\u002Fcancel-flows\u002Fanalytics\u002Fresponse-flow","2.cancel-flows\u002Fanalytics\u002F7.response-flow",{"title":101,"path":102,"stem":103},"Response Explorer","\u002Fcancel-flows\u002Fanalytics\u002Fresponse-explorer","2.cancel-flows\u002Fanalytics\u002F8.response-explorer",false,{"title":106,"path":107,"stem":108,"children":109,"page":104},"Multi Language Support","\u002Fcancel-flows\u002Fmulti-language-support","2.cancel-flows\u002Fmulti-language-support",[110,114],{"title":111,"path":112,"stem":113},"Manual Translations","\u002Fcancel-flows\u002Fmulti-language-support\u002Fmulti-language-support","2.cancel-flows\u002Fmulti-language-support\u002F5.multi-language-support",{"title":115,"path":116,"stem":117},"Automatic Translations","\u002Fcancel-flows\u002Fmulti-language-support\u002Fautomatic-translation","2.cancel-flows\u002Fmulti-language-support\u002Fautomatic-translation",{"title":119,"path":120,"stem":121,"children":122,"page":104},"Offers","\u002Fcancel-flows\u002Foffers","2.cancel-flows\u002Foffers",[123,127,131,135,139],{"title":124,"path":125,"stem":126},"Discount","\u002Fcancel-flows\u002Foffers\u002Fdiscounts","2.cancel-flows\u002Foffers\u002Fdiscounts",{"title":128,"path":129,"stem":130},"Hidden Plans","\u002Fcancel-flows\u002Foffers\u002Fhidden-plans","2.cancel-flows\u002Foffers\u002Fhidden-plans",{"title":132,"path":133,"stem":134},"Pause Subscription","\u002Fcancel-flows\u002Foffers\u002Fpause-subscription","2.cancel-flows\u002Foffers\u002Fpause-subscription",{"title":136,"path":137,"stem":138},"Switch Subscription Plan","\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription","2.cancel-flows\u002Foffers\u002Fswitch-subscription",{"title":140,"path":141,"stem":142},"Trial Extension","\u002Fcancel-flows\u002Foffers\u002Ftrial-extension","2.cancel-flows\u002Foffers\u002Ftrial-extension",{"title":144,"path":145,"stem":146,"children":147,"page":104},"Failed Payment Recovery","\u002Ffailed-payment-recovery","3.failed-payment-recovery",[148,152,156,160,164,167],{"title":149,"path":150,"stem":151},"Getting Started","\u002Ffailed-payment-recovery\u002Fpayment-recovery","3.failed-payment-recovery\u002F1.payment-recovery",{"title":153,"path":154,"stem":155},"Customize your Campaigns","\u002Ffailed-payment-recovery\u002Fcampaign-customization","3.failed-payment-recovery\u002F2.campaign-customization",{"title":157,"path":158,"stem":159},"Failed Payment Wall","\u002Ffailed-payment-recovery\u002Ffailed-payment-wall","3.failed-payment-recovery\u002F3.failed-payment-wall",{"title":161,"path":162,"stem":163},"Billing Contact API","\u002Ffailed-payment-recovery\u002Fbilling-contact-api","3.failed-payment-recovery\u002F4.billing-contact-api",{"title":52,"path":165,"stem":166},"\u002Ffailed-payment-recovery\u002Fab-testing","3.failed-payment-recovery\u002F5.ab-testing",{"title":68,"path":168,"stem":169,"children":170,"page":104},"\u002Ffailed-payment-recovery\u002Fanalytics","3.failed-payment-recovery\u002Fanalytics",[171],{"title":172,"path":173,"stem":174},"Overall Performance","\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview","3.failed-payment-recovery\u002Fanalytics\u002F1.overview",{"title":176,"path":177,"stem":178,"children":179,"page":104},"Reactivations","\u002Freactivations","4.reactivations",[180,183],{"title":149,"path":181,"stem":182},"\u002Freactivations\u002Freactivations","4.reactivations\u002F1.reactivations",{"title":184,"path":185,"stem":186},"Customization","\u002Freactivations\u002Fcampaign-customization-guide","4.reactivations\u002F2.campaign-customization-guide",{"title":188,"path":189,"stem":190,"children":191,"page":104},"Data Integrations","\u002Fdata-integrations","6.data-integrations",[192,196,200,204],{"title":193,"path":194,"stem":195},"Data API","\u002Fdata-integrations\u002Fdata-api","6.data-integrations\u002F1.data-api",{"title":197,"path":198,"stem":199},"Slack notifications","\u002Fdata-integrations\u002Fslack","6.data-integrations\u002F2.slack",{"title":201,"path":202,"stem":203},"Webhooks","\u002Fdata-integrations\u002Fwebhooks","6.data-integrations\u002F2.webhooks",{"title":205,"path":206,"stem":207},"Event Tracking","\u002Fdata-integrations\u002Fevent-tracking","6.data-integrations\u002F3.event-tracking",{"title":209,"path":210,"stem":211,"children":212,"page":104},"Billing Providers","\u002Fbilling-providers","7.billing-providers",[213,217,221,225,229,233,237,241],{"title":214,"path":215,"stem":216},"Payment Providers","\u002Fbilling-providers\u002Fpayment-provider-overview","7.billing-providers\u002F1.payment-provider-overview",{"title":218,"path":219,"stem":220},"Stripe","\u002Fbilling-providers\u002Fstripe","7.billing-providers\u002F3.stripe",{"title":222,"path":223,"stem":224},"Chargebee","\u002Fbilling-providers\u002Fchargebee","7.billing-providers\u002F4.chargebee",{"title":226,"path":227,"stem":228},"Paddle Classic","\u002Fbilling-providers\u002Fpaddle-classic","7.billing-providers\u002F5.paddle-classic",{"title":230,"path":231,"stem":232},"Paddle Billing","\u002Fbilling-providers\u002Fpaddle-billing","7.billing-providers\u002F6.paddle-billing",{"title":234,"path":235,"stem":236},"Braintree","\u002Fbilling-providers\u002Fbraintree","7.billing-providers\u002F7.braintree",{"title":238,"path":239,"stem":240},"Maxio","\u002Fbilling-providers\u002Fmaxio","7.billing-providers\u002F8.maxio",{"title":242,"path":243,"stem":244,"children":245,"page":104},"Direct Connect","\u002Fbilling-providers\u002Fdirect-connect","7.billing-providers\u002F9.direct-connect",[246,250],{"title":247,"path":248,"stem":249},"Churnkey Direct","\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect","7.billing-providers\u002F9.direct-connect\u002F1.direct",{"title":251,"path":252,"stem":253},"Direct Mode Examples","\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect-examples","7.billing-providers\u002F9.direct-connect\u002F2.direct-examples",{"title":255,"path":256,"stem":257,"children":258,"page":104},"Account","\u002Faccount","8.account",[259],{"title":260,"path":261,"stem":262},"Multi-Workspace Support","\u002Faccount\u002Fmulti-workspace-support","8.account\u002F1.multi-workspace-support",{"title":264,"path":265,"stem":266,"children":267,"page":104},"Support","\u002Fsupport","9.support",[268],{"title":269,"path":270,"stem":271},"Frequently Asked Questions","\u002Fsupport\u002Ffaqs","9.support\u002F1.faqs",{"title":273,"path":274,"stem":275,"children":276,"page":104},"Integrations","\u002Fintegrations","99.integrations",[277,306,335,360],{"title":278,"path":279,"stem":280,"children":281,"page":104},"Models","\u002Fintegrations\u002Fmodels","99.integrations\u002F2.models",[282,286,290,294,298,302],{"title":283,"path":284,"stem":285},"Customer Model","\u002Fintegrations\u002Fmodels\u002Fcustomer","99.integrations\u002F2.models\u002F1.customer",{"title":287,"path":288,"stem":289},"Price Model","\u002Fintegrations\u002Fmodels\u002Fprice","99.integrations\u002F2.models\u002F2.price",{"title":291,"path":292,"stem":293},"Subscription Model","\u002Fintegrations\u002Fmodels\u002Fsubscription","99.integrations\u002F2.models\u002F3.subscription",{"title":295,"path":296,"stem":297},"Coupon Model","\u002Fintegrations\u002Fmodels\u002Fcoupon","99.integrations\u002F2.models\u002F4.coupon",{"title":299,"path":300,"stem":301},"Product Model","\u002Fintegrations\u002Fmodels\u002Fproduct","99.integrations\u002F2.models\u002F5.product",{"title":303,"path":304,"stem":305},"Family Model","\u002Fintegrations\u002Fmodels\u002Ffamily","99.integrations\u002F2.models\u002F6.family",{"title":307,"path":308,"stem":309,"children":310,"page":104},"Controllers","\u002Fintegrations\u002Fcontrollers","99.integrations\u002F3.controllers",[311,315,319,323,327,331],{"title":312,"path":313,"stem":314},"Customers - Controller","\u002Fintegrations\u002Fcontrollers\u002Fcustomers","99.integrations\u002F3.controllers\u002F2.customers",{"title":316,"path":317,"stem":318},"Prices - Controller","\u002Fintegrations\u002Fcontrollers\u002Fprices","99.integrations\u002F3.controllers\u002F3.prices",{"title":320,"path":321,"stem":322},"Subscriptions - Controller","\u002Fintegrations\u002Fcontrollers\u002Fsubscriptions","99.integrations\u002F3.controllers\u002F4.subscriptions",{"title":324,"path":325,"stem":326},"Coupons - Controller","\u002Fintegrations\u002Fcontrollers\u002Fcoupons","99.integrations\u002F3.controllers\u002F5.coupons",{"title":328,"path":329,"stem":330},"Products - Controller","\u002Fintegrations\u002Fcontrollers\u002Fproducts","99.integrations\u002F3.controllers\u002F6.products",{"title":332,"path":333,"stem":334},"Families - Controller","\u002Fintegrations\u002Fcontrollers\u002Ffamilies","99.integrations\u002F3.controllers\u002F7.families",{"title":336,"path":337,"stem":338,"children":339,"page":104},"Actions","\u002Fintegrations\u002Factions","99.integrations\u002F4.actions",[340,344,348,352,356],{"title":341,"path":342,"stem":343},"Cancel - Action","\u002Fintegrations\u002Factions\u002Fcancel","99.integrations\u002F4.actions\u002F1.cancel",{"title":345,"path":346,"stem":347},"Pause - Action","\u002Fintegrations\u002Factions\u002Fpause","99.integrations\u002F4.actions\u002F2.pause",{"title":349,"path":350,"stem":351},"Extend Trial - Action","\u002Fintegrations\u002Factions\u002Fextend-trial","99.integrations\u002F4.actions\u002F3.extend-trial",{"title":353,"path":354,"stem":355},"Apply Coupon","\u002Fintegrations\u002Factions\u002Fapply-coupon","99.integrations\u002F4.actions\u002F4.apply-coupon",{"title":357,"path":358,"stem":359},"Change Price - Action","\u002Fintegrations\u002Factions\u002Fchange-price","99.integrations\u002F4.actions\u002F5.change-price",{"title":361,"path":362,"stem":363},"Publish your Integration","\u002Fintegrations\u002Fpublish","99.integrations\u002F5.publish",[365,369,375,380,385,388,392,397,402,407,412,418,423,428,433,436,441,444,448,453,458,463,468,472,477,482,487,490,494,499,504,509,514,519,524,529,534,539,543,548,553,558,563,568,572,576,580,585,590,595,600,603,608,613,618,623,628,633,638,643,648,651,656,661,666,671,676,681,686,689,694,699,704,709,714,719,722,727,732,735,740,745,750,755,760,765,770,775,780,785,790,795,800,805,810,815,818,823,828,833,838,843,848,853,858,863,868,873,876,881,886,891,896,901,906,911,916,920,925,930,935,940,945,950,953,959,964,969,973,978,981,986,991,996,1001,1006,1011,1016,1021,1025,1029,1033,1038,1043,1048,1053,1058,1063,1067,1072,1077,1082,1087,1092,1097,1102,1107,1112,1117,1122,1127,1132,1137,1142,1147,1152,1157,1162,1165,1170,1175,1180,1185,1190,1195,1198,1203,1208,1212,1217,1222,1227,1232,1236,1239,1243,1248,1253,1258,1263,1267,1272,1277,1282,1286,1291,1296,1301,1306,1311,1316,1320,1325,1330,1335,1340,1344,1347,1352,1357,1362,1366,1371,1376,1381,1386,1391,1396,1399,1404,1407,1411,1415,1420,1423,1427,1431,1436,1439,1443,1448,1453,1458,1463,1468,1472,1477,1482,1487,1492,1495,1499,1503,1507,1512,1517,1522,1527,1531,1536,1541,1546,1549,1553,1558,1562,1567,1572,1577,1582,1587,1592,1597,1602,1607,1610,1615,1620,1625,1630,1635,1640,1643,1647,1652,1657,1661,1666,1671,1676,1681,1684,1688,1692,1697,1702,1707,1712,1717,1722,1726,1730,1735,1740,1745,1748,1753,1758,1762,1767,1772,1777,1782,1785,1789,1793,1798,1803,1806,1811,1816,1821,1826,1831,1834,1839,1844,1849,1854,1859,1864,1869,1874,1879,1884,1889,1894,1899,1904,1909,1914,1919,1924,1929,1934,1939,1944,1949,1954,1959,1964,1969,1974,1979,1984,1988,1993,1998,2001,2006,2011,2016,2020,2023,2028,2031,2036,2041,2045,2048,2052,2056,2060,2065,2070,2075,2078,2083,2088,2093,2098,2103,2108,2111,2115,2119,2124,2127,2132,2137,2142,2147,2152,2156,2161,2166,2170,2174,2179,2183,2188,2193,2198,2202,2207,2212,2217,2222,2227,2230,2235,2240,2245,2250,2255,2260,2265,2270,2275,2278,2283,2288,2292,2297,2301,2306,2311,2316,2320,2324,2328,2333,2338,2342,2346,2350,2355,2360,2365,2370,2374,2379,2384,2388,2393,2398,2401,2406,2411,2416,2421,2426,2429,2434,2439,2444,2449,2454,2459,2464,2469,2472,2477,2481,2486,2491,2496,2501,2506,2511,2516,2521,2526,2531,2536,2541,2546,2549,2553,2557,2560,2564,2569,2573,2577,2581,2584,2589,2594,2599,2603,2606,2610,2615,2620,2625,2630,2635,2640,2645,2650,2655,2659,2664,2669,2674,2679,2684,2689,2694,2699,2704,2709,2714,2719,2724,2729,2733,2738,2743,2748,2751,2756,2760,2764,2768,2773,2778,2783,2788,2792,2796,2801,2806,2811,2815,2819,2823,2828,2832,2836,2840,2843,2848,2851,2855,2858,2863,2866,2870,2873,2876,2879,2882,2886,2890,2894,2898,2901,2904,2907,2911,2916,2921,2926,2931,2935,2940,2943,2948,2953,2958,2963,2968,2973,2978,2982,2987,2991,2996,3001,3006,3009,3014,3019,3023,3026,3030,3035,3040,3045,3050,3055,3060,3063,3068,3073,3078,3083,3088,3091,3096,3099,3104,3108,3113,3118,3123,3127,3132,3137,3142,3147,3152,3157,3162,3167,3172,3177,3182,3187,3192,3197,3200,3205,3210,3215,3220,3224,3228,3232,3236,3241,3245,3250,3255,3259,3263,3266,3271,3276,3281,3286,3290,3294,3298,3302,3306,3310,3315,3319,3322,3326,3330,3335,3339,3342,3346,3349,3353,3357,3361,3365,3368,3371,3375,3378,3383,3388,3393,3397,3402,3406,3410,3414,3418,3422,3426,3430,3434,3438,3443,3448,3453,3456,3461,3466,3470,3474,3478,3483,3487,3491,3495,3500,3503,3507,3512,3517,3522,3527,3532,3537,3542,3547,3550,3554,3559,3564,3569,3573,3578,3583,3587,3592,3597,3602,3607,3610,3615,3620,3625,3630,3635,3640,3645,3650,3655,3660,3664,3669,3674,3679,3682,3687,3692,3697,3702,3707,3712,3717,3722,3727,3732,3737,3742,3747,3752,3757,3762,3767,3772,3777,3782,3787,3792,3797,3802,3807,3812,3817,3822,3827,3831,3835,3840,3845,3850,3855,3858,3862,3867,3870,3875,3878,3882,3885,3888,3892,3895,3898,3902,3905,3909,3912,3916,3919,3922,3926,3929,3934,3939,3943,3948,3953,3958,3963,3967,3970,3974,3978,3981,3985,3989,3992,3996,4000,4003,4007,4011,4014,4018,4022,4025,4029,4033,4036,4039,4043,4047,4050,4054,4058,4062,4065,4069,4073,4076,4080,4084,4087,4090,4094,4098,4101,4106,4111,4116,4119,4123,4127,4130,4134,4138,4142,4145,4149,4153,4156,4160,4164,4167,4171,4175,4178,4182,4186,4189,4193,4197,4200,4204,4208,4211,4216,4221,4226,4230,4235,4240],{"id":6,"title":5,"titles":366,"content":367,"level":368},[],"Everything you need to know about integrating Churnkey with your product and billing stack.",1,{"id":370,"title":371,"titles":372,"content":373,"level":374},"\u002Fgetting-started#where-do-i-start","Where do I start?",[5],"Need to integrate Churnkey with your web app? Head to our complete walkthrough: Quick Start Guide Further Configuration FAQs",2,{"id":376,"title":377,"titles":378,"content":379,"level":374},"\u002Fgetting-started#want-details-on-connecting-your-billing-provider","Want details on connecting your billing provider?",[5],"Churnkey does a lot of the heavy lifting for you when it comes to updating customer subscriptions during the Cancel Flow, including pausing subscriptions, applying discounts, and canceling subscriptions at the end of the billing month. Learn more about exactly what actions Churnkey can take on your behalf with our billing provider guides. Stripe Braintree Chargebee Paddle Classic Paddle Billing Maxio",{"id":381,"title":382,"titles":383,"content":384,"level":374},"\u002Fgetting-started#interested-in-getting-the-most-out-of-churnkey","Interested in getting the most out of Churnkey?",[5],"Add Churnkey to your company Slack channel to keep your finger on the pulse. Slack Update customer profiles in CRMs like Customer.io, Hubspot, and Intercom with custom webhooks. Webhooks Use our data API to connect rich customer data to your data warehouse Data API Get started with a low-code implementation of Churnkey's Cancel Flow Email-Verified Cancel Flow",{"id":17,"title":16,"titles":386,"content":387,"level":368},[],"A 3-step guide on how to implement Cancel Flows in your website",{"id":389,"title":5,"titles":390,"content":391,"level":374},"\u002Fcancel-flows\u002Fquick-start-guide#overview",[16],"Integrating Churnkey Cancel Flows into your application involves three main steps: Load the Churnkey script - Add the Churnkey JavaScript library to your website to make window.churnkey availableGenerate secure authentication - Create a backend endpoint that generates an HMAC hash to verify customer identityInitialize the cancel flow - When a customer clicks cancel, fetch the authHash from your backend and display the Churnkey modal This integration pattern ensures that only authenticated customers can access their cancellation options, protecting your application from unauthorized access.",{"id":393,"title":394,"titles":395,"content":396,"level":374},"\u002Fcancel-flows\u002Fquick-start-guide#step-one-place-script-element","Step One: Place Script Element",[16],"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 Churnkey Cancel Flow for your customers. Place it in the HTML \u003Chead> element.\nTo find YOUR_APP_ID,from any initial page in Churnkey's dashboard you can navigate: Settings (Bottom Left Corner)Organization (Top Menu)Scroll Down to the section Cancel Flow API Keys \u003Cscript>\n!function(){\n  if (!window.churnkey || !window.churnkey.created) {\n    window.churnkey = { created: true };\n    const a = document.createElement('script');\n    a.src = 'https:\u002F\u002Fassets.churnkey.co\u002Fjs\u002Fapp.js?appId=YOUR_APP_ID';\n    a.async = true;\n    const b = document.getElementsByTagName('script')[0];\n    b.parentNode.insertBefore(a, b);\n  }\n}();\n\u003C\u002Fscript>",{"id":398,"title":399,"titles":400,"content":401,"level":374},"\u002Fcancel-flows\u002Fquick-start-guide#step-two-generate-secure-hmac-hash","Step Two: Generate Secure HMAC Hash",[16],"Note for Paddle UsersUse the Subscription ID instead of Customer ID for creating the HMAC hash To ensure that all customer requests processed by Churnkey are authorized, server-side verification is implemented. This involves generating an HMAC hash on the customer ID (or subscription ID for Paddle users) using SHA-256 hashing. Before triggering the Churnkey flow, a request is sent to the server to (a) validate the request's authenticity, typically using existing authorization measures, and (b) compute the customer's ID hash. Below are examples in various backend languages. To find your API_KEY, from any initial page in Churnkey's dashboard you can navigate: Settings (Bottom Left Corner)Organization (Top Menu)Scroll Down to the section Cancel Flow API Keys const crypto = require(\"crypto\");\nconst userHash = crypto.createHmac(\n  \"sha256\",\n  API_KEY \u002F\u002F Your Churnkey API Key (keep this safe)\n).update(CUSTOMER_ID).digest(\"hex\"); \u002F\u002F Send to front-end\nimport crypto from \"crypto\";\nimport type { NextApiRequest, NextApiResponse } from \"next\";\n\ntype Data = {\n  userHash?: string;\n  error?: string;\n};\n\nexport default function handler(\n  req: NextApiRequest,\n  res: NextApiResponse\u003CData>\n) {\n  const { customerId } = req.body;\n\n  if (!customerId) {\n    return res.status(400).json({ error: \"Missing customerId\" });\n  }\n\n  const API_KEY = process.env.CHURNKEY_API_KEY || \"\"; \u002F\u002F Your Churnkey API Key\n  const userHash = crypto\n    .createHmac(\"sha256\", API_KEY)\n    .update(customerId)\n    .digest(\"hex\");\n\n  return res.status(200).json({ userHash });\n}\nimport hmac\nimport hashlib\n\nuser_hash = hmac.new(\n    API_KEY, # Your Churnkey API Key (keep safe)\n    CUSTOMER_ID, # Stripe Customer ID\n    digestmod=hashlib.sha256\n).hexdigest()\n# Send user_hash to front-end\nuser_hash = OpenSSL::HMAC.hexdigest(\n  \"sha256\",\n  API_KEY, # Your Churnkey API Key (keep safe)\n  CUSTOMER_ID # Stripe Customer ID\n)\n# Send user_hash to front-end\n\u003C?php\n$user_hash = hash_hmac('sha256', CUSTOMER_ID, API_KEY);\necho $user_hash; \u002F\u002F Send to front-end\n?>\npackage main\n\nimport (\n    \"crypto\u002Fhmac\"\n    \"crypto\u002Fsha256\"\n    \"encoding\u002Fhex\"\n)\n\nfunc main() {\n    h := hmac.New(sha256.New, API_KEY) \u002F\u002F Your Churnkey API Key (keep safe)\n    h.Write(CUSTOMER_ID)                \u002F\u002F Stripe Customer ID\n    userHash := hex.EncodeToString(h.Sum(nil))\n    \u002F\u002F Send userHash to front-end\n}\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\n\npublic class Test {\n    public static void main(String[] args) {\n        try {\n            String secret = API_KEY; \u002F\u002F Your Churnkey API Key (keep safe)\n            String message = CUSTOMER_ID; \u002F\u002F Stripe Customer ID\n\n            Mac sha256HMAC = Mac.getInstance(\"HmacSHA256\");\n            SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(), \"HmacSHA256\");\n            sha256HMAC.init(secretKey);\n\n            byte[] hash = sha256HMAC.doFinal(message.getBytes());\n            StringBuffer userHash = new StringBuffer();\n            for (byte b : hash) {\n                userHash.append(String.format(\"%02x\", b));\n            }\n            System.out.println(userHash.toString()); \u002F\u002F Send to front-end\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n    }\n}\nusing System;\nusing System.Security.Cryptography;\nusing System.Text;\n\npublic class Test\n{\n    public static void Main(string[] args)\n    {\n        try\n        {\n            string secret = API_KEY; \u002F\u002F Your Churnkey API Key (keep safe)\n            string message = CUSTOMER_ID; \u002F\u002F Stripe Customer ID\n\n            using (var hmacsha256 = new HMACSHA256(Encoding.UTF8.GetBytes(secret)))\n            {\n                byte[] hash = hmacsha256.ComputeHash(Encoding.UTF8.GetBytes(message));\n                StringBuilder userHash = new StringBuilder();\n                foreach (byte b in hash)\n                {\n                    userHash.Append(b.ToString(\"x2\"));\n                }\n                Console.WriteLine(userHash.ToString()); \u002F\u002F Send to front-end\n            }\n        }\n        catch (Exception e)\n        {\n            Console.WriteLine(\"Error: \" + e.Message);\n        }\n    }\n}",{"id":403,"title":404,"titles":405,"content":406,"level":374},"\u002Fcancel-flows\u002Fquick-start-guide#step-three-launch-churnkey","Step Three: Launch Churnkey",[16],"Once the HMAC hash has been generated, you can initialize and display the Churnkey Cancel Flow by calling window.churnkey.init('show') with the required configuration parameters. window.churnkey.init('show', {\n  customerId: 'CUSTOMER_ID', \u002F\u002F required unless Paddle\n  authHash: 'HMAC_HASH', \u002F\u002F required - fetched from your backend\n  subscriptionId: 'SUBSCRIPTION_ID', \u002F\u002F recommended unless Paddle\n  appId: 'YOUR_APP_ID', \u002F\u002F required\n  mode: 'live', \u002F\u002F 'live', 'test', or 'sandbox' (Stripe only)\n  provider: 'stripe', \u002F\u002F set to 'stripe', 'chargebee', 'braintree', 'paddle'\n}) Session recording is now a dashboard toggle. You no longer need to pass record: true here — recording defaults to on and is controlled from Cancel Flow → Settings. Only keep the record parameter in your embed if you need to override the dashboard for a specific segment (for example, record: false for users who have opted out); when both are set, the embed parameter wins.",{"id":408,"title":409,"titles":410,"content":411,"level":374},"\u002Fcancel-flows\u002Fquick-start-guide#complete-integration-example","Complete Integration Example",[16],"In practice, you'll need to fetch the authHash from your backend before initializing Churnkey. Here's a complete example showing the full flow: \u002F\u002F When user clicks cancel button, fetch authHash from your backend\ndocument.getElementById('cancel-button').addEventListener('click', async function () {\n  try {\n    \u002F\u002F Step 1: Fetch the authHash from your backend\n    const customerId = 'CUSTOMER_ID'; \u002F\u002F Get from your logged-in user session\n\n    const response = await fetch('\u002Fapi\u002Fchurnkey', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application\u002Fjson',\n      },\n      body: JSON.stringify({\n        customerId: customerId,\n      }),\n    });\n\n    if (!response.ok) {\n      console.error('Failed to fetch authHash');\n      return;\n    }\n\n    const { userHash } = await response.json();\n\n    \u002F\u002F Step 2: Initialize and display Churnkey with the authHash\n    window.churnkey.init('show', {\n      customerId: customerId, \u002F\u002F required unless Paddle\n      authHash: userHash, \u002F\u002F required - fetched from backend\n      subscriptionId: 'SUBSCRIPTION_ID', \u002F\u002F recommended unless Paddle\n      appId: 'YOUR_APP_ID', \u002F\u002F required\n      mode: 'live', \u002F\u002F 'live', 'test', or 'sandbox' (Stripe only)\n      provider: 'stripe', \u002F\u002F set to 'stripe', 'chargebee', 'braintree', 'paddle'\n    });\n  } catch (error) {\n    console.error('Error initializing Churnkey:', error);\n  }\n});",{"id":413,"title":414,"titles":415,"content":416,"level":417},"\u002Fcancel-flows\u002Fquick-start-guide#framework-specific-examples","Framework-Specific Examples",[16,409],"import { useState } from 'react';\n\nfunction CancelButton() {\n  const [loading, setLoading] = useState(false);\n\n  const handleCancel = async () => {\n    setLoading(true);\n    try {\n      const customerId = user.customerId; \u002F\u002F From your auth context\n\n      const response = await fetch('\u002Fapi\u002Fchurnkey', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application\u002Fjson' },\n        body: JSON.stringify({ customerId }),\n      });\n\n      const { userHash } = await response.json();\n\n      window.churnkey.init('show', {\n        customerId: customerId,\n        authHash: userHash,\n        subscriptionId: user.subscriptionId,\n        appId: 'YOUR_APP_ID',\n        mode: 'live',\n        provider: 'stripe',\n      });\n    } catch (error) {\n      console.error('Error:', error);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  return (\n    \u003Cbutton onClick={handleCancel} disabled={loading}>\n      {loading ? 'Loading...' : 'Cancel Subscription'}\n    \u003C\u002Fbutton>\n  );\n}\n\u003Ctemplate>\n  \u003Cbutton @click=\"handleCancel\" :disabled=\"loading\">\n    {{ loading ? 'Loading...' : 'Cancel Subscription' }}\n  \u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nexport default {\n  data() {\n    return {\n      loading: false,\n    };\n  },\n  methods: {\n    async handleCancel() {\n      this.loading = true;\n      try {\n        const customerId = this.$auth.user.customerId; \u002F\u002F From your auth\n\n        const response = await fetch('\u002Fapi\u002Fchurnkey', {\n          method: 'POST',\n          headers: { 'Content-Type': 'application\u002Fjson' },\n          body: JSON.stringify({ customerId }),\n        });\n\n        const { userHash } = await response.json();\n\n        window.churnkey.init('show', {\n          customerId: customerId,\n          authHash: userHash,\n          subscriptionId: this.$auth.user.subscriptionId,\n          appId: 'YOUR_APP_ID',\n          mode: 'live',\n          provider: 'stripe',\n        });\n      } catch (error) {\n        console.error('Error:', error);\n      } finally {\n        this.loading = false;\n      }\n    },\n  },\n};\n\u003C\u002Fscript>\n'use client';\n\nimport { useState } from 'react';\n\nexport default function CancelButton({ user }: { user: User }) {\n  const [loading, setLoading] = useState(false);\n\n  const handleCancel = async () => {\n    setLoading(true);\n    try {\n      const response = await fetch('\u002Fapi\u002Fchurnkey', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application\u002Fjson' },\n        body: JSON.stringify({ customerId: user.customerId }),\n      });\n\n      const { userHash } = await response.json();\n\n      window.churnkey.init('show', {\n        customerId: user.customerId,\n        authHash: userHash,\n        subscriptionId: user.subscriptionId,\n        appId: process.env.NEXT_PUBLIC_CHURNKEY_APP_ID,\n        mode: 'live',\n        provider: 'stripe',\n      });\n    } catch (error) {\n      console.error('Error:', error);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  return (\n    \u003Cbutton onClick={handleCancel} disabled={loading}>\n      {loading ? 'Loading...' : 'Cancel Subscription'}\n    \u003C\u002Fbutton>\n  );\n}",3,{"id":419,"title":420,"titles":421,"content":422,"level":374},"\u002Fcancel-flows\u002Fquick-start-guide#troubleshooting-common-issues","Troubleshooting Common Issues",[16],"",{"id":424,"title":425,"titles":426,"content":427,"level":417},"\u002Fcancel-flows\u002Fquick-start-guide#hmac-authentication-error-invalid-authhash","HMAC Authentication Error: \"Invalid authHash\"",[16,420],"If you receive an authentication error when initializing Churnkey, this indicates that the HMAC hash generated by your backend doesn't match what Churnkey expects. Verify your HMAC implementation Churnkey provides an HMAC verification tool in the dashboard to help you debug authentication issues: Navigate to Settings (bottom left corner)Click Installation in the top menuScroll down to the Verify Your Server Implementation sectionSelect the Production API Key or Test API Key (depending on which environment you're testing)Enter the customerId in the input fieldCompare this hash with what your backend is producing Common causes of HMAC mismatches: Extra whitespace: Make sure there are no leading or trailing spaces in your Customer ID or API Key when generating the hash.Encoding issues: The HMAC hash should be a hexadecimal string. Verify that your implementation uses .digest(\"hex\") or equivalent in your language.API key mismatch: Ensure you're using the correct API key for your environment (Test API Key for test mode, Production API Key for live mode).",{"id":429,"title":430,"titles":431,"content":432,"level":417},"\u002Fcancel-flows\u002Fquick-start-guide#cancel-flow-modal-not-displaying","Cancel Flow Modal Not Displaying",[16,420],"If the Churnkey modal doesn't appear after clicking the cancel button, this is typically caused by a timing issue in server-side rendered (SSR) applications. The Churnkey script attempts to initialize before the page has fully hydrated, preventing the modal from rendering correctly. What's happening: When your application uses server-side rendering (Next.js, Nuxt, SvelteKit, etc.), the page goes through a hydration process where the server-rendered HTML is converted into an interactive client-side application. If the Churnkey script loads during this hydration phase, window.churnkey may not be fully initialized when you call window.churnkey.init('show'). While the API call succeeds, the modal interface fails to render. Solution: Ensure the Churnkey script loads after the page has completed hydration. In most modern frameworks, this means loading the script within a client-side lifecycle method or hook. Framework-specific implementations: import { useEffect } from 'react';\n\nfunction App() {\n  useEffect(() => {\n    \u002F\u002F Load Churnkey script after component mounts (hydration complete)\n    if (!window.churnkey || !window.churnkey.created) {\n      window.churnkey = { created: true };\n      const script = document.createElement('script');\n      script.src = 'https:\u002F\u002Fassets.churnkey.co\u002Fjs\u002Fapp.js?appId=YOUR_APP_ID';\n      script.async = true;\n      document.head.appendChild(script);\n    }\n  }, []);\n\n  return \u003Cdiv>{\u002F* Your app content *\u002F}\u003C\u002Fdiv>;\n}\n'use client';\n\nimport { useEffect } from 'react';\n\nexport default function ChurnkeyProvider({ children }: { children: React.ReactNode }) {\n  useEffect(() => {\n    \u002F\u002F Load Churnkey script after client-side hydration\n    if (!window.churnkey || !window.churnkey.created) {\n      window.churnkey = { created: true };\n      const script = document.createElement('script');\n      script.src = `https:\u002F\u002Fassets.churnkey.co\u002Fjs\u002Fapp.js?appId=${process.env.NEXT_PUBLIC_CHURNKEY_APP_ID}`;\n      script.async = true;\n      document.head.appendChild(script);\n    }\n  }, []);\n\n  return \u003C>{children}\u003C\u002F>;\n}\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003C!-- Your app content -->\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nexport default {\n  mounted() {\n    \u002F\u002F Load Churnkey script after component mounts\n    if (!window.churnkey || !window.churnkey.created) {\n      window.churnkey = { created: true };\n      const script = document.createElement('script');\n      script.src = 'https:\u002F\u002Fassets.churnkey.co\u002Fjs\u002Fapp.js?appId=YOUR_APP_ID';\n      script.async = true;\n      document.head.appendChild(script);\n    }\n  },\n};\n\u003C\u002Fscript>",{"id":434,"title":269,"titles":435,"content":422,"level":374},"\u002Fcancel-flows\u002Fquick-start-guide#frequently-asked-questions",[16],{"id":437,"title":438,"titles":439,"content":440,"level":417},"\u002Fcancel-flows\u002Fquick-start-guide#why-do-i-need-to-fetch-the-authhash-every-time","Why do I need to fetch the authHash every time?",[16,269],"The authHash must be fetched fresh each time a customer initiates cancellation. Never hardcode the authHash or cache it on the frontend, as this would compromise security. Fetching it from your backend ensures that: Each cancellation request is authenticated through your backendOnly logged-in customers can generate valid authHash valuesThe authHash is tied to the specific customer making the request html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .smKOL, html code.shiki .smKOL{--shiki-light:#E53935;--shiki-default:#116329;--shiki-dark:#7EE787}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sH4kE, html code.shiki .sH4kE{--shiki-light:#FF5370;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sIO_5, html code.shiki .sIO_5{--shiki-light:#F76D47;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sXZA8, html code.shiki .sXZA8{--shiki-light:#E53935;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sHdwX, html code.shiki .sHdwX{--shiki-light:#E2931D;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sTlLh, html code.shiki .sTlLh{--shiki-light:#39ADB5;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html pre.shiki code .sY2fa, html code.shiki .sY2fa{--shiki-light:#6182B8;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sRZlD, html code.shiki .sRZlD{--shiki-light:#6182B8;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sFKTw, html code.shiki .sFKTw{--shiki-light:#90A4AE;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sIfGp, html code.shiki .sIfGp{--shiki-light:#39ADB5;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .scASs, html code.shiki .scASs{--shiki-light:#F76D47;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .swYII, html code.shiki .swYII{--shiki-light:#9C3EDA;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sZtn1, html code.shiki .sZtn1{--shiki-light:#E2931D;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sH12a, html code.shiki .sH12a{--shiki-light:#E53935;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .s1PQW, html code.shiki .s1PQW{--shiki-light:#90A4AE;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sEbZY, html code.shiki .sEbZY{--shiki-light:#9C3EDA;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sjIoE, html code.shiki .sjIoE{--shiki-light:#E53935;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}",{"id":21,"title":20,"titles":442,"content":443,"level":368},[],"Implement automatic compliance with global subscription cancellation requirements",{"id":445,"title":5,"titles":446,"content":447,"level":374},"\u002Fcancel-flows\u002Fclick-to-cancel#overview",[20],"\"Click-to-Cancel\" requirements have cropped up around the world in various forms, with the common thread that each law or rule requires subscription businesses to make their cancellation process as straightforward as their sign-up process. The goal of \"Click-to-Cancel\" rules is to eliminate complicated cancellation processes for customers. The core principle is symmetry: if a customer can subscribe with a few clicks, they should be able to cancel just as easily. This means no mandatory interactions with customer service, no complex flows, and no hidden cancellation options. Cancellations must be processed immediately upon request, with a clearly visible and accessible cancellation option.",{"id":449,"title":450,"titles":451,"content":452,"level":374},"\u002Fcancel-flows\u002Fclick-to-cancel#compliance-configuration","Compliance Configuration",[20],"Churnkey offers two compliance options to meet different regulatory requirements. You can choose the level of compliance that best fits your business needs.",{"id":454,"title":455,"titles":456,"content":457,"level":417},"\u002Fcancel-flows\u002Fclick-to-cancel#single-click-cancellation-compliance","Single-Click Cancellation Compliance",[20,450],"This option makes your Cancel Flows compliant with French, German, European Union, and US state laws for California and New York. When enabled, customers in these jurisdictions will see a \"Cancel Now →\" option throughout their Cancel Flow session. The following regions are currently supported under this compliance option: RegionStatusEffective DateEuropean UnionActiveCurrentFranceActiveCurrentGermanyActiveCurrentCaliforniaActiveCurrentNew YorkActiveCurrent",{"id":459,"title":460,"titles":461,"content":462,"level":417},"\u002Fcancel-flows\u002Fclick-to-cancel#ftc-click-to-cancel-compliance","FTC \"Click-to-Cancel\" Compliance",[20,450],"While Churnkey is by default FTC-compliant, this toggle provides a maximal solution to support the FTC rule with single-click cancellation support for all US states. This ensures comprehensive compliance across the entire United States.",{"id":464,"title":465,"titles":466,"content":467,"level":417},"\u002Fcancel-flows\u002Fclick-to-cancel#how-it-works","How It Works",[20,450],"When a customer triggers a Cancel Flow, Churnkey automatically detects their location using browser metadata, billing information, and IP geolocation. Based on your selected compliance settings, the appropriate cancellation options will be displayed to meet regulatory requirements. Both compliance options are currently available and require no additional setup. Churnkey handles the complexity of different regional requirements so you can focus on your business.",{"id":469,"title":470,"titles":471,"content":422,"level":374},"\u002Fcancel-flows\u002Fclick-to-cancel#faq","FAQ",[20],{"id":473,"title":474,"titles":475,"content":476,"level":417},"\u002Fcancel-flows\u002Fclick-to-cancel#how-can-i-track-customers-who-used-the-click-to-cancel-button","How can I track customers who used the Click-to-Cancel button?",[20,470],"You can view customers who used the Cancel Now button in Cancel Flows > Analytics > Activity Stream. These customers will be flagged with their region and \"Click-to-Cancel\" indicator.",{"id":478,"title":479,"titles":480,"content":481,"level":417},"\u002Fcancel-flows\u002Fclick-to-cancel#can-i-disable-the-click-to-cancel-compliance-features","Can I disable the Click-to-Cancel compliance features?",[20,470],"Yes. Both compliance options are enabled by default, but you can manage them in Cancel Flows > Settings > Automatic Compliance. You'll find toggles for both Single-Click and FTC compliance options.",{"id":483,"title":484,"titles":485,"content":486,"level":417},"\u002Fcancel-flows\u002Fclick-to-cancel#can-i-access-click-to-cancel-data-via-webhooks","Can I access Click-to-Cancel data via webhooks?",[20,470],"Yes. The usedClickedToCancel field is available in the session's payload when using webhooks. This allows you to programmatically track when customers use the Cancel Now button. Note: This method requires technical implementation to set up webhook endpoints.",{"id":25,"title":24,"titles":488,"content":489,"level":368},[],"Collect deeper cancellation insights and deliver targeted retention offers with a second layer of predefined follow-up options in your cancel flow survey. When a customer selects \"Too Expensive\" as their cancellation reason, do you know whether they mean they are not seeing ROI, their company cut budgets, or the price is simply too high? Each of those situations calls for a different retention strategy, yet a single survey choice treats them all the same. Structured Follow-Up Questions solve this by adding a second screen of predefined options after the primary cancellation survey, so you can capture the real reason and respond with the right offer.",{"id":491,"title":5,"titles":492,"content":493,"level":374},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#overview",[24],"Structured Follow-Up Questions extend the Cancellation Survey step in your cancel flow. After a customer selects a primary cancel reason, they see a second screen that presents a set of specific follow-up options you define. Each follow-up option can carry its own retention offer: a discount, a pause, a plan change, or any other offer type your flow supports. The result is a two-level feedback loop: Level 1 tells you the general category, such as \"Too Expensive\"Level 2 tells you the precise situation, such as \"Not seeing enough ROI\"The offer matches that precise situation: a 30% discount to buy more time This matters because retention offers perform significantly better when they match the customer's actual problem. A customer whose company cut budgets is unlikely to respond to a discount the same way as a customer who simply finds the price too high relative to value. Structured follow-ups give you the specificity to make that distinction and act on it.",{"id":495,"title":496,"titles":497,"content":498,"level":374},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#how-follow-up-questions-work-in-the-customer-experience","How Follow-Up Questions Work in the Customer Experience",[24],"To understand the configuration, it helps to see what the customer actually sees. Here is the complete flow from the customer's perspective. The screenshots below show one example configuration; your survey questions, choices, and follow-up options will reflect whatever you set up in the builder.",{"id":500,"title":501,"titles":502,"content":503,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#step-1-primary-survey","Step 1: Primary Survey",[24,496],"The customer clicks \"Cancel Subscription\" in your product and enters your cancel flow. When they reach the Cancellation Survey step, they see the primary question, for example \"What's going wrong?\", with the options you have defined (Budget, No Longer Need, Missing Features, Technical Issues, and so on). The customer selects \"Budget\", and the form immediately advances to the next screen. The customer selects a primary cancel reason from your configured choices",{"id":505,"title":506,"titles":507,"content":508,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#step-2-follow-up-question","Step 2: Follow-Up Question",[24,496],"Instead of moving directly to the next step in your flow, the customer now sees a second screen titled with their selected answer, \"Budget.\" Below the title, a follow-up question specific to that reason appears. In this case, the question \"What could we have done better?\" is shown with options like: Company budget cutsNot seeing enough ROIOverall price is too high for what I getFound a cheaper alternative The follow-up screen presents specific sub-reasons within the selected category",{"id":510,"title":511,"titles":512,"content":513,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#step-3-targeted-offer","Step 3: Targeted Offer",[24,496],"The customer selects one of these options and continues. If you associated an offer with that particular follow-up option, they see it next. If you did not, the flow proceeds to the next configured step as usual. Throughout this process, the customer can navigate back to the primary survey screen if they want to change their initial selection. An optional freeform text input can appear alongside the structured options, giving customers the ability to elaborate in their own words while still selecting a predefined category.",{"id":515,"title":516,"titles":517,"content":518,"level":374},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#the-question-cascade","The Question Cascade",[24],"The Question Cascade is the branching logic that powers structured follow-ups. Each primary survey choice can branch into its own set of follow-up options, and each follow-up option can trigger a different retention offer. The result is a tree of paths where one initial question expands into highly specific outcomes. The table below walks through three primary survey choices and shows how each one branches into follow-up options with targeted offers. Primary Reason\n      Follow-Up Question\n      Follow-Up Option\n      Retention Offer\n    \n  \n  \n    \n      Budget\n      \"What could we have done better?\"\n      Company budget cuts\n      Pause 3 Months\n    \n    \n      Not seeing enough ROI\n      30% Discount\n    \n    \n      Overall price is too high for what I get\n      Hidden Plan\n    \n    \n      Found a cheaper alternative\n      40% Discount\n    \n    \n      Missing Features\n      \"Which feature matters most?\"\n      API Access\n      Switch Subscription\n    \n    \n      Mobile App\n      Trial Extension\n    \n    \n      SSO \u002F SAML\n      No offer\n    \n    \n      Other\n      Freeform text input only. The customer types their reason in their own words\n      No offer Notice how \"Budget\" branches into four distinct paths, each with a different retention strategy. A customer dealing with company budget cuts needs a pause, not a discount. A customer who found a cheaper alternative needs a bigger discount, not a hidden plan. One extra question yields dramatically better offer targeting. The same principle applies to \"Missing Features.\" Knowing the customer wants API access, where you can offer a switch to a plan that includes it, rather than SSO, where no relevant offer is available, makes the difference between a relevant retention attempt and a wasted one.",{"id":520,"title":521,"titles":522,"content":523,"level":374},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#response-type-modes","Response Type Modes",[24],"Each survey choice in your Cancellation Survey can be configured with one of three follow-up response types. The mode you choose determines what the customer sees after selecting that primary reason.",{"id":525,"title":526,"titles":527,"content":528,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#freeform","Freeform",[24,521],"The existing behavior. The customer sees only a text input field where they can type a response in their own words. No structured options are presented. This mode works well for survey choices where the range of possible reasons is too broad to anticipate, such as \"Other\" or \"Something else\".",{"id":530,"title":531,"titles":532,"content":533,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#structured","Structured",[24,521],"A set of predefined radio button options with no text input. The customer must select one of the options to continue. This mode is ideal when you have a clear understanding of the specific sub-reasons within a category and want clean, categorized data for analysis and offer targeting.",{"id":535,"title":536,"titles":537,"content":538,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#freeform-structured","Freeform + Structured",[24,521],"Combines both. The customer sees the predefined options and a text input field. They can select a structured option, type freeform text, or do both. This is the most flexible mode and works well when you want categorized data but also want to capture unexpected feedback. The label on the freeform input field is configurable, so you can prompt the customer with something specific like \"Tell us more about your situation\" rather than a generic placeholder. Response type configuration in the builder. Choose the mode that matches your data needs for each survey choice Choose Structured when you want clean data and precise offer targeting. Choose Freeform + Structured when you also want qualitative feedback. Reserve Freeform for catch-all choices where predefined options would feel forced.",{"id":540,"title":541,"titles":542,"content":422,"level":374},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#setting-up-structured-follow-up-questions","Setting Up Structured Follow-Up Questions",[24],{"id":544,"title":545,"titles":546,"content":547,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#accessing-the-configuration","Accessing the Configuration",[24,541],"Navigate to Cancel Flows in your Churnkey dashboardOpen the Builder for the cancel flow you want to editSelect the Survey step in your flow You will see the list of survey choices you have already configured. Each choice now displays an \"Add follow-up question\" button. The \"Add follow-up question\" button appears on each survey choice in the builder",{"id":549,"title":550,"titles":551,"content":552,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#adding-follow-up-options-to-a-survey-choice","Adding Follow-Up Options to a Survey Choice",[24,541],"Click the \"Add follow-up question\" button on the survey choice you want to enhance. A configuration panel opens where you set up the follow-up. Select the response type. Choose between Freeform, Structured, or Freeform + Structured. If you select Structured or Freeform + Structured, the panel expands to show the options editor. Add your follow-up options. Type the text for each option. Each option appears as a radio button in the customer-facing follow-up screen. You can add, remove, and reorder options by dragging them into position. The order you set here is the order the customer sees. Configure the freeform label (Freeform + Structured mode only). Set the placeholder text or label that appears on the text input field alongside the structured options. Save your changes. The follow-up is now attached to that survey choice. The configuration panel lets you choose the response type, add options, and set the freeform label Repeat this process for each survey choice where you want a structured follow-up. You do not need to add follow-ups to every choice. Survey choices without a follow-up continue to work exactly as before; the customer selects the choice and moves to the next step in the flow.",{"id":554,"title":555,"titles":556,"content":557,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#associating-offers-with-follow-up-options","Associating Offers with Follow-Up Options",[24,541],"This is where the feature delivers its full value. Instead of mapping one offer to one survey choice, you can now map different offers to different follow-up options within the same survey choice. In the Cancel Flow Builder, switch to the Offer Builder tabSelect the survey choice that has follow-up options configuredThe interface expands to show each follow-up option with its own offer editor The Offers tab shows each follow-up option with its own independent offer editor For each follow-up option, you can assign any offer type supported by your flow: Offer TypeBest ForExampleDiscountPrice sensitivity, ROI concerns30% off for 3 monthsPause SubscriptionTemporary circumstances, budget freezes2-month payment pauseSwitch PlanFeature mismatch, over-provisioningDowngrade to a lower tierTrial ExtensionNot enough time to evaluate14-day extensionContact Us \u002F Custom RedirectComplex issues needing human conversationRoute to support team You do not need to assign an offer to every follow-up option. If a follow-up option has no associated offer, the customer proceeds to the next step in the flow after making their selection. Each follow-up option has its own offer type dropdown with all available offer types Important: Follow-up option offers take priority over parent choice offers. If you assign an offer to both a primary survey choice and its follow-up options, the follow-up option's offer will be shown instead of the parent choice's offer. When using structured follow-ups, configure your offers on the individual follow-up options rather than on the parent choice to ensure each path triggers the intended offer.",{"id":559,"title":560,"titles":561,"content":562,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#worked-example-too-expensive-with-targeted-offers","Worked Example: \"Too Expensive\" with Targeted Offers",[24,541],"Suppose your cancel flow survey includes a \"Too Expensive\" choice. Here is how you might configure the structured follow-up: Primary survey choice: Too Expensive Follow-up options and their associated offers: Follow-Up OptionOffer TypeOffer DetailsRationaleNot seeing enough ROIDiscount30% off for 3 monthsGive the customer time to discover value at a lower costCompany budget cutsPause2-month pauseAccommodate temporary financial constraints without losing the accountOverall price is too highPlan ChangeSwitch to a lower-tier planRetain the customer at a sustainable price pointFound a cheaper alternativeDiscount40% off for 2 monthsCompete on price to keep the customer while demonstrating value With this configuration, a customer who selects \"Too Expensive\" and then \"Company budget cuts\" sees a pause offer. A different customer who selects \"Too Expensive\" and then \"Not seeing enough ROI\" sees a discount. The primary survey choice is the same, but the retention strategy is tailored to the actual situation.",{"id":564,"title":565,"titles":566,"content":567,"level":374},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#analyzing-follow-up-response-data","Analyzing Follow-Up Response Data",[24],"Structured Follow-Up Questions generate a new layer of data in your cancel flow analytics. Two dedicated visualizations help you understand how customers move through the survey and what their responses mean for your revenue.",{"id":569,"title":97,"titles":570,"content":571,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#response-flow",[24,565],"The Response Flow is a Sankey diagram that visualizes the relationship between primary survey choices and their follow-up selections. Navigate to Cancel Flows > Analytics to find this visualization. The left side of the diagram shows your primary survey choices. The right side shows the follow-up options selected by customers. Colored flows connect each primary choice to its follow-up selections, with the width of each flow proportional to the number of responses. The Response Flow Sankey diagram reveals how customers distribute across follow-up options within each primary reason Hover over any flow to see a tooltip with the response count, the percentage of total responses that path represents, and the associated MRR (Monthly Recurring Revenue). This tells you not just how many customers follow a particular path, but how much revenue those customers represent. The Response Flow answers a key question: where are your customers going after they pick a primary reason? If 60% of \"Too Expensive\" respondents select \"Not seeing enough ROI\", that tells you the problem is not your price, it is your value communication. The flow diagram makes this pattern immediately visible. Export options: Download the visualization as a PNG image for presentations, or export the underlying data as a CSV file for further analysis in a spreadsheet. See the full Response Flow documentation for detailed guidance on reading and using this diagram.",{"id":573,"title":101,"titles":574,"content":575,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#response-explorer",[24,565],"The Response Explorer provides a Treemap visualization that shows the distribution of follow-up responses across all your survey choices. Each cell in the treemap represents a unique follow-up option, sized by either response count or MRR impact. The Response Explorer Treemap shows follow-up response distribution, sized by count or MRR impact Toggle between views. Use the toggle at the top of the visualization to switch between Response Count (how many customers selected each follow-up option) and MRR Impact (the total monthly recurring revenue represented by customers who selected each option). These two views often tell different stories. A follow-up option with a modest response count might represent a disproportionately large share of MRR if it is selected by higher-value customers. The MRR Impact view reorders the treemap by revenue. \"Overall price is too high\" jumps to the largest cell despite having fewer responses than \"Not seeing enough ROI\" Drill down into individual responses. Click any cell in the treemap to open a detailed drill-down modal. Inside the modal, you will find: Trend chart. A line chart showing how the response count for that follow-up option has changed over time. Use this to spot emerging trends. A sudden spike in \"Company budget cuts\" might correlate with a macroeconomic event or a change in your pricing. Search and filter. Search across responses, questions, or customer email addresses to find specific entries. Sorting options. Sort individual response records by newest first, oldest first, highest MRR, or lowest MRR. Sorting by MRR is particularly useful when you want to prioritize outreach to the customers whose retention has the greatest revenue impact. Individual response cards. Each card shows the customer's email, their primary survey selection, their follow-up selection, their subscription details, and the MRR at risk. This gives you everything you need to understand a single customer's situation or to follow up with a personalized outreach. Export: Download the Response Explorer data as a CSV file for deeper analysis or integration with other tools. See the full Response Explorer documentation for detailed guidance on workflows and analysis techniques.",{"id":577,"title":578,"titles":579,"content":422,"level":374},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#best-practices","Best Practices",[24],{"id":581,"title":582,"titles":583,"content":584,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#designing-effective-follow-up-options","Designing Effective Follow-Up Options",[24,578],"Keep your list to 3-6 options per survey choice. Fewer than three options provides little added value over a single survey choice. More than six creates decision fatigue and slows the cancellation flow down, which can frustrate customers and reduce completion rates. If you find yourself writing more than six options, consider whether some of them could be consolidated or whether the primary survey choice is too broad. Make each option specific and actionable. A good follow-up option tells you something you can act on. \"Not seeing enough ROI\" is actionable: you can respond with a discount to buy more time, or with an onboarding session to demonstrate value. \"Just not working for me\" is not actionable because it does not tell you what to fix or what to offer. Write options that connect to specific retention strategies or product improvements. Use \"Freeform + Structured\" for your most important survey choices. Your top two or three most-selected survey choices deserve the richest data collection. Structured options give you clean categories for analysis and offer targeting. The freeform input catches reasons you did not anticipate. Together, they ensure you are never missing critical feedback. Write follow-up options from the customer's perspective. Use first-person language that mirrors how customers think about their situation. \"My team is not using it enough\" resonates more than \"Low team adoption\" because it matches the customer's internal narrative. This improves selection accuracy and makes the experience feel more empathetic.",{"id":586,"title":587,"titles":588,"content":589,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#targeting-offers-strategically","Targeting Offers Strategically",[24,578],"Associate offers with your most common follow-up responses first. Check your analytics after the first two weeks to see which follow-up options are selected most frequently. Assign offers to those first, then work down the list. There is no need to assign offers to every option on day one. Match the offer type to the follow-up reason. A discount addresses price sensitivity. A pause addresses temporary situations. A plan change addresses feature mismatch or over-provisioning. A support redirect addresses confusion or unresolved issues. When the offer type matches the stated problem, acceptance rates increase because the customer feels understood. Reserve your strongest offers for high-MRR follow-up paths. Use the Response Explorer's MRR Impact view to identify which follow-up paths represent the most revenue at risk. Allocate your most generous offers, such as larger discounts, longer pauses, and exclusive plans, to those paths. The ROI of saving a $500\u002Fmonth customer justifies a more aggressive offer than saving a $15\u002Fmonth customer. Place offers on follow-up options, not on parent choices. When a survey choice has structured follow-up options, offers configured on the individual follow-up options always take priority over an offer on the parent survey choice. For the clearest behavior, configure your offers on the follow-up options directly.",{"id":591,"title":592,"titles":593,"content":594,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#reviewing-and-iterating","Reviewing and Iterating",[24,578],"Review the Response Flow weekly during the first month. The Sankey diagram reveals patterns quickly. You might discover that a follow-up option you expected to be popular is rarely selected, or that an option you did not prioritize is attracting high-MRR customers. These insights should drive both your offer assignments and your follow-up option wording. Use the MRR Impact view in Response Explorer to prioritize action. Response count shows breadth of an issue. MRR Impact shows its financial significance. A follow-up option selected by 5% of respondents might still represent 20% of churning MRR. Always check both dimensions before deciding where to invest effort. Refine your follow-up options quarterly. Customer behavior and language shift over time. Options that were highly relevant six months ago may become less so as your product, pricing, or market evolves. Review the data, retire options that no longer attract meaningful selections, and add new ones that reflect emerging patterns from your freeform responses.",{"id":596,"title":597,"titles":598,"content":599,"level":374},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#compatibility","Compatibility",[24],"Structured Follow-Up Questions work with all existing cancel flow features: Multi-language support. Follow-up options can be translated alongside your primary survey choices through the same translation workflow.Customer segments. Each segment's cancel flow can have its own set of follow-up options tailored to that audience.A\u002FB tests. Each variant of the flow can test different follow-up configurations independently.All offer types. Discounts, pauses, plan changes, trial extensions, contact forms, and custom redirects are all supported on follow-up options.All billing providers. Works with Stripe, Chargebee, Braintree, Paddle Billing, and Maxio. Structured Follow-Up Questions require the feature to be enabled for your organization. If you do not see the \"Add follow-up question\" button on your survey choices, contact your Customer Success manager or email support to request activation.",{"id":601,"title":269,"titles":602,"content":422,"level":374},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#frequently-asked-questions",[24],{"id":604,"title":605,"titles":606,"content":607,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#do-i-need-to-add-follow-up-questions-to-every-survey-choice","Do I need to add follow-up questions to every survey choice?",[24,269],"No. Follow-up questions are optional per survey choice. You can add them to only the choices where deeper specificity improves your retention strategy. Survey choices without a follow-up continue to work exactly as they do today.",{"id":609,"title":610,"titles":611,"content":612,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#can-a-follow-up-option-have-the-same-offer-as-the-primary-survey-choice","Can a follow-up option have the same offer as the primary survey choice?",[24,269],"Yes. If you want a follow-up option to present the same offer that was previously mapped to the primary choice, you can configure that. However, the value of structured follow-ups comes from varying the offer based on the specific sub-reason, so consider whether the same offer is truly the best response for every follow-up option.",{"id":614,"title":615,"titles":616,"content":617,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#what-happens-if-i-have-offers-on-both-the-parent-choice-and-the-follow-up-options","What happens if I have offers on both the parent choice and the follow-up options?",[24,269],"The follow-up option's offer takes priority. When a customer selects a follow-up option that has its own offer, that offer is shown instead of any offer on the parent survey choice. To avoid confusion, we recommend placing offers exclusively on the follow-up options when structured follow-ups are enabled for a survey choice.",{"id":619,"title":620,"titles":621,"content":622,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#what-happens-if-a-customer-navigates-back-from-the-follow-up-screen","What happens if a customer navigates back from the follow-up screen?",[24,269],"The customer returns to the primary survey screen and can select a different reason. Their previous follow-up selection is not recorded as a final response until they continue past the follow-up screen.",{"id":624,"title":625,"titles":626,"content":627,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#how-do-follow-up-responses-appear-in-my-existing-analytics","How do follow-up responses appear in my existing analytics?",[24,269],"Follow-up data appears in two new analytics views: the Response Flow (Sankey diagram) and the Response Explorer (Treemap). Your existing cancellation trends, save rate, and session outcome metrics continue to work as before and are not affected by the addition of follow-up questions.",{"id":629,"title":630,"titles":631,"content":632,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#can-i-reorder-follow-up-options-after-creating-them","Can I reorder follow-up options after creating them?",[24,269],"Yes. Open the follow-up configuration for the survey choice, then drag options into the order you want. The order you set is the order customers see.",{"id":634,"title":635,"titles":636,"content":637,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#does-the-follow-up-screen-add-friction-to-the-cancel-flow","Does the follow-up screen add friction to the cancel flow?",[24,269],"It adds one additional screen to the flow for customers who select a survey choice that has follow-ups configured. In practice, this additional step is brief; the customer clicks one radio button and continues. The tradeoff is worth it because the specificity it provides improves offer targeting, which directly increases acceptance rates and retention.",{"id":639,"title":640,"titles":641,"content":642,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#can-i-use-follow-up-questions-with-the-freeform-feedback-step","Can I use follow-up questions with the Freeform Feedback step?",[24,269],"These are separate features. Structured Follow-Up Questions are part of the Cancellation Survey step. The Freeform Feedback step is a standalone step later in your flow. You can use both in the same cancel flow without conflict.",{"id":644,"title":645,"titles":646,"content":647,"level":417},"\u002Fcancel-flows\u002Fstructured-follow-up-questions#is-there-a-limit-to-how-many-follow-up-options-i-can-create","Is there a limit to how many follow-up options I can create?",[24,269],"There is no hard technical limit, but we strongly recommend keeping each follow-up to 3-6 options. Beyond that, customer decision fatigue reduces completion rates and data quality.",{"id":29,"title":28,"titles":649,"content":650,"level":368},[],"Configure pause invoice behavior, coupon stacking, and session recording directly from the dashboard — no redeploy required. Some Cancel Flow behaviors used to be controllable only through parameters passed to the JavaScript embed. Every adjustment meant another engineering ticket and another deploy. The payment provider settings panel — labeled with your provider's name (Stripe Settings, Chargebee Settings, etc.) — lifts three of those behaviors into the dashboard so your Ops, Growth, and CX teams can iterate on them directly. This page covers what each setting does, when to reach for it, how it interacts with the JavaScript embed, and what stays in code.",{"id":652,"title":653,"titles":654,"content":655,"level":374},"\u002Fcancel-flows\u002Fcancel-flow-settings#accessing-the-settings","Accessing the settings",[28],"All three toggles live on a single panel. Open the Churnkey dashboard and go to Cancel Flow → Settings.Scroll to the section named after your billing provider (for example, Stripe Settings).Flip any toggle. Changes take effect on the next Cancel Flow session — you do not need to redeploy the embed. These settings apply account-wide to every Cancel Flow that runs under your Churnkey workspace. If you run A\u002FB tests or segmented flows, the same Payment Provider Settings apply to all variants.",{"id":657,"title":658,"titles":659,"content":660,"level":374},"\u002Fcancel-flows\u002Fcancel-flow-settings#pause-invoice-behavior","Pause invoice behavior",[28],"When a customer accepts a pause offer and your billing provider is Stripe, Churnkey pauses the subscription using Stripe's native pause collection feature. Stripe requires you to pick what happens to invoices generated during the paused window, and the Pause Invoice Behavior setting controls that choice. Two options are available. Mark uncollectible is the default. Stripe still creates invoices during the pause, but tags them as uncollectible so no collection attempts are made. When the pause ends, billing resumes normally. This is the cleanest audit trail: you have a full history of what would have been charged, and Stripe handles the bookkeeping. Void tells Stripe to void the invoice entirely. No invoice exists after the pause takes effect, and nothing shows up in uncollectible reports. Which one should you pick? It comes down to how your finance team counts revenue. Some teams treat mark_uncollectible invoices as churned revenue in their MRR reporting — because a real invoice was generated and never paid, it looks like a loss on paper. If that is your situation, switch to void so pauses stay neutral in your financials. If your reporting already excludes uncollectible invoices from churn, leave it on the default. This setting only affects Stripe. Chargebee, Braintree, Paddle Billing, Paddle Classic, and Maxio use their own pause mechanisms and ignore this toggle. This setting has no JavaScript equivalent. It is a dashboard-only control, so there is nothing to change in your embed code when you flip it.",{"id":662,"title":663,"titles":664,"content":665,"level":374},"\u002Fcancel-flows\u002Fcancel-flow-settings#stack-coupons","Stack coupons",[28],"When a customer accepts a discount offer, Churnkey applies the new coupon to their subscription. By default, applying a new coupon replaces any coupon that was already on the subscription — Stripe only supports one active coupon per subscription at a time in most cases, so Churnkey removes the existing one before attaching the new one. The Stack Coupons toggle changes that behavior. When enabled, Churnkey keeps the existing coupon in place and attaches the new one on top of it, allowing the discounts to compound. Before this toggle existed, the only way to stack coupons was to write custom logic inside the handleDiscount callback — meaning any change required engineering work and a deploy. The dashboard toggle removes that requirement entirely. When stacking is enabled, Churnkey also updates the customer-facing experience. The \"This will override your previous coupon\" disclaimer that normally appears on the Discount Offer screen of the embed is hidden automatically, since the statement is no longer true. Stack Coupons applies only to subscriptions. One-time payments are not affected by this setting. When should you stack? Stacking is useful when you want layered retention incentives — for example, keeping an existing annual-plan coupon in place while adding a temporary cancel-save discount on top. It is also handy if your legacy customers already carry grandfathered coupons that you do not want the Cancel Flow to wipe out. When should you leave it off? If your offers are designed as mutually exclusive (a 50% save offer is meant to replace, not add to, a 20% loyalty coupon), keep stacking disabled. The default behavior matches what most teams expect.",{"id":667,"title":668,"titles":669,"content":670,"level":374},"\u002Fcancel-flows\u002Fcancel-flow-settings#record-session","Record session",[28],"Churnkey can record Cancel Flow sessions for session playback so you can watch exactly how customers moved through your flow, which answers they picked, and where they hesitated. The Record Session toggle controls whether new sessions are recorded. Recording is on by default. Turning it off stops all new recordings across every flow until you switch it back on. If a session is mid-recording when you flip the toggle to off, the recording stops immediately in runtime — the portion already captured is kept, but nothing new is written. Supported providers: Stripe, Chargebee, Paddle Billing, Braintree, Paddle Classic, and Maxio.",{"id":672,"title":673,"titles":674,"content":675,"level":417},"\u002Fcancel-flows\u002Fcancel-flow-settings#precedence-with-the-record-embed-parameter","Precedence with the record embed parameter",[28,668],"Unlike the previous two settings, Record Session has a JavaScript equivalent: the record option on window.churnkey.init('show', {...}). When both are present, the embed parameter always wins. Init record valueDashboard toggleResulttrueOnSession recordedtrueOffSession recorded (init overrides dashboard)falseOnNot recorded (init overrides dashboard)falseOffNot recorded(omitted)OnSession recorded(omitted)OffNot recorded The practical consequence: if you want to control recording centrally from the dashboard going forward, remove the record key from your embed code. Leaving it in — even set to the value you think you want — locks that behavior into your deploys. \u002F\u002F Dashboard-controlled: dashboard toggle is the source of truth\nwindow.churnkey.init('show', {\n  customerId: 'CUSTOMER_ID',\n  authHash: 'HMAC_HASH',\n  appId: 'YOUR_APP_ID',\n  \u002F\u002F record key intentionally omitted\n});\n\n\u002F\u002F Code-controlled: always records, dashboard toggle has no effect\nwindow.churnkey.init('show', {\n  customerId: 'CUSTOMER_ID',\n  authHash: 'HMAC_HASH',\n  appId: 'YOUR_APP_ID',\n  record: true,\n}); If you are turning recording off for compliance reasons (for example, a specific customer segment opts out), keep record: false in your embed for that segment rather than relying on the dashboard — code-level enforcement is unambiguous and auditable.",{"id":677,"title":678,"titles":679,"content":680,"level":374},"\u002Fcancel-flows\u002Fcancel-flow-settings#when-do-i-still-need-to-redeploy","When do I still need to redeploy?",[28],"The three toggles on this page were chosen specifically so they would not require a deploy. For a quick reference on what can now be changed from the dashboard versus what still lives in code: BehaviorDashboardEmbed codePause invoice behavior (Stripe)YesNoStack coupons on discountYesNoRecord session (default)YesYes (record) — init wins when both setCustomer ID \u002F subscription IDNoYesLive \u002F test \u002F sandbox modeNoYes (mode)Dynamic offers overrideNoYes (dynamicOffers)Handler callbacks (handleCancel, etc.)NoYesCustom attributes for segmentationNoYes (customerAttributes) For everything in the \"Embed code\" column, see Configuration Options.",{"id":682,"title":683,"titles":684,"content":685,"level":374},"\u002Fcancel-flows\u002Fcancel-flow-settings#related","Related",[28],"Quick Start Guide — full embed initialization referenceConfiguration Options — all JavaScript parameters, including recordPause Action — how Churnkey executes pauses behind the scenes html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":33,"title":32,"titles":687,"content":688,"level":368},[],"Learn about the available components that make up a Cancel Flow, from initial offers to cancellation confirmation, and how each step contributes to the overall flow. A Cancel Flow is the sequence of steps that customers go through when they attempt to cancel their subscription. Each step serves a specific purpose, from gathering feedback to presenting retention options. This document explains what you can include in your Cancel Flow and how each component works.",{"id":690,"title":691,"titles":692,"content":693,"level":374},"\u002Fcancel-flows\u002Fflow-configuration#flow-structure","Flow Structure",[32],"Every Cancel Flow can be composed of the following 5 steps: Initial OfferCancellation SurveyFreeform FeedbackFinal OfferCancellation Confirmation All steps are optional individually, but to have a functioning Cancel Flow, it's mandatory to have at least one step.",{"id":695,"title":696,"titles":697,"content":698,"level":417},"\u002Fcancel-flows\u002Fflow-configuration#initial-offer","Initial Offer",[32,691],"The Initial Offer is the customer's first contact with your Cancel Flow. This step focuses on interrupting the customer's intent to cancel by presenting alternative arrangements. It captures their attention and improves the performance of subsequent steps in gathering valuable feedback. You can add a variety of offers within it: Apply a CouponPause SubscriptionTrial ExtensionSwitch Subscription PlanContact Us (Requires Further integration)Send to a Custom Page Initial Offer interface with multiple retention options available",{"id":700,"title":701,"titles":702,"content":703,"level":417},"\u002Fcancel-flows\u002Fflow-configuration#cancellation-survey","Cancellation Survey",[32,691],"The Cancellation Survey is a powerful Cancel Flow step that helps you understand why customers are leaving while giving you the opportunity to make targeted offers based on their specific reasons. It presents customers with a multiple-choice question where they can select a single answer, and each answer can be paired with a specific offer tailored to address that particular concern. For example, if a customer selects \"Budget\" as their reason, you can automatically offer them a downgrade to a cheaper plan or a discount on their next renewal. Cancellation Survey with customizable multiple choice options While there's no technical limit to the number of answers you can add, we recommend keeping it to a maximum of 5 options. Too many choices can overwhelm customers and lead to less meaningful responses. To gather more detailed feedback, you can enable a follow-up question for any answer. When enabled, a text box appears allowing customers to provide additional context. You can make the response mandatory and set a minimum character requirement, helping you collect more specific insights about each cancellation reason. Configuration toggle for mandatory survey responses When creating your survey, remember to use the randomization feature to remove any bias from answer placement. Keep the language clear and concise, and make sure each answer option is distinct and actionable. Most importantly, consider the specific offers you'll pair with each answer before creating the survey.",{"id":705,"title":706,"titles":707,"content":708,"level":417},"\u002Fcancel-flows\u002Fflow-configuration#freeform-feedback","Freeform Feedback",[32,691],"The Freeform Feedback step provides customers with an open-ended opportunity to share their cancellation reasons in their own words. This unstructured approach captures authentic feedback about their experience with your product or service, offering valuable insights that might not surface through predefined options. You can configure this step to be either optional or mandatory, with the ability to set a minimum character requirement before proceeding to the next step. Since this step is focused on gathering qualitative feedback, it cannot be paired with specific offers based on the customer's response. Freeform Feedback interface allowing detailed customer input",{"id":710,"title":711,"titles":712,"content":713,"level":417},"\u002Fcancel-flows\u002Fflow-configuration#final-offer","Final Offer",[32,691],"The Final Offer is the last step in your Cancel Flow where you can present retention offers to your customers. Like the Initial Offer, this step supports various offer types including discounts, plan changes, and other retention strategies. Final Offer interface showing last opportunity for retention offers Since this is your final opportunity to make an offer, it's crucial to present something truly unique and compelling. If you're not offering something different from previous steps, this step might not add value to your retention strategy. Make sure your Final Offer stands out and directly addresses the customer's specific needs.",{"id":715,"title":716,"titles":717,"content":718,"level":417},"\u002Fcancel-flows\u002Fflow-configuration#cancellation-confirmation","Cancellation Confirmation",[32,691],"The Cancellation Confirmation is the final step in your Cancel Flow, serving as the last checkpoint before the subscription is terminated. In this step, you can present a comprehensive overview of what the customer will lose by cancelling their subscription, such as access to features, benefits, and any accumulated data or progress. This transparency helps customers make an informed decision and may serve as a final retention opportunity by highlighting the value they're about to lose. Cancellation Confirmation showing subscription termination details",{"id":720,"title":269,"titles":721,"content":422,"level":374},"\u002Fcancel-flows\u002Fflow-configuration#frequently-asked-questions",[32],{"id":723,"title":724,"titles":725,"content":726,"level":417},"\u002Fcancel-flows\u002Fflow-configuration#can-i-disable-churnkeys-watermark-in-the-cancellation-confirmation","Can I disable Churnkey's watermark in the Cancellation Confirmation?",[32,269],"The watermark is mandatory only for Starter Plans.",{"id":728,"title":729,"titles":730,"content":731,"level":417},"\u002Fcancel-flows\u002Fflow-configuration#is-it-possible-to-embed-a-custom-step-into-the-cancel-flow","Is it possible to embed a custom step into the Cancel Flow?",[32,269],"No, you can only rely on our native steps and edit them using the Cancel Flow Builder",{"id":37,"title":36,"titles":733,"content":734,"level":368},[],"Additional details for all of the following options are included below. window.churnkey.init('show', {\n  customerId: 'billing_provider_cus_id',\n  subscriptionId: 'billing_provider_sub_id',\n  billingProvider: 'stripe', \u002F\u002F or chargebee, braintree, paddle, paddling-billing\n  mode: 'live', \u002F\u002F or 'test',\n  record: true, \u002F\u002F session playback recording — also controllable from the dashboard (defaults to on)\n  preview: false, \u002F\u002F disables any billing actions if true\n  report: true, \u002F\u002F enable\u002Fdisable including this sessions results in analytics dashboard\n  bypassDiscountAppliedScreen: false,\n  bypassPauseAppliedScreen: false,\n  customerAttributes: {\n    favoriteAnimal: 'penguin', \u002F\u002F details below\n  },\n  i18n: {\n   lang: 'en',\n   messages: {\n     en: {\n       declineOffer: 'No thanks',\n       ...\n      },\n    }\n  },\n\n  \u002F\u002F dynamic offers\n  dynamicOffers: {\n    discount: {\n      couponId: 'override-coupon-id',\n    },\n    planChange: {\n      planIds: ['price_abc', 'price_xyz'],\n    }\n  },\n\n  \u002F\u002F handler callbacks\n  handlePause: \u003CPromise(customer, { pauseDuration: number})>,\n  handleCancel: \u003CPromise(customer, surveyResponse, freeformFeedback)>,\n  handleDiscount: \u003CPromise(customer, coupon)>,\n  handleTrialExtension: \u003CPromise(customer, { trialExtensionDays })>,\n  handlePlanChange: \u003CPromise(customer, { plan })>,\n  handleSupportRequest: \u003Cfunction(customer)>,\n  handleRedirect: \u003Cfunction(customer, { redirectLabel, redirectUrl })>,\n\n  \u002F\u002F listener callbacks\n  onPause: \u003Cfunction(customer, { pauseDuration }>,\n  onCancel: \u003Cfunction(customer, surveyResponse)>,\n  onDiscount: \u003Cfunction(customer, coupon)>,\n  onTrialExtension: \u003Cfunction(customer, { trialExtensionDays })>,\n  onPlanChange: \u003Cfunction(customer, { planId })>,\n  onGoToAccount: \u003Cfunction(sessionResults)>,\n  onClose: \u003Cfunction(sessionResults)>,\n  onStepChange: \u003Cfunction(newStep, oldStep)>\n\n})",{"id":736,"title":737,"titles":738,"content":739,"level":374},"\u002Fcancel-flows\u002Ffurther-configuration#enabledisable-session-recording","Enable\u002FDisable Session Recording",[36],"By default, session recording is enabled. It can be toggled from the dashboard in Cancel Flow → Settings — no code change or redeploy needed for most teams. You can still set the record option in the embed when you need to override the dashboard for a specific segment (for example, turning recording off for users who have opted out). When both are set, the embed parameter wins. window.churnkey.init('show', {\n  ...,\n  record: false \u002F\u002F only needed when overriding the dashboard\n})",{"id":741,"title":742,"titles":743,"content":744,"level":374},"\u002Fcancel-flows\u002Ffurther-configuration#live-mode-vs-test-mode-vs-sandbox-mode","Live Mode vs Test Mode vs Sandbox Mode",[36],"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. For Stripe users, a third option is available: sandbox mode connects to a completely separate Stripe sandbox account, providing full data isolation from both live and test environments. See the Stripe Sandbox Mode documentation for details. window.churnkey.init('show', {\n  ...,\n  mode: 'live' \u002F\u002F 'live', 'test', or 'sandbox' (Stripe only)\n})",{"id":746,"title":747,"titles":748,"content":749,"level":374},"\u002Fcancel-flows\u002Ffurther-configuration#customer-id-vs-subscription-id","Customer ID vs Subscription ID",[36],"Note for Paddle UsersPass in just the 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.",{"id":751,"title":752,"titles":753,"content":754,"level":417},"\u002Fcancel-flows\u002Ffurther-configuration#billing-actions-using-just-customer-id","Billing Actions using just Customer ID",[36,747],"Below are the default actions that Churnkey will take on your behalf if you pass only a customer ID as a configuration option. Stripe\n   \n    CancelIf not using subscription schedules:For active and trialing subscriptions, they are set to cancel at the end of the term via stripe.subscriptions.update(subscriptionId, { cancel_at_period_end: true }) . If you have set subscriptions to cancel immediately in your Cancel Flow settings, subscriptions will cancel immediately via stripe.subscriptions.del .Inactive customer subscriptions are always canceled immediately via stripe.subscriptions.del.If using subscription schedulesFor active and trialing subscriptions, the subscription schedule is first released via stripe.subscriptionSchedules.release , and then is set to cancel at the end of the term via stripe.subscriptions.update(subscriptionId, { cancel_at_period_end: true }) . If you have set subscriptions to cancel immediately in your Cancel Flow settings, subscriptions will be released from their schedule and then canceled immediately via stripe.subscriptions.del .Inactive customer subscriptions are canceled immediately via stripe.subscriptionSchedules.cancel\n\n\n   \n    DiscountThe coupon is applied directly to the Stripe customer via stripe.customers.update\n  \n   \n    PauseStripe 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.\n \n   \n    Plan changeIf you are using plan changes, please make sure to pass a subscription ID in addition to a customer ID. Braintree\n   \n    CancelEvery customer subscription is set to cancel at the end of the subscription’s billing month\n  \n   \n    DiscountThe discount is applied to all customer subscriptions.\n  \n   \n    PauseA 100% discount is applied to all customer subscriptions for the selected duration. Chargebee Chargebee Paddle Paddle Classic",{"id":756,"title":757,"titles":758,"content":759,"level":417},"\u002Fcancel-flows\u002Ffurther-configuration#billing-actions-using-customer-id-subscription-id","Billing Actions using Customer ID + Subscription ID",[36,747],"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. Stripe\n  \n    CancelIf not using subscription schedules:For active and trialing subscriptions, they are set to cancel at the end of the term via stripe.subscriptions.update(subscriptionId, { cancel_at_period_end: true }). If you have set subscriptions to cancel immediately in your Cancel Flow settings, subscriptions will cancel immediately via stripe.subscriptions.del.Inactive customer subscriptions are always canceled immediately via stripe.subscriptions.del.If using subscription schedules:For active and trialing subscriptions, the subscription schedule is first released via stripe.subscriptionSchedules.release, and then is set to cancel at the end of the term via stripe.subscriptions.update(subscriptionId, { cancel_at_period_end: true }). If you have set subscriptions to cancel immediately in your Cancel Flow settings, subscriptions will be released from their schedule and then canceled immediately via stripe.subscriptions.del.Inactive customer subscriptions are canceled immediately via stripe.subscriptionSchedules.cancel\n  DiscountThe coupon is applied to the specified subscription via stripe.subscriptions.update\n  \n  \n    PauseThe 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.\n  \n  \n    Plan ChangeThe specific subscription is updated to the new plan via stripe.subscriptions.update. You can optionally set to not prorate changes. Braintree\n  \n    CancelThe specified subscription is set to cancel at the end of the billing month.\n  \n  \n    DiscountThe discount is applied to the specified subscription.\n  \n  \n    PauseA 100% discount is applied to the subscription for the selected duration. Chargebee Chargebee Paddle Paddle Classic",{"id":761,"title":762,"titles":763,"content":764,"level":374},"\u002Fcancel-flows\u002Ffurther-configuration#custom-attributes","Custom Attributes",[36],"When initializing the Cancel Flow, you can pass in customerAttributes which can be used for customer segmentation. Define a new custom attribute in the \"Advanced Settings\" tab of the Cancel Flows Builder pageExample: videosCreated, which will represent the total number of videos a customer has created on our example platformUse this custom attribute to create a new audience of a segmented Cancel FlowExample: 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 discountsPass in videosCreated under customerAttributes when calling window.churnkey.init()window.churnkey.init('show', {\n  customerId: 'CUSTOMER_ID', \u002F\u002F required\n  authHash: 'HMAC_HASH', \u002F\u002F required\n  appId: 'YOUR_APP_ID', \u002F\u002F required\n  customerAttributes: {\n    videosCreated: 28, \u002F\u002F data about the customer you provide\n  },\n});",{"id":766,"title":767,"titles":768,"content":769,"level":374},"\u002Fcancel-flows\u002Ffurther-configuration#customer-segmentation-by-metadata","Customer Segmentation by Metadata",[36],"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.\nExample: 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.\nExample: 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 discountsAll 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.",{"id":771,"title":772,"titles":773,"content":774,"level":374},"\u002Fcancel-flows\u002Ffurther-configuration#bypass-offer-accepted-screen","Bypass Offer Accepted Screen",[36],"If a previously discount or pause offer is still in effect, we will message this to the customer. This pattern is designed to remind the customer of the offer and maximize retention. However, you can disable this check using our bypassDiscountAppliedScreen and bypassPauseAppliedScreen configuration parameters. window.churnkey.init('show', {\n  ...,\n  bypassDiscountAppliedScreen: false,\n  bypassPauseAppliedScreen: false,\n})",{"id":776,"title":777,"titles":778,"content":779,"level":368},"\u002Fcancel-flows\u002Ffurther-configuration#dynamic-offers","Dynamic Offers",[],"Advanced UsageDynamically override the offers shown to your customers You can optionally pass plan IDs and\u002For a coupon ID which will override the default offer(s) for you flow. window.churnkey.init('show', {\n  ...,\n  dynamicOffers: {\n    discount: {\n      couponId: 'override-coupon-id',\n    },\n    planChange: {\n      planIds: ['price_abc', 'price_xyz'],\n    }\n  },\n}) Note that these overrides, if present, will take the place of all existing discount and plan change offers in your Cancel Flow, respectively. We advise you to still include default discount and plan change settings in your Cancel Flow.",{"id":781,"title":782,"titles":783,"content":784,"level":374},"\u002Fcancel-flows\u002Ffurther-configuration#custom-callbacks","Custom Callbacks",[777],"Churnkey provides two types of callbacks for hooking into the Cancel Flow: handler callbacks and listener callbacks.",{"id":786,"title":787,"titles":788,"content":789,"level":417},"\u002Fcancel-flows\u002Ffurther-configuration#handler-callbacks","Handler Callbacks",[777,782],"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. {\n    \u002F\u002F AVAILABLE NOW\n    handlePause: \u003CPromise>,\n    handleCancel: \u003CPromise>,\n    handleDiscount: \u003CPromise>,\n    handleTrialExtension: \u003CPromise>,\n    handleRedirect: \u003CPromise\u003C(customer, redirectConfig)>,\n    handleSupportRequest: \u003Cfunction>\n    handlePlanChange: \u003CPromise\u003C(customer, { plan })>,\n}",{"id":791,"title":792,"titles":793,"content":794,"level":417},"\u002Fcancel-flows\u002Ffurther-configuration#listener-callbacks","Listener Callbacks",[777,782],"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. {\n    \u002F\u002F AVAILABLE NOW\n    onPause: \u003Cfunction(customer, { pauseDuration }>,\n    onCancel: \u003Cfunction(customer, surveyResponse)>,\n    onDiscount: \u003Cfunction(customer, coupon)>,\n    onTrialExtension: \u003Cfunction(customer, { trialExtensionDays })>,\n    onPlanChange: \u003Cfunction(customer, { planId })>,\n}",{"id":796,"title":797,"titles":798,"content":799,"level":417},"\u002Fcancel-flows\u002Ffurther-configuration#example-callbacks-for-custom-pause-and-cancellation-handling","Example callbacks for custom pause and cancellation handling",[777,782],"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', {\n  appId: 'YOUR_APP_ID',\n  customerId: 'STRIPE_CUSTOMER_ID',\n  authHash: 'HMAC_HASH,\n  handlePause: (customer, { pauseDuration }) => {\n    return new Promise(async (resolve, reject) => {\n      try {\n        \u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\n        \u002F\u002F YOUR CUSTOM PAUSING FUNCTION GOES HERE\n        \u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\n\n        \u002F\u002F Optionally, display a custom message by passing a `message` when resolving\n        \u002F\u002F if you don't pass a message, a generic pause confirmation message will be shown\n        resolve({ message: `Account has been paused for ${pauseDuration} month${duration === 1 ? '' : 's'}` });\n      } catch (e) {\n        console.log(e);\n        reject({ message: 'Failed to pause account. Please reach out to us through our live chat.' });\n      }\n    });\n  },\n  handleCancel: (customer, surveyAnswer) => {\n    \u002F\u002F customer is the Stripe customer object (string)\n    \u002F\u002F surveyAnswer is the reason they're leaving (string)\n    return new Promise(async (resolve, reject) => {\n      try {\n        \u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\n        \u002F\u002F CANCEL THE CUSTOMERS SUBSCRIPTION HERE\n        \u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\n\n        \u002F\u002F Optionally, display a custom message by passing a `message` when resolving\n        \u002F\u002F if you don't pass a message, the generic message below will be displayed\n        resolve({ message: 'Your account has been canceled. You will not be billed again' });\n      } catch (e) {\n        console.log(e);\n        reject({ message: 'Failed to cancel account. Please reach out to us through our live chat.' });\n      }\n    });\n  },\n})",{"id":801,"title":802,"titles":803,"content":804,"level":417},"\u002Fcancel-flows\u002Ffurther-configuration#example-support-request-callback-connect-with-intercom-crisp-etc","Example support request callback (connect with Intercom, Crisp, etc.)",[777,782],"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', {\n  appId: 'YOUR_APP_ID',\n  customerId: 'STRIPE_CUSTOMER_ID',\n  authHash: 'HMAC_HASH,\n  handleSupportRequest: customer => {\n    console.log(customer);\n    window.Intercom('showNewMessage', 'Attention: Offboarding Customer Needs Help ASAP.\\n');\n    window.churnkey.hide();\n  },\n})",{"id":806,"title":807,"titles":808,"content":809,"level":417},"\u002Fcancel-flows\u002Ffurther-configuration#example-step-change-listener","Example Step Change Listener",[777,782],"If you would like to use the onStepChange(newStep, oldStep) listener, each step has the following structure {\n    \"stepType\": \"OFFER\",\n    \"header\": \"Need Some Time Off? No Problem!\",\n    \"description\": \"\u003Cp>We totally get it. Everyone needs a break sometimes. Did you know you can pause your subscription for up to 3 months, free of charge? When you're ready, your favorite shows and movies will be right here waiting for you.\u003C\u002Fp>\",\n    \"offer\": {\n        \"guid\": \"90b5af40-be1d-4c2c-aa93-c3d3007234da\",\n        \"offerType\": \"PLAN_CHANGE\", \u002F\u002F or PAUSE or DISCOUNT or TRIAL_EXTENSION\n    },\n}",{"id":811,"title":812,"titles":813,"content":814,"level":374},"\u002Fcancel-flows\u002Ffurther-configuration#utility-functions","Utility Functions",[777],"Churnkey stores temporary flow state to keep track of survey responses and if a customer has already completed a flow to show messages like \"Your discount has been applied\", if the flow is triggered again. Depending on your application, you may want to clear this state manually, or each time the flow is retriggered. \u002F\u002F used to reset internal state, useful for calling when a customer logs out\nwindow.churnkey.clearState()\n\n\u002F\u002F used in place of window.churnkey.init('show',...)\n\u002F\u002F first clears internal state and then calls window.churnkey.init('show',...\nwindow.churnkey.init('restart', ... html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":41,"title":40,"titles":816,"content":817,"level":368},[],"Brand your Churnkey Cancel Flow with your own CSS",{"id":819,"title":820,"titles":821,"content":822,"level":374},"\u002Fcancel-flows\u002Fcustom-styling#css-classes-available","CSS Classes Available",[40],"## General Components\n.ck-style ## applies to everything\n.ck-modal ## the embed pop-up\n.ck-background-overlay ## the partially transparent overlay\n.ck-step ## a wrapper around all content\n\n## specific steps in flow\n.ck-pause-step\n.ck-discount-step\n.ck-contact-step\n.ck-redirect-step\n.ck-survey-step\n.ck-freeform-step\n.ck-confirm-step\n\n## while a customer subscription is being modified\n.ck-progress-step\n\n## once flow is complete - after discount, pause, cancel\n.ck-complete-step\n\n## if error is shown at any point (hopefully customers will never see this)\n.ck-error-step\n\n## Step components\n.ck-step-header\n.ck-step-header-text\n.ck-brand-image\n.ck-brand-image-header\n.ck-step-description-text\n.ck-step-body\n.ck-step-footer\n\n## Button variations\n.ck-button\n.ck-text-button ## custom color\n.ck-black-text-button\n.ck-primary-button ## custom color\n.ck-black-primary-button\n.ck-gray-primary-button",{"id":824,"title":825,"titles":826,"content":827,"level":374},"\u002Fcancel-flows\u002Fcustom-styling#example-css-override","Example CSS Override",[40],"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 {\n  color: black;\n  background: yellow;\n  font-style: italic;\n} Here's how that looks in Chrome dev tools.",{"id":829,"title":830,"titles":831,"content":832,"level":374},"\u002Fcancel-flows\u002Fcustom-styling#ready-made-css-templates","Ready-Made CSS Templates",[40],"Copy and paste these templates to quickly style your Cancel Flow. Each template covers the modal, form elements (dropdowns, inputs, textareas), buttons, text colors, and info boxes. These templates override visual styling only. Your brand colors configured in the Churnkey dashboard (e.g., the CTA button color) will still apply on top of these styles.",{"id":834,"title":835,"titles":836,"content":837,"level":417},"\u002Fcancel-flows\u002Fcustom-styling#dark-obsidian","Dark Obsidian",[40,830],"Inspired by leading developer deployment platforms — pure neutral dark. \u002F* ===========================================\n   Dark Obsidian — Pure Neutral Dark\n   =========================================== *\u002F\n\n\u002F* Overlay *\u002F\n#ck-app .ck-background-overlay {\n  background: rgba(0, 0, 0, 0.7) !important;\n}\n\n\u002F* Modal container *\u002F\n#ck-app .ck-modal {\n  background: #141414 !important;\n  border: 1px solid #262626 !important;\n  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5) !important;\n  border-radius: 16px !important;\n}\n\n\u002F* All step backgrounds *\u002F\n#ck-app .ck-step,\n#ck-app .ck-survey-step, #ck-app .ck-confirm-step, #ck-app .ck-freeform-step,\n#ck-app .ck-pause-step, #ck-app .ck-discount-step, #ck-app .ck-contact-step,\n#ck-app .ck-redirect-step, #ck-app .ck-complete-step, #ck-app .ck-progress-step {\n  background: #141414 !important;\n}\n\n\u002F* Header, body, footer *\u002F\n#ck-app .ck-step-header { background: #141414 !important; border-bottom: 1px solid #1f1f1f !important; }\n#ck-app .ck-step-header-text { color: #f5f5f5 !important; }\n#ck-app .ck-step-description-text { color: #a3a3a3 !important; }\n#ck-app .ck-step-body { background: #141414 !important; color: #a3a3a3 !important; }\n#ck-app .ck-step-footer { background: #141414 !important; border-top: 1px solid #1f1f1f !important; }\n\n\u002F* Form elements (dropdowns, inputs, textareas) *\u002F\n#ck-app select,\n#ck-app input,\n#ck-app textarea {\n  background-color: #1c1c1c !important;\n  color: #e5e5e5 !important;\n  border-color: #333333 !important;\n}\n#ck-app select:focus,\n#ck-app input:focus,\n#ck-app textarea:focus {\n  border-color: #525252 !important;\n  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1) !important;\n  outline: none !important;\n}\n#ck-app ::placeholder { color: #525252 !important; }\n#ck-app option { background-color: #1c1c1c !important; color: #e5e5e5 !important; }\n\n\u002F* Text color overrides for dark background legibility *\u002F\n#ck-app .text-gray-900, #ck-app .text-gray-800 { color: #e5e5e5 !important; }\n#ck-app .text-gray-700, #ck-app .text-gray-600, #ck-app .text-gray-500 { color: #a3a3a3 !important; }\n#ck-app .text-brand-black { color: #f5f5f5 !important; }\n#ck-app .text-opacity-60, #ck-app .text-opacity-80, #ck-app .text-opacity-90 {\n  --tw-text-opacity: 1 !important;\n}\n\n\u002F* Info boxes and offer containers *\u002F\n#ck-app [class*=\"bg-client-primary-light\"] { background-color: #1c1c1c !important; }\n#ck-app .bg-opacity-5 { --tw-bg-opacity: 1 !important; }\n#ck-app .ck-pause-subscription-details { border-color: #333333 !important; color: #a3a3a3 !important; }\n#ck-app .ck-pause-subscription-details b { color: #e5e5e5 !important; }\n#ck-app .active-discount-disclaimer { color: #a3a3a3 !important; background-color: #1c1c1c !important; }\n#ck-app .ck-step-body li, #ck-app .ck-step-body label { color: #a3a3a3 !important; }\n\n\u002F* Brand color overrides (header bar, CTA, icons, borders) *\u002F\n#ck-app .h-14.rounded-t-lg { background-color: #1c1c1c !important; }\nhtml body #ck-app .bg-client-primary { background-color: #525252 !important; }\nhtml body #ck-app .text-client-primary { color: #a3a3a3 !important; }\nhtml body #ck-app .text-client-primary-middle { color: #a3a3a3 !important; opacity: 0.4 !important; }\nhtml body #ck-app .text-client-primary-light { color: #a3a3a3 !important; }\nhtml body #ck-app .border-client-primary { border-color: #525252 !important; }\nhtml body #ck-app .border-client-primary-light { border-color: #262626 !important; }\n\n\u002F* Buttons *\u002F\n#ck-app .ck-primary-button { background: #525252 !important; color: #ffffff !important; border: none !important; border-radius: 8px !important; font-weight: 600 !important; }\n#ck-app .ck-black-primary-button { background: #262626 !important; color: #f5f5f5 !important; border: 1px solid #333333 !important; border-radius: 8px !important; }\n#ck-app .ck-gray-primary-button { background: #1c1c1c !important; color: #d4d4d4 !important; border: 1px solid #262626 !important; border-radius: 8px !important; }\n#ck-app .ck-text-button { color: #a3a3a3 !important; }",{"id":839,"title":840,"titles":841,"content":842,"level":417},"\u002Fcancel-flows\u002Fcustom-styling#dark-midnight","Dark Midnight",[40,830],"Inspired by a global payments infrastructure — cool blue-tinted dark. \u002F* ===========================================\n   Dark Midnight — Blue-Tinted Dark\n   =========================================== *\u002F\n\n\u002F* Overlay *\u002F\n#ck-app .ck-background-overlay {\n  background: rgba(2, 6, 23, 0.75) !important;\n}\n\n\u002F* Modal container *\u002F\n#ck-app .ck-modal {\n  background: #0f172a !important;\n  border: 1px solid #1e293b !important;\n  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5) !important;\n  border-radius: 16px !important;\n}\n\n\u002F* All step backgrounds *\u002F\n#ck-app .ck-step,\n#ck-app .ck-survey-step, #ck-app .ck-confirm-step, #ck-app .ck-freeform-step,\n#ck-app .ck-pause-step, #ck-app .ck-discount-step, #ck-app .ck-contact-step,\n#ck-app .ck-redirect-step, #ck-app .ck-complete-step, #ck-app .ck-progress-step {\n  background: #0f172a !important;\n}\n\n\u002F* Header, body, footer *\u002F\n#ck-app .ck-step-header { background: #0f172a !important; border-bottom: 1px solid #1a2437 !important; }\n#ck-app .ck-step-header-text { color: #f1f5f9 !important; }\n#ck-app .ck-step-description-text { color: #94a3b8 !important; }\n#ck-app .ck-step-body { background: #0f172a !important; color: #94a3b8 !important; }\n#ck-app .ck-step-footer { background: #0f172a !important; border-top: 1px solid #1a2437 !important; }\n\n\u002F* Form elements *\u002F\n#ck-app select,\n#ck-app input,\n#ck-app textarea {\n  background-color: #1e293b !important;\n  color: #f1f5f9 !important;\n  border-color: #334155 !important;\n}\n#ck-app select:focus,\n#ck-app input:focus,\n#ck-app textarea:focus {\n  border-color: #3b82f6 !important;\n  box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3) !important;\n  outline: none !important;\n}\n#ck-app ::placeholder { color: #475569 !important; }\n#ck-app option { background-color: #1e293b !important; color: #f1f5f9 !important; }\n\n\u002F* Text color overrides *\u002F\n#ck-app .text-gray-900, #ck-app .text-gray-800 { color: #f1f5f9 !important; }\n#ck-app .text-gray-700, #ck-app .text-gray-600, #ck-app .text-gray-500 { color: #94a3b8 !important; }\n#ck-app .text-brand-black { color: #f1f5f9 !important; }\n#ck-app .text-opacity-60, #ck-app .text-opacity-80, #ck-app .text-opacity-90 {\n  --tw-text-opacity: 1 !important;\n}\n\n\u002F* Info boxes and offer containers *\u002F\n#ck-app [class*=\"bg-client-primary-light\"] { background-color: #1e293b !important; }\n#ck-app .bg-opacity-5 { --tw-bg-opacity: 1 !important; }\n#ck-app .ck-pause-subscription-details { border-color: #1e293b !important; color: #94a3b8 !important; }\n#ck-app .ck-pause-subscription-details b { color: #f1f5f9 !important; }\n#ck-app .active-discount-disclaimer { color: #94a3b8 !important; background-color: #1e293b !important; }\n#ck-app .ck-step-body li, #ck-app .ck-step-body label { color: #94a3b8 !important; }\n\n\u002F* Brand color overrides *\u002F\n#ck-app .h-14.rounded-t-lg { background-color: #1e293b !important; }\nhtml body #ck-app .bg-client-primary { background-color: #3b82f6 !important; }\nhtml body #ck-app .text-client-primary { color: #3b82f6 !important; }\nhtml body #ck-app .text-client-primary-middle { color: #3b82f6 !important; opacity: 0.4 !important; }\nhtml body #ck-app .text-client-primary-light { color: #94a3b8 !important; }\nhtml body #ck-app .border-client-primary { border-color: #3b82f6 !important; }\nhtml body #ck-app .border-client-primary-light { border-color: #1e293b !important; }\n\n\u002F* Buttons *\u002F\n#ck-app .ck-primary-button { background: #3b82f6 !important; color: #ffffff !important; border: none !important; border-radius: 8px !important; font-weight: 600 !important; }\n#ck-app .ck-black-primary-button { background: #1e293b !important; color: #cbd5e1 !important; border: 1px solid #334155 !important; border-radius: 8px !important; }\n#ck-app .ck-gray-primary-button { background: #1e293b !important; color: #94a3b8 !important; border: 1px solid #1e293b !important; border-radius: 8px !important; }\n#ck-app .ck-text-button { color: #94a3b8 !important; }",{"id":844,"title":845,"titles":846,"content":847,"level":417},"\u002Fcancel-flows\u002Fcustom-styling#dark-ember","Dark Ember",[40,830],"Inspired by popular team knowledge bases — warm dark with amber undertones. \u002F* ===========================================\n   Dark Ember — Warm Dark\n   =========================================== *\u002F\n\n\u002F* Overlay *\u002F\n#ck-app .ck-background-overlay {\n  background: rgba(12, 10, 9, 0.7) !important;\n}\n\n\u002F* Modal container *\u002F\n#ck-app .ck-modal {\n  background: #1c1917 !important;\n  border: 1px solid #292524 !important;\n  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5) !important;\n  border-radius: 16px !important;\n}\n\n\u002F* All step backgrounds *\u002F\n#ck-app .ck-step,\n#ck-app .ck-survey-step, #ck-app .ck-confirm-step, #ck-app .ck-freeform-step,\n#ck-app .ck-pause-step, #ck-app .ck-discount-step, #ck-app .ck-contact-step,\n#ck-app .ck-redirect-step, #ck-app .ck-complete-step, #ck-app .ck-progress-step {\n  background: #1c1917 !important;\n}\n\n\u002F* Header, body, footer *\u002F\n#ck-app .ck-step-header { background: #1c1917 !important; border-bottom: 1px solid #231f1d !important; }\n#ck-app .ck-step-header-text { color: #fafaf9 !important; }\n#ck-app .ck-step-description-text { color: #a8a29e !important; }\n#ck-app .ck-step-body { background: #1c1917 !important; color: #a8a29e !important; }\n#ck-app .ck-step-footer { background: #1c1917 !important; border-top: 1px solid #231f1d !important; }\n\n\u002F* Form elements *\u002F\n#ck-app select,\n#ck-app input,\n#ck-app textarea {\n  background-color: #292524 !important;\n  color: #fafaf9 !important;\n  border-color: #44403c !important;\n}\n#ck-app select:focus,\n#ck-app input:focus,\n#ck-app textarea:focus {\n  border-color: #f97316 !important;\n  box-shadow: 0 0 0 2px rgba(249, 115, 22, 0.3) !important;\n  outline: none !important;\n}\n#ck-app ::placeholder { color: #57534e !important; }\n#ck-app option { background-color: #292524 !important; color: #fafaf9 !important; }\n\n\u002F* Text color overrides *\u002F\n#ck-app .text-gray-900, #ck-app .text-gray-800 { color: #fafaf9 !important; }\n#ck-app .text-gray-700, #ck-app .text-gray-600, #ck-app .text-gray-500 { color: #a8a29e !important; }\n#ck-app .text-brand-black { color: #fafaf9 !important; }\n#ck-app .text-opacity-60, #ck-app .text-opacity-80, #ck-app .text-opacity-90 {\n  --tw-text-opacity: 1 !important;\n}\n\n\u002F* Info boxes and offer containers *\u002F\n#ck-app [class*=\"bg-client-primary-light\"] { background-color: #292524 !important; }\n#ck-app .bg-opacity-5 { --tw-bg-opacity: 1 !important; }\n#ck-app .ck-pause-subscription-details { border-color: #292524 !important; color: #a8a29e !important; }\n#ck-app .ck-pause-subscription-details b { color: #fafaf9 !important; }\n#ck-app .active-discount-disclaimer { color: #a8a29e !important; background-color: #292524 !important; }\n#ck-app .ck-step-body li, #ck-app .ck-step-body label { color: #a8a29e !important; }\n\n\u002F* Brand color overrides *\u002F\n#ck-app .h-14.rounded-t-lg { background-color: #292524 !important; }\nhtml body #ck-app .bg-client-primary { background-color: #f97316 !important; }\nhtml body #ck-app .text-client-primary { color: #f97316 !important; }\nhtml body #ck-app .text-client-primary-middle { color: #f97316 !important; opacity: 0.4 !important; }\nhtml body #ck-app .text-client-primary-light { color: #a8a29e !important; }\nhtml body #ck-app .border-client-primary { border-color: #f97316 !important; }\nhtml body #ck-app .border-client-primary-light { border-color: #292524 !important; }\n\n\u002F* Buttons *\u002F\n#ck-app .ck-primary-button { background: #f97316 !important; color: #ffffff !important; border: none !important; border-radius: 8px !important; font-weight: 600 !important; }\n#ck-app .ck-black-primary-button { background: #292524 !important; color: #d6d3d1 !important; border: 1px solid #44403c !important; border-radius: 8px !important; }\n#ck-app .ck-gray-primary-button { background: #292524 !important; color: #a8a29e !important; border: 1px solid #292524 !important; border-radius: 8px !important; }\n#ck-app .ck-text-button { color: #a8a29e !important; }",{"id":849,"title":850,"titles":851,"content":852,"level":417},"\u002Fcancel-flows\u002Fcustom-styling#dark-blurple","Dark Blurple",[40,830],"Inspired by the world's largest real-time community platform — elevated dark. \u002F* ===========================================\n   Dark Blurple — Elevated Dark with Violet\n   =========================================== *\u002F\n\n\u002F* Overlay *\u002F\n#ck-app .ck-background-overlay {\n  background: rgba(0, 0, 0, 0.7) !important;\n}\n\n\u002F* Modal container *\u002F\n#ck-app .ck-modal {\n  background: #2f3136 !important;\n  border: 1px solid #40444b !important;\n  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5) !important;\n  border-radius: 16px !important;\n}\n\n\u002F* All step backgrounds *\u002F\n#ck-app .ck-step,\n#ck-app .ck-survey-step, #ck-app .ck-confirm-step, #ck-app .ck-freeform-step,\n#ck-app .ck-pause-step, #ck-app .ck-discount-step, #ck-app .ck-contact-step,\n#ck-app .ck-redirect-step, #ck-app .ck-complete-step, #ck-app .ck-progress-step {\n  background: #2f3136 !important;\n}\n\n\u002F* Header, body, footer *\u002F\n#ck-app .ck-step-header { background: #2f3136 !important; border-bottom: 1px solid #36393f !important; }\n#ck-app .ck-step-header-text { color: #ffffff !important; }\n#ck-app .ck-step-description-text { color: #b9bbbe !important; }\n#ck-app .ck-step-body { background: #2f3136 !important; color: #b9bbbe !important; }\n#ck-app .ck-step-footer { background: #2f3136 !important; border-top: 1px solid #36393f !important; }\n\n\u002F* Form elements *\u002F\n#ck-app select,\n#ck-app input,\n#ck-app textarea {\n  background-color: #36393f !important;\n  color: #ffffff !important;\n  border-color: #4f545c !important;\n}\n#ck-app select:focus,\n#ck-app input:focus,\n#ck-app textarea:focus {\n  border-color: #5865f2 !important;\n  box-shadow: 0 0 0 2px rgba(88, 101, 242, 0.3) !important;\n  outline: none !important;\n}\n#ck-app ::placeholder { color: #72767d !important; }\n#ck-app option { background-color: #36393f !important; color: #ffffff !important; }\n\n\u002F* Text color overrides *\u002F\n#ck-app .text-gray-900, #ck-app .text-gray-800 { color: #ffffff !important; }\n#ck-app .text-gray-700, #ck-app .text-gray-600, #ck-app .text-gray-500 { color: #b9bbbe !important; }\n#ck-app .text-brand-black { color: #ffffff !important; }\n#ck-app .text-opacity-60, #ck-app .text-opacity-80, #ck-app .text-opacity-90 {\n  --tw-text-opacity: 1 !important;\n}\n\n\u002F* Info boxes and offer containers *\u002F\n#ck-app [class*=\"bg-client-primary-light\"] { background-color: #36393f !important; }\n#ck-app .bg-opacity-5 { --tw-bg-opacity: 1 !important; }\n#ck-app .ck-pause-subscription-details { border-color: #40444b !important; color: #b9bbbe !important; }\n#ck-app .ck-pause-subscription-details b { color: #ffffff !important; }\n#ck-app .active-discount-disclaimer { color: #b9bbbe !important; background-color: #36393f !important; }\n#ck-app .ck-step-body li, #ck-app .ck-step-body label { color: #b9bbbe !important; }\n\n\u002F* Brand color overrides *\u002F\n#ck-app .h-14.rounded-t-lg { background-color: #36393f !important; }\nhtml body #ck-app .bg-client-primary { background-color: #5865f2 !important; }\nhtml body #ck-app .text-client-primary { color: #5865f2 !important; }\nhtml body #ck-app .text-client-primary-middle { color: #5865f2 !important; opacity: 0.4 !important; }\nhtml body #ck-app .text-client-primary-light { color: #b9bbbe !important; }\nhtml body #ck-app .border-client-primary { border-color: #5865f2 !important; }\nhtml body #ck-app .border-client-primary-light { border-color: #40444b !important; }\n\n\u002F* Buttons *\u002F\n#ck-app .ck-primary-button { background: #5865f2 !important; color: #ffffff !important; border: none !important; border-radius: 8px !important; font-weight: 600 !important; }\n#ck-app .ck-black-primary-button { background: #36393f !important; color: #ffffff !important; border: 1px solid #40444b !important; border-radius: 8px !important; }\n#ck-app .ck-gray-primary-button { background: #36393f !important; color: #b9bbbe !important; border: 1px solid #40444b !important; border-radius: 8px !important; }\n#ck-app .ck-text-button { color: #b9bbbe !important; }",{"id":854,"title":855,"titles":856,"content":857,"level":417},"\u002Fcancel-flows\u002Fcustom-styling#light-clean","Light Clean",[40,830],"A polished light theme with soft borders and subtle shadows. \u002F* ===========================================\n   Light Clean — Polished Light\n   =========================================== *\u002F\n\n\u002F* Overlay *\u002F\n#ck-app .ck-background-overlay {\n  background: rgba(15, 23, 42, 0.4) !important;\n  backdrop-filter: blur(4px) !important;\n}\n\n\u002F* Modal container *\u002F\n#ck-app .ck-modal {\n  background: #ffffff !important;\n  border: 1px solid #e2e8f0 !important;\n  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.15) !important;\n  border-radius: 16px !important;\n}\n\n\u002F* All step backgrounds *\u002F\n#ck-app .ck-step,\n#ck-app .ck-survey-step, #ck-app .ck-confirm-step, #ck-app .ck-freeform-step,\n#ck-app .ck-pause-step, #ck-app .ck-discount-step, #ck-app .ck-contact-step,\n#ck-app .ck-redirect-step, #ck-app .ck-complete-step, #ck-app .ck-progress-step {\n  background: #ffffff !important;\n}\n\n\u002F* Header, body, footer *\u002F\n#ck-app .ck-step-header { background: #ffffff !important; border-bottom: 1px solid #f1f5f9 !important; }\n#ck-app .ck-step-header-text { color: #0f172a !important; }\n#ck-app .ck-step-description-text { color: #64748b !important; }\n#ck-app .ck-step-body { background: #ffffff !important; color: #334155 !important; }\n#ck-app .ck-step-footer { background: #ffffff !important; border-top: 1px solid #f1f5f9 !important; }\n\n\u002F* Form elements *\u002F\n#ck-app select,\n#ck-app input,\n#ck-app textarea {\n  background-color: #f8fafc !important;\n  color: #0f172a !important;\n  border-color: #e2e8f0 !important;\n}\n#ck-app select:focus,\n#ck-app input:focus,\n#ck-app textarea:focus {\n  border-color: #6366f1 !important;\n  box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2) !important;\n  outline: none !important;\n}\n#ck-app ::placeholder { color: #94a3b8 !important; }\n#ck-app option { background-color: #ffffff !important; color: #0f172a !important; }\n\n\u002F* Brand color overrides *\u002F\nhtml body #ck-app .bg-client-primary { background-color: #6366f1 !important; }\nhtml body #ck-app .text-client-primary { color: #6366f1 !important; }\nhtml body #ck-app .text-client-primary-middle { color: #6366f1 !important; opacity: 0.3 !important; }\nhtml body #ck-app .text-client-primary-light { color: #64748b !important; }\nhtml body #ck-app .border-client-primary { border-color: #6366f1 !important; }\nhtml body #ck-app .border-client-primary-light { border-color: #e2e8f0 !important; }\n#ck-app [class*=\"bg-client-primary-light\"] { background-color: #eef2ff !important; }\n\n\u002F* Info boxes and offer containers *\u002F\n#ck-app .ck-pause-subscription-details { border-color: #e2e8f0 !important; color: #64748b !important; }\n#ck-app .ck-pause-subscription-details b { color: #0f172a !important; }\n#ck-app .active-discount-disclaimer { color: #64748b !important; background-color: #f8fafc !important; }\n#ck-app .ck-step-body li, #ck-app .ck-step-body label { color: #334155 !important; }\n\n\u002F* Buttons *\u002F\n#ck-app .ck-primary-button { background: #6366f1 !important; color: #ffffff !important; border: none !important; border-radius: 8px !important; font-weight: 600 !important; }\n#ck-app .ck-text-button { color: #6366f1 !important; }\n#ck-app .ck-black-primary-button { background: #f8fafc !important; color: #334155 !important; border: 1px solid #e2e8f0 !important; border-radius: 8px !important; }\n#ck-app .ck-gray-primary-button { background: #f1f5f9 !important; color: #475569 !important; border: 1px solid #e2e8f0 !important; border-radius: 8px !important; }",{"id":859,"title":860,"titles":861,"content":862,"level":374},"\u002Fcancel-flows\u002Fcustom-styling#building-custom-dark-themes","Building Custom Dark Themes",[40],"The templates above override all brand colors (CTA buttons, header bar icons, survey borders, offer containers) using html body #ck-app selectors for specificity. They also override .text-brand-black, .text-opacity-*, and [class*=\"bg-client-primary-light\"] for dark background legibility. If you build a custom theme, include both the brand color overrides and the text color overrides sections or elements may remain in your dashboard's default brand color.",{"id":864,"title":865,"titles":866,"content":867,"level":374},"\u002Fcancel-flows\u002Fcustom-styling#quick-implementation-guide","Quick Implementation Guide",[40],"Add one of the templates above to your site by injecting a \u003Cstyle> tag anywhere on the page where the Churnkey embed loads. The CSS will automatically apply to the Cancel Flow modal. \u003Cstyle>\n  \u002F* Paste your chosen template CSS here *\u002F\n\u003C\u002Fstyle>",{"id":869,"title":870,"titles":871,"content":872,"level":417},"\u002Fcancel-flows\u002Fcustom-styling#using-an-ai-coding-assistant","Using an AI coding assistant",[40,865],"If you use an AI coding assistant (Claude Code, Cursor, Copilot, etc.), paste the following prompt to have it wire up the CSS for you: I need to add custom CSS styling to my Churnkey Cancel Flow embed.\n\nHere is the CSS template I want to apply:\n\n\u003CPASTE YOUR CHOSEN CSS TEMPLATE HERE>\n\nInstructions:\n1. Find where the Churnkey embed script loads in my codebase\n   (look for `churnkey.init`, `assets.churnkey.co`, or `window.churnkey`).\n2. Add a \u003Cstyle> tag on the same page, BEFORE the Churnkey script,\n   containing the CSS above.\n3. If my app uses a framework (React, Vue, Next.js, etc.), inject\n   the styles using the idiomatic approach for that framework\n   (e.g., a global stylesheet import or a \u003Cstyle> block in the layout).\n4. Do not modify the Churnkey embed script itself.\n\nReference docs: https:\u002F\u002Fdocs.churnkey.co\u002Fcancel-flows\u002Fcustom-styling html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .smKOL, html code.shiki .smKOL{--shiki-light:#E53935;--shiki-default:#116329;--shiki-dark:#7EE787}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}",{"id":45,"title":44,"titles":874,"content":875,"level":368},[],"Protect your work in the Cancel Flow builder with undo, redo, discard, and one-click restore of any previously published version. The Cancel Flow builder saves your changes automatically as you edit. That is convenient most of the time, but auto-save also means that a stray click, an accidental deletion, or a mid-edit mistake can be written to your draft before you notice. Version control gives you a safety net around that behavior: you can step backward through your recent edits, throw away a messy draft, or jump back to any version of the flow you have previously published. This page explains how each control works, when to reach for it, and what actually gets versioned behind the scenes.",{"id":877,"title":878,"titles":879,"content":880,"level":374},"\u002Fcancel-flows\u002Fversion-history#why-this-exists","Why this exists",[44],"There are two moments where customers historically lost work. The first is the small one: you delete a step, move something by mistake, or paste the wrong copy into a header, and auto-save persists the change before you realize. The second is larger: you spend an afternoon reworking a flow, decide the new version is worse than the one you shipped last month, and want the old one back. Undo\u002Fredo addresses the first case. Version history with restore addresses the second. Discard Changes is the middle ground — when you want to throw away everything you have touched in this editing session and return to the currently live flow.",{"id":882,"title":883,"titles":884,"content":885,"level":374},"\u002Fcancel-flows\u002Fversion-history#accessing-version-history","Accessing Version History",[44],"Open any flow in the builder (app.churnkey.co\u002Fbuilder\u002F:flowId). In the top toolbar you will find a History dropdown next to the Undo and Redo buttons. On smaller screens the same action lives inside the collapsed menu button. Clicking History opens a short menu with two options: View version history and Discard unsaved changes. The second option is disabled when your draft is in sync with the last published version. The History dropdown exposes both entry points into version control. Choosing View version history opens the Version History drawer on the right side of the editor. The drawer lists every published version of this flow, newest first, and stays open while you continue to work so you can compare what is in front of you against previous snapshots. The drawer lists every published version of the flow with date, time, and a Restore shortcut. Each entry in the timeline shows: A version number (auto-incremented every time you publish).The date and time the version was published.A Live badge on the version currently being served to your customers.A Restore button on every version that is not the live one. If you have unpublished changes in the draft, the Discard unsaved changes option in the History dropdown becomes enabled, giving you a shortcut to revert without leaving the builder.",{"id":887,"title":888,"titles":889,"content":890,"level":374},"\u002Fcancel-flows\u002Fversion-history#undo-and-redo","Undo and Redo",[44],"The builder keeps a rolling history of the changes you make during an editing session. You can step backward and forward through that history at any time.",{"id":892,"title":893,"titles":894,"content":895,"level":417},"\u002Fcancel-flows\u002Fversion-history#keyboard-shortcuts","Keyboard shortcuts",[44,888],"ActionmacOSWindows \u002F LinuxUndo⌘ZCtrl+ZRedo⌘⇧ZCtrl+Shift+Z or Ctrl+Y The same actions are available as Undo and Redo buttons in the builder toolbar. On mobile screens the buttons move into the collapsed toolbar menu. Undo, Redo, and the History dropdown live next to the Saved indicator in the builder toolbar.",{"id":897,"title":898,"titles":899,"content":900,"level":417},"\u002Fcancel-flows\u002Fversion-history#how-far-back-can-you-go","How far back can you go?",[44,888],"The builder stores up to 50 recent states per editing session. Once you pass that limit, the oldest state drops off the bottom of the stack. Continuous changes are grouped together so that the undo stack stays useful. The builder waits 500 milliseconds after you stop typing (or dragging, or clicking) before it records a new snapshot. In practice this means that typing a paragraph of copy is a single undo, not one undo per keystroke — ⌘Z takes you back to before you started typing, not back one character. Undo and redo are session-local. They track the changes you have made since you opened the builder on this device. Closing the tab, refreshing the page, or switching devices clears the stack. To recover something from a previous session, use Version History.",{"id":902,"title":903,"titles":904,"content":905,"level":374},"\u002Fcancel-flows\u002Fversion-history#discard-changes","Discard Changes",[44],"Discard Changes reverts your current draft to match the version that is live for your customers right now. Use it when you have been experimenting, decide nothing you did is worth keeping, and want a clean slate. To discard, click History in the toolbar and choose Discard unsaved changes. The option is only active when your draft diverges from the last published version. Discard Changes asks for confirmation before replacing your draft with the live version. Churnkey asks you to confirm before discarding, because the action cannot be undone from the drawer itself: This will revert all unpublished changes to the last published version. Confirming replaces your draft with the live version. Specifically, the following fields are restored: All steps and their configurationThe flow nameThe brand image and primary colorThe list of translated languages If you discard by mistake, you can still press ⌘Z to bring the draft back — undo works across this action too, as long as you have not closed the tab.",{"id":907,"title":908,"titles":909,"content":910,"level":374},"\u002Fcancel-flows\u002Fversion-history#restoring-a-previously-published-version","Restoring a previously published version",[44],"Restoring goes one step further than Discard: instead of returning to the current live flow, you can pick any version in the timeline and roll the draft back to its contents. Open the Version History drawer from the toolbar.Find the version you want to restore. Each entry shows its publish timestamp.Click Restore on that entry.Confirm in the warning modal that appears. Restore warns you before overwriting the draft — the action is undoable via ⌘Z. Restoring rewrites your current draft with the steps, flow name, branding, and translations from the version you selected. Restoring does not re-publish the flow. Your customers continue to see the live version until you review the restored draft and publish it yourself — this lets you make further edits on top of an old version before shipping it. Restore is undoable. If you click Restore on the wrong version, press ⌘Z (or Ctrl+Z) to bring back the draft you had before. The restore counts as a single entry in the undo stack, so one undo is enough.",{"id":912,"title":913,"titles":914,"content":915,"level":374},"\u002Fcancel-flows\u002Fversion-history#what-gets-versioned","What gets versioned",[44],"Understanding what lands in the timeline — and what does not — will save you some head-scratching the first time you go looking for something. Only published versions appear in Version History. Every time you click Publish, Churnkey takes a snapshot of the flow in its current state, locks that snapshot so it cannot be edited, and advances your working draft on top of it. That snapshot is what shows up in the drawer. Drafts in progress are not versioned. If you are halfway through redesigning a step and close the builder, the draft is auto-saved, but nothing new lands in the timeline. You are protected during that editing session by the undo stack, and protected against losing the live flow by Discard and Restore. This model keeps the timeline focused on states you actually shipped to customers. You are not wading through dozens of half-finished drafts to find the last version that went live.",{"id":917,"title":918,"titles":919,"content":422,"level":374},"\u002Fcancel-flows\u002Fversion-history#frequently-asked-questions","Frequently asked questions",[44],{"id":921,"title":922,"titles":923,"content":924,"level":417},"\u002Fcancel-flows\u002Fversion-history#does-restoring-an-old-version-publish-it-automatically","Does restoring an old version publish it automatically?",[44,918],"No. Restore only replaces your current draft. You still need to click Publish when you are ready for customers to see it. That gives you a chance to tweak the restored flow before shipping it.",{"id":926,"title":927,"titles":928,"content":929,"level":417},"\u002Fcancel-flows\u002Fversion-history#can-i-preview-a-version-before-restoring-it","Can I preview a version before restoring it?",[44,918],"Not directly. The timeline shows each version's publish timestamp, and restoring itself is undoable — so the recommended workflow is to restore the version you are considering, inspect the draft in the builder, and press ⌘Z if it was not what you wanted.",{"id":931,"title":932,"titles":933,"content":934,"level":417},"\u002Fcancel-flows\u002Fversion-history#does-undo-work-after-i-publish","Does undo work after I publish?",[44,918],"Publishing is not itself undone by ⌘Z. Undo and redo track edits to your draft. Once a version is published it lives in the timeline, and if you need to roll back, use Restore to bring an earlier published version into the draft and publish again.",{"id":936,"title":937,"titles":938,"content":939,"level":417},"\u002Fcancel-flows\u002Fversion-history#are-ab-test-variants-versioned-the-same-way","Are A\u002FB test variants versioned the same way?",[44,918],"Yes. Each flow — including each variant of an A\u002FB test — has its own independent version history. Publishing one variant does not create a version on the other.",{"id":941,"title":942,"titles":943,"content":944,"level":417},"\u002Fcancel-flows\u002Fversion-history#why-did-my-undo-history-disappear","Why did my undo history disappear?",[44,918],"The undo stack is held in memory for your current editing session. Refreshing the page, closing the tab, or opening the flow on another device starts a fresh stack. To reach a state from a previous session, use Version History.",{"id":946,"title":947,"titles":948,"content":949,"level":417},"\u002Fcancel-flows\u002Fversion-history#what-happens-to-the-live-flow-while-i-am-editing","What happens to the live flow while I am editing?",[44,918],"Nothing. Customers continue to see the last published version until you publish again. Your draft, your undo\u002Fredo stack, and any discards or restores only affect the builder view — they never change what is live in production until you explicitly publish.",{"id":49,"title":48,"titles":951,"content":952,"level":368},[],"Our managed email based setup takes care of customer verification for you with an email verification code.",{"id":954,"title":955,"titles":956,"content":957,"level":958},"\u002Fcancel-flows\u002Femail-verified-cancel-flow#_1-insert-the-churnkey-js-snippet","1. Insert the Churnkey JS Snippet",[48],"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 \u003Chead> element. \u003Cscript>\n!function(){\n  if (!window.churnkey || !window.churnkey.created) {\n    window.churnkey = { created: true };\n    const a = document.createElement('script');\n    a.src = 'https:\u002F\u002Fassets.churnkey.co\u002Fjs\u002Fapp.js?appId=YOUR_APP_ID';\n    a.async = true;\n    const b = document.getElementsByTagName('script')[0];\n    b.parentNode.insertBefore(a, b);\n  }\n}();\n\u003C\u002Fscript>",4,{"id":960,"title":961,"titles":962,"content":963,"level":958},"\u002Fcancel-flows\u002Femail-verified-cancel-flow#_2-linking-your-cancel-button-to-your-cancel-flow","2. Linking your cancel button to your Cancel Flow",[48],"document.getElementById('BUTTON_ID').addEventListener('click', function () {\n  window.churnkey.managedFlow('show', {\n    appId: 'YOUR_APP_ID',\n    mode: 'live',\n    provider: 'stripe',\n    prefilled: {\n      email: 'jane@example.com', \u002F\u002Foptional\n    },\n    customerDataEndpoint: 'https:\u002F\u002Fyour_company_endpoint\u002Fcustomer-data', \u002F\u002Foptional\n  });\n});",{"id":965,"title":966,"titles":967,"content":968,"level":374},"\u002Fcancel-flows\u002Femail-verified-cancel-flow#how-it-works","How it Works",[48],"After your customer enters their email address, we will send a verification code to that email. They’ll have 15 minutes to enter the verification code, at which point they will proceed to the Churnkey Cancel Flow as usual. You can customize the verification email, including which address it comes from and the subject line, on Churnkey | Flows | Settings.",{"id":970,"title":36,"titles":971,"content":972,"level":374},"\u002Fcancel-flows\u002Femail-verified-cancel-flow#configuration-options",[48],"You can use all of the same configuration options from our standard setup.",{"id":974,"title":975,"titles":976,"content":977,"level":958},"\u002Fcancel-flows\u002Femail-verified-cancel-flow#customer-attributes-with-a-data-endpoint","Customer Attributes with a Data Endpoint",[48,36],"Our customer data endpoint is particularly relevant for email based setups, since you may not have access to which customer is cancelling before they enter their email address. Once a customer’s email address has been verified and Churnkey finds the matching customer ID within your payment provider data, if customerDataEndpoint is defined, Churnkey will make a POST request to that endpoint. POST https:\u002F\u002Fyour_company_endpoint\u002Fcustomer-data\n\nBODY\n{\n  customerId: 'cus_4815162342',\n  signature: 'signed_customer_hmac'\n} The signature will match the signed customerId as described with Server Side Authentication. The expected return format is: {\n  customerId: 'cus_4815162342', \u002F\u002F optional\n  subscriptionId: 'sub_48153811', \u002F\u002F optional\n  customerAttributes: {\n    \u002F\u002F key-value pairs\n    exampleNumber: 42,\n    exampleBoolean: true,\n    exampleString: 'active',\n  } html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":53,"title":52,"titles":979,"content":980,"level":368},[],"Run controlled experiments to optimize your Cancel Flows and maximize customer retention.",{"id":982,"title":983,"titles":984,"content":985,"level":374},"\u002Fcancel-flows\u002Fa-b-testing#what-is-ab-testing","What is A\u002FB Testing?",[52],"A\u002FB testing lets you compare two versions of a Cancel Flow to see which one performs better. Churnkey splits your traffic between a control variant (your current flow) and a test variant (your modified flow), then measures which one saves more customers and generates more revenue.",{"id":987,"title":988,"titles":989,"content":990,"level":374},"\u002Fcancel-flows\u002Fa-b-testing#the-5-lifecycle-states","The 5 Lifecycle States",[52],"Every A\u002FB test moves through five states: Not Started — Test created, waiting for you to startEnrolling (1–4 weeks) — Traffic splits between Control and TestTracking (30 days) — Measuring if saved customers actually stayAwaiting Decision — Data complete, waiting for you to pick a winnerCompleted — Winner declared, test archived Total timeline: 37–58 days (1–4 weeks enrollment + 30 days tracking)",{"id":992,"title":993,"titles":994,"content":995,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#state-1-not-started","State 1: Not Started",[52,988],"What's happeningTest is created but not running yetDurationUntil you click \"Start\"Your actionVerify both variants are ready, then start the testData collectedNone This state gives you time to review your test variant in the Cancel Flow builder and ensure everything is configured correctly before going live.",{"id":997,"title":998,"titles":999,"content":1000,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#state-2-enrolling-14-weeks","State 2: Enrolling (1–4 weeks)",[52,988],"What's happeningNew cancel sessions are split 50\u002F50 between Control and TestDuration1–4 weeks (you choose during setup)Your actionWait. Do not edit variants during this phaseData collectedSession counts, initial save rates During enrollment, every customer who enters your Cancel Flow is randomly assigned to one variant. This assignment stays consistent if they return later. You choose the enrollment duration when creating your test from four presets: 1 week, 2 weeks, 3 weeks, or 4 weeks. Churnkey recommends a duration based on your flow's weekly session volume to ensure enough data for statistical significance. See Choosing an Enrollment Period for guidance. When enrollment ends, the cohort is locked. No new sessions enter the test after the enrollment period.",{"id":1002,"title":1003,"titles":1004,"content":1005,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#state-3-tracking-30-days","State 3: Tracking (30 days)",[52,988],"What's happeningNo new enrollments. Monitoring if \"saved\" users actually stayDuration30 days (fixed)Your actionWait. This phase validates real retentionData collectedRetention rates, reactivation, LTV impact, revenue per exposure Why this phase matters: When a customer accepts an offer (pause, discount), we mark them as \"saved\" but we don't know yet if they actually stayed. They might cancel again next week, skip their next invoice, or churn silently. The 30-day tracking window captures the real outcome by measuring whether saved customers pay their next invoice and remain subscribed.",{"id":1007,"title":1008,"titles":1009,"content":1010,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#state-4-awaiting-decision","State 4: Awaiting Decision",[52,988],"What's happeningAll data is in. Time to pick a winnerDurationUntil you decideYour actionReview results and declare a winnerData availableStatistical confidence, save rate lift, revenue difference, ARR impact Look at the statistical confidence level to know how trustworthy your results are: ConfidenceWhat it means95%+Strong evidence. Safe to declare a winner80-95%Moderate evidence. Proceed with cautionBelow 80%Weak evidence. Results may be due to chance",{"id":1012,"title":1013,"titles":1014,"content":1015,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#state-5-completed","State 5: Completed",[52,988],"What's happeningWinner declared, test is finishedDurationPermanentYour actionNone. The winning variant is now liveData availableFinal results archived for reference When you confirm your decision, the winning variant becomes active and the losing variant is deactivated. All future customers in this segment see the winner.",{"id":1017,"title":1018,"titles":1019,"content":1020,"level":374},"\u002Fcancel-flows\u002Fa-b-testing#setting-up-a-test","Setting Up a Test",[52],"Prerequisite: A\u002FB tests can only run on segmented Cancel Flows, not on your primary (default) Cancel Flow. StepWhat to do1. HypothesisDocument what you're testing and why. Be specific: \"Offering a 3-month pause instead of 1-month will increase save rates for annual subscribers.\"2. Primary MetricChoose which metric determines the winner. Revenue Per Exposure is recommended for most tests. See Primary Metrics Reference for details on all 6 options.3. Cancel FlowSelect which segmented flow to test. Higher-volume flows reach statistical significance faster.4. Enrollment PeriodChoose how long to enroll new sessions: 1 week, 2 weeks, 3 weeks, or 4 weeks. Churnkey recommends a duration based on your flow's volume. The tracking window is always 30 days. See Choosing an Enrollment Period for details.5. Review & LaunchConfirm settings and create the test. It starts in \"Not Started\" state until you click \"Start.\"",{"id":1022,"title":1023,"titles":1024,"content":422,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#step-1-hypothesis-metric-flow","Step 1: Hypothesis, Metric & Flow",[52,1018],{"id":1026,"title":1027,"titles":1028,"content":422,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#step-2-enrollment-period","Step 2: Enrollment Period",[52,1018],{"id":1030,"title":1031,"titles":1032,"content":422,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#step-3-review-launch","Step 3: Review & Launch",[52,1018],{"id":1034,"title":1035,"titles":1036,"content":1037,"level":374},"\u002Fcancel-flows\u002Fa-b-testing#choosing-an-enrollment-period","Choosing an Enrollment Period",[52],"The enrollment period controls how long Churnkey splits traffic between your Control and Test variants. A longer enrollment period collects more sessions, which increases statistical confidence — but also delays your results.",{"id":1039,"title":1040,"titles":1041,"content":1042,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#enrollment-presets","Enrollment Presets",[52,1035],"OptionDurationBest for1 week7 daysHigh-volume flows or testing dramatic changes2 weeks14 daysMost tests (default recommendation)3 weeks21 daysMedium-volume flows needing more data4 weeks28 daysLow-volume flows or detecting small differences",{"id":1044,"title":1045,"titles":1046,"content":1047,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#how-recommendations-work","How Recommendations Work",[52,1035],"When you select a Cancel Flow, Churnkey analyzes your flow's weekly session volume and recommends an enrollment period. The goal is to collect roughly 200 sessions per variant (400 total), which provides enough data for reliable statistical analysis. The recommended preset is highlighted with a \"Recommended\" badge in the enrollment selector.",{"id":1049,"title":1050,"titles":1051,"content":1052,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#the-two-phase-timeline","The Two-Phase Timeline",[52,1035],"Every A\u002FB test runs in two consecutive phases, shown as a visual timeline in the setup screen: Phase 1 — Enrollment: New cancel sessions are split between variants for the duration you choose (7, 14, 21, or 28 days).Phase 2 — Tracking: No new enrollments. Churnkey monitors saved customers for 30 days to measure whether they actually stay and pay. Total test duration = enrollment period + 30 days. For example, a 2-week enrollment results in a 44-day test.",{"id":1054,"title":1055,"titles":1056,"content":1057,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#low-volume-warning","Low Volume Warning",[52,1035],"If your flow's expected session count during the enrollment period is below 200 total (100 per variant), Churnkey displays a warning. With low volume, only large differences between variants will be statistically significant. Small optimizations may not be detectable, and results may reflect directional trends rather than proven outcomes. If you see this warning, consider selecting a longer enrollment period or testing on a higher-volume flow.",{"id":1059,"title":1060,"titles":1061,"content":1062,"level":374},"\u002Fcancel-flows\u002Fa-b-testing#primary-metrics-reference","Primary Metrics Reference",[52],"Your primary metric determines which variant \"wins.\" Choose based on what matters most to your business. Revenue Per Exposure (Recommended) Formula: Total Revenue from Saved Customers ÷ Total SessionsBest for: Overall business impact—balances save rate against revenue qualityExample: Test saves 100 customers at $10 each ($1,000). Control saves 50 at $25 each ($1,250). Control wins despite lower save rate. Save Rate Formula: Customers Saved ÷ Total Sessions × 100Best for: Maximizing retention count; good for testing copy, layout, or flow lengthWatch out: Can lead to over-discounting if used alone Reactivation Rate Formula: Saved Customers Who Paid Next Invoice ÷ Total Saved Customers × 100Best for: When saved customers frequently cancel before their next paymentWhy it matters: A customer who accepts an offer but never pays again = $0 value Pause Acceptance Rate Formula: Customers Who Accepted Pause ÷ Total Sessions × 100Best for: Testing pause duration, messaging, or positioning specifically Discount Acceptance Rate Formula: Customers Who Accepted Discount ÷ Total Sessions × 100Best for: Optimizing discount percentages, durations, or presentationWatch out: Higher acceptance with larger discounts might hurt revenue LTV Extension Formula: Sum of Additional Months Stayed ÷ Total Saved CustomersBest for: Long-term optimization when you have historical LTV dataNote: Requires longer tracking periods to produce meaningful data",{"id":1064,"title":1065,"titles":1066,"content":422,"level":374},"\u002Fcancel-flows\u002Fa-b-testing#reading-results","Reading Results",[52],{"id":1068,"title":1069,"titles":1070,"content":1071,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#the-results-dashboard","The Results Dashboard",[52,1065],"The results dashboard shows side-by-side performance cards for Control and Test variants. Each card displays the same metrics, making it easy to compare performance directly. The variant with better performance on your primary metric is highlighted. If the difference is statistically significant (95%+ confidence), you'll see a \"Significant\" badge. Key metrics on each variant card: MetricWhat it tells youSessions enrolledTotal customers assigned to this variantSave rate% accepting any retention offerRevenue per exposureAverage revenue per session (your likely primary metric)Reactivation rate% of saves who paid their next invoiceLTV extensionAverage additional months customers stayed",{"id":1073,"title":1074,"titles":1075,"content":1076,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#offer-breakdown","Offer Breakdown",[52,1065],"The offer breakdown shows which retention offers customers accepted in each variant: Offer TypeWhat it showsPauseCustomers who chose to pause their subscriptionDiscountCustomers who accepted a discount offerPlan ChangeCustomers who downgraded to a lower plan Compare the distribution between Control and Test to understand how customers are being saved, not just whether they're saved.",{"id":1078,"title":1079,"titles":1080,"content":1081,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#retention-timeline","Retention Timeline",[52,1065],"The retention timeline tracks what percentage of saved customers remain active over time (day 7, 14, 30, 60, 90). This reveals whether your saves are \"sticky\" or if customers churn shortly after accepting an offer. A steep drop-off early in the timeline suggests customers are accepting offers but not genuinely retained. A flat line indicates strong long-term retention.",{"id":1083,"title":1084,"titles":1085,"content":1086,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#expected-impact","Expected Impact",[52,1065],"Once results are in, the dashboard calculates the projected business impact if you roll out the winning variant: MetricWhat it showsSave Rate LiftPercentage point difference between variants (e.g., +5% means Test saves 5 more customers per 100)Revenue Per Exposure DifferenceDollar difference per customer entering the flowARR ImpactProjected annual recurring revenue change based on your traffic volume These projections help you quantify whether the improvement is worth implementing. A statistically significant result with minimal ARR impact might not justify the change.",{"id":1088,"title":1089,"titles":1090,"content":1091,"level":374},"\u002Fcancel-flows\u002Fa-b-testing#making-a-decision","Making a Decision",[52],"When your test reaches \"Awaiting Decision,\" you need to analyze the results and pick a winner. This section helps you understand what the numbers mean and how to decide.",{"id":1093,"title":1094,"titles":1095,"content":1096,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#understanding-statistical-significance","Understanding Statistical Significance",[52,1089],"Statistical significance tells you whether the difference between variants is real or just random chance. When you flip a coin 10 times and get 6 heads, that doesn't prove the coin is biased—it could easily happen by chance. But if you flip 1,000 times and get 600 heads, something is definitely going on. A\u002FB testing works the same way. Confidence LevelWhat It MeansCan You Trust It?95%+Only 5% chance the difference is randomYes — mathematically reliable80-94%6-20% chance the difference is randomMaybe — proceed with cautionBelow 80%High chance the difference is randomNo — not statistically reliable Important: You can always make a decision regardless of confidence level. Low confidence doesn't prevent you from choosing—it just means there's higher risk that the \"winner\" isn't actually better. You're making a judgment call, not a data-driven decision.",{"id":1098,"title":1099,"titles":1100,"content":1101,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#understanding-lift","Understanding Lift",[52,1089],"Lift measures how much better (or worse) Test performed compared to Control, as a percentage. Formula: Lift = (Test - Control) \u002F Control × 100 Control Save RateTest Save RateLift40%44%+10%40%48%+20%40%36%-10%",{"id":1103,"title":1104,"titles":1105,"content":1106,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#how-sessions-affect-confidence","How Sessions Affect Confidence",[52,1089],"The smaller the improvement you're trying to detect, the more data you need. Think of it like hearing someone in a noisy room: Lift SizeDifficultySessions Needed (per variant)20%+ liftLike someone shouting—easy to detect~250 sessions10% liftNormal conversation—need to focus~500 sessions5% liftA whisper—need quiet to hear~1,000+ sessions Minimum requirement: Below 30 sessions per variant, confidence is automatically 0%. The math simply doesn't work with fewer samples. Practical guidance based on your volume: Your Weekly VolumeWhat You Can Reliably Detect50 sessions\u002FweekOnly dramatic wins or losses (20%+ lift)100 sessions\u002FweekModerate differences (10-15% lift)250+ sessions\u002FweekSubtle optimizations (5% lift) Bottom line: If you're making small tweaks expecting 5% improvements, you need a lot of data. If you're testing dramatically different approaches, you'll know faster.",{"id":1108,"title":1109,"titles":1110,"content":1111,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#the-decision-path","The Decision Path",[52,1089],"When evaluating your results, check these metrics in order: Step 1: Check Statistical Confidence Is it 95%+? → You have reliable data to make a decisionIs it below 95%? → Results are not mathematically reliable (see \"Decision with Risk\" cases below) Step 2: Look at Your Primary Metric Which variant performed better on the metric you chose (e.g., Revenue Per Exposure)?How big is the difference? A 2% lift vs. a 20% lift have very different implications. Step 3: Review Secondary Metrics Does the \"winner\" also perform well on other metrics?Watch for trade-offs: higher save rate but lower reactivation rate could mean you're saving customers who churn again quickly. Step 4: Consider the Lift Positive lift = Test outperformed ControlNegative lift = Control outperformed TestNear-zero lift = Both performed similarly",{"id":1113,"title":1114,"titles":1115,"content":1116,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#decision-by-case","Decision by Case",[52,1089],"Here are common scenarios you'll encounter and how to handle each:",{"id":1118,"title":1119,"titles":1120,"content":1121,"level":958},"\u002Fcancel-flows\u002Fa-b-testing#case-1-clear-winner-high-confidence-strong-lift","Case 1: Clear Winner (High Confidence + Strong Lift)",[52,1089,1114],"MetricControlTestSessions550550Save Rate38%52%Revenue\u002FExposure$22$34 Confidence: 98% · Lift: +55% What this means: Test dramatically outperforms Control, and you have enough data to trust this result. Decision: Choose Test. This is the ideal outcome—clear winner with high confidence.",{"id":1123,"title":1124,"titles":1125,"content":1126,"level":958},"\u002Fcancel-flows\u002Fa-b-testing#case-2-insufficient-data-low-confidence","Case 2: Insufficient Data (Low Confidence)",[52,1089,1114],"MetricControlTestSessions87Save Rate37.5%42.8%Revenue\u002FExposure$18$21 Confidence: 15% · Lift: +14% What this means: Test looks better, but with only 15 sessions total, this could easily be random chance. The 15% confidence means there's an 85% probability this difference is noise. Decision: The result is not statistically reliable. Your options: Choose Control (recommended) — It's your proven baseline. Don't change what works based on unreliable data.Choose Test anyway — If you have strong qualitative reasons to believe the changes are better, you can accept the risk.Run another test — Wait until you have more traffic and test again.",{"id":1128,"title":1129,"titles":1130,"content":1131,"level":958},"\u002Fcancel-flows\u002Fa-b-testing#case-3-equal-performance-no-difference","Case 3: Equal Performance (No Difference)",[52,1089,1114],"MetricControlTestSessions500500Save Rate45%45%Revenue\u002FExposure$25$25 Confidence: 50% · Lift: 0% What this means: Both variants perform identically. Your changes made no measurable impact. Decision: Choose Control. When there's no difference, stick with your original flow—it's simpler and already proven. Consider testing a more significant change next time.",{"id":1133,"title":1134,"titles":1135,"content":1136,"level":958},"\u002Fcancel-flows\u002Fa-b-testing#case-4-test-performs-worse-negative-lift","Case 4: Test Performs Worse (Negative Lift)",[52,1089,1114],"MetricControlTestSessions450450Save Rate50%30%Revenue\u002FExposure$30$18 Confidence: 99% · Lift: -40% What this means: Your changes hurt performance. Control is significantly better, and the high confidence means this is definitely real, not random. Decision: Choose Control. Your hypothesis was wrong—the test variant made things worse. This is still a valuable learning: you now know what doesn't work.",{"id":1138,"title":1139,"titles":1140,"content":1141,"level":958},"\u002Fcancel-flows\u002Fa-b-testing#case-5-high-volume-small-difference","Case 5: High Volume, Small Difference",[52,1089,1114],"MetricControlTestSessions5,5005,500Save Rate43%46%Revenue\u002FExposure$24$27 Confidence: 97% · Lift: +12% What this means: Test is better, and with 11,000 total sessions you can trust this result. However, the improvement is modest (3 percentage points on save rate). Decision: Choose Test. Even small improvements compound over time. A 3% save rate increase across thousands of customers adds up to significant revenue.",{"id":1143,"title":1144,"titles":1145,"content":1146,"level":958},"\u002Fcancel-flows\u002Fa-b-testing#case-6-missing-data-technical-issue","Case 6: Missing Data (Technical Issue)",[52,1089,1114],"MetricControlTestSessions3000Save Rate42%—Revenue\u002FExposure$26— Confidence: N\u002FA · Lift: N\u002FA What this means: Something went wrong. Test variant received no traffic—possibly a configuration error, broken flow, or technical issue. Decision: Choose Control (you have no choice). Investigate why Test got no sessions before running another test. Check that the Test variant is published and active.",{"id":1148,"title":1149,"titles":1150,"content":1151,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#document-your-rationale","Document Your Rationale",[52,1089],"Use the rationale field to record why you chose this winner. Good documentation helps your team understand past decisions. Examples: \"Test showed 55% revenue lift with 98% confidence. Clear winner.\"\"Only 15 sessions enrolled. Choosing Control due to insufficient data.\"\"Test performed 40% worse. Reverting to Control.\"\"Small 3% lift but high confidence. Choosing Test for incremental gains.\"",{"id":1153,"title":1154,"titles":1155,"content":1156,"level":417},"\u002Fcancel-flows\u002Fa-b-testing#what-happens-after-confirmation","What Happens After Confirmation",[52,1089],"When you confirm your decision: The winning variant goes live immediately for all future customers in this segmentThe losing variant is deactivated but preserved for referenceThe test moves to your completed tests history",{"id":1158,"title":1159,"titles":1160,"content":1161,"level":374},"\u002Fcancel-flows\u002Fa-b-testing#faqs","FAQs",[52],"Can I test my primary Cancel Flow?\nNo. A\u002FB tests require segmented Cancel Flows because the primary flow serves as the fallback for all customers who don't match segment criteria. Can I run multiple tests at once?\nYes, as long as they test different segments with non-overlapping customer populations. What happens if I need to edit a variant during the test?\nTechnically possible, but strongly discouraged. It invalidates results because customers before and after the change experienced different flows. Can I pause a test?\nYes. Pausing stops new enrollments. You can resume later, but enrollment restarts from zero. How do I see which variant a specific customer saw?\nCheck the Exposure Stream section in the test results page. Why can't I see final results immediately?\nThe 30-day tracking period ensures we measure actual retention, not just initial offer acceptance. Without it, you'd optimize for people who say \"yes\" but churn anyway. What happens to the losing variant's enrolled users?\nNothing changes for them. The test measures what happened; it doesn't retroactively change anyone's experience.",{"id":57,"title":56,"titles":1163,"content":1164,"level":368},[],"Ensure that your Cancel Flow is working as intended before going live.",{"id":1166,"title":1167,"titles":1168,"content":1169,"level":374},"\u002Fcancel-flows\u002Fcancel-flow-testing#test-in-churnkey","Test in Churnkey",[56],"The first and most simple way to test your Churnkey Cancel Flows is by utilizing the eye icon button within the editor. The allows you to click through the flow to see the modal in real time. Use the eye icon in the Cancel Flow editor to view your Cancel Flow modal.",{"id":1171,"title":1172,"titles":1173,"content":1174,"level":374},"\u002Fcancel-flows\u002Fcancel-flow-testing#live-mode-vs-test-mode-vs-sandbox-mode","Live mode vs Test mode vs Sandbox mode",[56],"In your Churnkey modal embed code, you can switch between 'test', 'live', and 'sandbox' modes by setting the mode parameter. Refer to 'Step Three' of the Quick Start Guide for details.",{"id":1176,"title":1177,"titles":1178,"content":1179,"level":417},"\u002Fcancel-flows\u002Fcancel-flow-testing#live-mode","Live mode",[56,1172],"When your Cancel Flow is in live mode, the Churnkey Cancel Flow only looks for live customers and live coupons from your payment provider.",{"id":1181,"title":1182,"titles":1183,"content":1184,"level":417},"\u002Fcancel-flows\u002Fcancel-flow-testing#test-mode","Test mode",[56,1172],"When your Cancel Flow is in test mode, the Churnkey Cancel Flow only looks for test customers and test coupons from your payment provider.",{"id":1186,"title":1187,"titles":1188,"content":1189,"level":417},"\u002Fcancel-flows\u002Fcancel-flow-testing#sandbox-mode","Sandbox mode",[56,1172],"When your Cancel Flow is in sandbox mode, the Churnkey Cancel Flow pulls customer data, subscriptions, and coupons from your connected Stripe sandbox account. Sandbox mode requires connecting a separate Stripe sandbox account in Settings | Billing Provider. See the Stripe Sandbox Mode documentation for setup instructions.",{"id":1191,"title":1192,"titles":1193,"content":1194,"level":374},"\u002Fcancel-flows\u002Fcancel-flow-testing#view-test-data-and-sandbox-data","View Test Data and Sandbox Data",[56],"Use the dropdown in the top-right corner to switch between viewing live, test, and sandbox data in the Cancel Flows dashboard. The \"View Sandbox Data\" option appears only when a Stripe sandbox account is connected. By clicking the dropdown in the top right hand corner of the Cancel Flows | Analytics page, you can switch between viewing live, test, and sandbox data.",{"id":61,"title":60,"titles":1196,"content":1197,"level":368},[],"Block access to your application during subscription pauses The Pause Wall helps you manage access to your application by automatically blocking access when a customer's subscription is paused. It displays a streamlined UI that allows customers to resume or cancel their subscription immediately. Currently available for Stripe and Chargebee",{"id":1199,"title":1200,"titles":1201,"content":1202,"level":374},"\u002Fcancel-flows\u002Fpause-wall#quick-start","Quick start",[60],"To implement the Pause Wall: Ensure the Churnkey script is loadedAdd the check function to your application initializationConfigure the wall behavior window.churnkey.check('pause', {\n  \u002F\u002F Required - Authentication & identification\n  customerId: 'CUSTOMER_ID',\n  authHash: 'HMAC_HASH',\n  appId: 'YOUR_APP_ID',\n  provider: 'stripe',        \u002F\u002F or 'chargebee'\n  \n  \u002F\u002F Optional - Wall behavior\n  mode: 'live',             \u002F\u002F Use 'test' for development\n  softWall: false,          \u002F\u002F Allow users to exit the wall\n  forceCheck: false         \u002F\u002F Skip caching (not recommended)\n})",{"id":1204,"title":1205,"titles":1206,"content":1207,"level":374},"\u002Fcancel-flows\u002Fpause-wall#how-it-works","How it works",[60],"The Pause Wall activates automatically when: A customer's subscription status is pausedThe subscription is in its paused period When activated, it: Blocks access to your applicationDisplays options to:\nResume the subscription immediatelyCancel the subscription at term endExit the wall (if softWall is enabled)Processes the customer's choiceRestores access when appropriate",{"id":1209,"title":1210,"titles":1211,"content":422,"level":374},"\u002Fcancel-flows\u002Fpause-wall#configuration","Configuration",[60],{"id":1213,"title":1214,"titles":1215,"content":1216,"level":417},"\u002Fcancel-flows\u002Fpause-wall#core-options","Core options",[60,1210],"OptionTypeDefaultDescriptionmodestring'live''live', 'test', or 'sandbox' (Stripe only)softWallbooleanfalseAllow customers to exit the wallforceCheckbooleanfalseBypass subscription status cacheproviderstringrequired'stripe' or 'chargebee'subscriptionIdstringoptionalSpecific subscription to check",{"id":1218,"title":1219,"titles":1220,"content":1221,"level":417},"\u002Fcancel-flows\u002Fpause-wall#custom-actions","Custom actions",[60,1210],"Override default billing actions with custom handlers: window.churnkey.check('pause', {\n  \u002F\u002F ... other options\n  handleResume(customer) {\n    \u002F\u002F Custom resume logic\n    return Promise.resolve()\n  },\n  handleCancel(customer) {\n    \u002F\u002F Custom cancellation logic\n    return Promise.resolve()\n  }\n})",{"id":1223,"title":1224,"titles":1225,"content":1226,"level":417},"\u002Fcancel-flows\u002Fpause-wall#optional-ui-elements","Optional UI elements",[60,1210],"Add extra buttons to the wall: window.churnkey.check('pause', {\n  \u002F\u002F ... other options\n  handleLogout() { }  \u002F\u002F Add logout button\n})",{"id":1228,"title":1229,"titles":1230,"content":1231,"level":417},"\u002Fcancel-flows\u002Fpause-wall#event-callbacks","Event callbacks",[60,1210],"Monitor Pause Wall activity: EventDescriptiononPauseWallActivated()Wall is displayedonResume(customer)Subscription resumed successfullyonCancel(customer)Subscription cancelledonPauseWallClose()Wall is dismissed (soft wall only)onError(error, type)Error occurred Error types: PAUSE_WALL_INITIALIZATION_ERRORPAUSE_WALL_CANCEL_ERRORPAUSE_WALL_RESUME_ERROR",{"id":1233,"title":56,"titles":1234,"content":1235,"level":374},"\u002Fcancel-flows\u002Fpause-wall#testing",[60],"If you would like to test the implementation in your test environment: Create a test customerCreate a subscription for the customerPause the subscription through your billing providerVerify the Pause Wall appearsTest each action:\nResume subscriptionCancel subscriptionExit wall (if softWall is enabled) html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sH4kE, html code.shiki .sH4kE{--shiki-light:#FF5370;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sjIoE, html code.shiki .sjIoE{--shiki-light:#E53935;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sHdwX, html code.shiki .sHdwX{--shiki-light:#E2931D;--shiki-default:#0550AE;--shiki-dark:#79C0FF}",{"id":65,"title":64,"titles":1237,"content":1238,"level":368},[],"AI-powered discount optimization that automatically finds the perfect offer for each customer Adaptive Offers eliminates the guesswork from discount strategies by automatically optimizing discount amounts and durations for each customer. Instead of manually testing different offers or using one-size-fits-all discounts, Adaptive Offers uses AI to find the perfect balance between customer retention and revenue protection.",{"id":1240,"title":5,"titles":1241,"content":1242,"level":374},"\u002Fcancel-flows\u002Fadaptive-offers#overview",[64],"Traditional discount strategies often fail because they treat all customers the same. A 50% discount might save one customer while being unnecessarily generous to another who would have accepted 20%. Adaptive Offers solves this by: Maximizing retention with personalized discount amountsProtecting revenue by avoiding excessive discountsImproving efficiency through continuous optimizationReducing guesswork with data-driven decisions For example, instead of offering every customer a fixed 30% discount for 3 months, Adaptive Offers might learn that newer customers respond better to 25% for 2 months, while long-term customers prefer 35% for 1 month.",{"id":1244,"title":1245,"titles":1246,"content":1247,"level":374},"\u002Fcancel-flows\u002Fadaptive-offers#how-adaptive-offers-works","How Adaptive Offers Works",[64],"Adaptive Offers continuously learns from customer behavior to optimize discount selection. The system operates in three key phases:",{"id":1249,"title":1250,"titles":1251,"content":1252,"level":417},"\u002Fcancel-flows\u002Fadaptive-offers#_1-data-collection","1. Data Collection",[64,1245],"When customers interact with your Cancel Flow, the system tracks: Which discount amounts customers accept or declineHow duration affects acceptance ratesPatterns across different customer segments",{"id":1254,"title":1255,"titles":1256,"content":1257,"level":417},"\u002Fcancel-flows\u002Fadaptive-offers#_2-intelligent-optimization","2. Intelligent Optimization",[64,1245],"Using advanced machine learning, the system: Identifies which discount combinations perform bestBalances testing new options with proven performersAdapts to changing customer behavior over timeEnsures each customer sees a consistent, optimized offer",{"id":1259,"title":1260,"titles":1261,"content":1262,"level":417},"\u002Fcancel-flows\u002Fadaptive-offers#_3-continuous-improvement","3. Continuous Improvement",[64,1245],"As more data becomes available, the system: Refines discount selection accuracyDiscovers new optimization opportunitiesMaintains performance as your business evolves Each customer always receives the same optimized discount offer, ensuring a consistent experience across multiple cancel attempts.",{"id":1264,"title":1210,"titles":1265,"content":1266,"level":374},"\u002Fcancel-flows\u002Fadaptive-offers#configuration",[64],"Setting up Adaptive Offers is straightforward and gives you complete control over your discount strategy while letting AI handle the optimization. Navigate to Cancel Flows in your Churnkey dashboardAccess the Adaptive Offers section from the top navigation menuToggle Enable Adaptive Offers to activate the feature",{"id":1268,"title":1269,"titles":1270,"content":1271,"level":417},"\u002Fcancel-flows\u002Fadaptive-offers#discount-percentage-range","Discount Percentage Range",[64,1210],"Configure the discount percentage boundaries that align with your business model and profitability requirements: Minimum Percentage: The lowest discount threshold (5-95%)Maximum Percentage: The highest discount threshold (5-95%) The system automatically creates discount options in 5% increments within your range. Setting a 20% minimum and 40% maximum gives customers access to 20%, 25%, 30%, 35%, and 40% discounts. The AI will learn which percentage works best for different situations while staying within your comfort zone.",{"id":1273,"title":1274,"titles":1275,"content":1276,"level":417},"\u002Fcancel-flows\u002Fadaptive-offers#discount-duration-range","Discount Duration Range",[64,1210],"Control how long your discounts should last: Minimum Duration: Shortest discount period in months (1-12)Maximum Duration: Longest discount period in months (1-12) Duration options are generated in 1-month increments within your specified range. A configuration of 1-3 months produces three duration options: 1 month, 2 months, and 3 months. A setup with 20-40% discounts and 1-3 month durations, generates 21 unique discount combinations for intelligent optimization.",{"id":1278,"title":1279,"titles":1280,"content":1281,"level":417},"\u002Fcancel-flows\u002Fadaptive-offers#optimization-strategy","Optimization Strategy",[64,1210],"Choose a strategy that aligns with your business margins to define how aggressively the model should optimize discounts. Conservative: More selective with discounts, suited for margins under 60%Balanced (Recommended): Optimizes for sustainable growth with moderate margins (60-80%)Aggressive: More willing to offer larger discounts, for margins above 80%",{"id":1283,"title":1284,"titles":1285,"content":422,"level":374},"\u002Fcancel-flows\u002Fadaptive-offers#advanced-configuration","Advanced Configuration",[64],{"id":1287,"title":1288,"titles":1289,"content":1290,"level":417},"\u002Fcancel-flows\u002Fadaptive-offers#annual-customer-discounts","Annual Customer Discounts",[64,1284],"Annual subscribers typically demonstrate different price sensitivity patterns and represent significantly higher customer lifetime value. When enabled, customers with 12+ month billing intervals will only see 12-month discount offers, ensuring the discount duration aligns with their billing cycle. Enable Annual Customer Discounts in the advanced settings panel to activate this behavior. The system automatically applies a 12-month duration while still optimizing the discount percentage within your configured range.",{"id":1292,"title":1293,"titles":1294,"content":1295,"level":417},"\u002Fcancel-flows\u002Fadaptive-offers#multi-month-customer-discounts","Multi-Month Customer Discounts",[64,1284],"When enabled, customers with 2-11 month billing intervals will only see discounts matching their exact billing interval. This ensures discount duration synchronization with existing customer billing cycles, creating seamless renewal experiences. Enable Multi-Month Customer Discounts in the advanced settings panel to activate this behavior. The system automatically matches the discount duration to each customer's individual billing interval.",{"id":1297,"title":1298,"titles":1299,"content":1300,"level":374},"\u002Fcancel-flows\u002Fadaptive-offers#metrics","Metrics",[64],"Once Adaptive Offers is enabled and customers begin interacting with your Cancel Flow, performance data becomes available in the Adaptive Offers Performance section on the Analytics page under Cancel Flows.",{"id":1302,"title":1303,"titles":1304,"content":1305,"level":417},"\u002Fcancel-flows\u002Fadaptive-offers#key-performance-indicators","Key Performance Indicators",[64,1298],"Offers Presented: Total number of Cancel Flow sessions where Adaptive Offers were shown, along with the count of accepted offersSave Rate: The percentage of customers who accepted the optimized discount. This is shown alongside the equivalent fixed-rate baseline (e.g. \"vs 4.8% fixed rate\") so you can directly measure how much Adaptive Offers improves over a static discount strategyValue Boost Uplift: The revenue improvement percentage that Adaptive Offers achieves compared to a fixed discount strategy. Click Learn More for a detailed breakdown of how this metric is calculated",{"id":1307,"title":1308,"titles":1309,"content":1310,"level":417},"\u002Fcancel-flows\u002Fadaptive-offers#offer-acceptance-rate","Offer Acceptance Rate",[64,1298],"The heatmap visualizes how different discount combinations perform across two dimensions — discount percentage (x-axis) and duration in months (y-axis). Toggle between three views: Acceptance Rate: The percentage of customers who accepted each offer combinationProfit Boost: Revenue impact after discount, based on your optimization strategyTimes Presented: How frequently each combination was offered to customers Higher-performing combinations appear in darker shades, making it easy to identify your most effective discount strategies at a glance. Faded text in the heatmap indicates an offer combination that is no longer active — for example, if you narrowed your discount range after the system had already tested wider combinations.",{"id":1312,"title":1313,"titles":1314,"content":1315,"level":417},"\u002Fcancel-flows\u002Fadaptive-offers#top-segments","Top Segments",[64,1298],"The Top Segments table breaks down Adaptive Offers performance by customer segment, based on billing interval and plan type. Each row shows the segment name along with the number of sessions observed. For each segment, you can see: Save Rate compared to the fixed-rate baseline (shown as \"vs X.X%\")Value Boost Uplift showing the revenue improvement percentageBest Offers For Segment — the top-performing discount combinations and their selection rates, so you can understand which offers resonate most with each customer group",{"id":1317,"title":1318,"titles":1319,"content":422,"level":374},"\u002Fcancel-flows\u002Fadaptive-offers#a-2-month-implementation-strategy","A 2-Month Implementation Strategy",[64],{"id":1321,"title":1322,"titles":1323,"content":1324,"level":417},"\u002Fcancel-flows\u002Fadaptive-offers#phase-1-initial-configuration-week-1","Phase 1: Initial Configuration (Week 1)",[64,1318],"Step 1: Establish Conservative Boundaries\nBegin with narrower discount ranges to minimize risk while gathering initial performance data. Configure percentage ranges that maintain healthy profit margins and align with your existing retention budget. Step 2: Validate Technical Integration\nEnsure your Cancel Flow blueprint includes discount offers and verify Adaptive Offers is properly enabled. Confirm automatic coupon creation is functioning correctly in your payment provider.",{"id":1326,"title":1327,"titles":1328,"content":1329,"level":417},"\u002Fcancel-flows\u002Fadaptive-offers#phase-2-data-collection-period-weeks-2-3","Phase 2: Data Collection Period (Weeks 2-3)",[64,1318],"Step 3: Monitor Initial Performance\nThe system initiates comprehensive data collection across all configured discount combinations. During this phase, discount selection may appear random as the AI builds its foundational understanding of customer response patterns. Step 4: Avoid Premature Adjustments\nResist the temptation to modify configuration settings during the initial learning period. Premature changes can disrupt the optimization algorithm and extend the learning timeline.",{"id":1331,"title":1332,"titles":1333,"content":1334,"level":417},"\u002Fcancel-flows\u002Fadaptive-offers#phase-3-optimization-emergence-weeks-4-8","Phase 3: Optimization Emergence (Weeks 4-8)",[64,1318],"Step 5: Observe Pattern Development\nThe AI begins favoring higher-performing discount combinations while maintaining exploration of underutilized options. Performance patterns become increasingly evident as statistical significance develops. Step 6: Document Performance Trends\nTrack key metrics including save rates, revenue impact, and discount utilization patterns. Focus on overall trend analysis rather than daily fluctuations.",{"id":1336,"title":1337,"titles":1338,"content":1339,"level":417},"\u002Fcancel-flows\u002Fadaptive-offers#phase-4-ongoing-optimization-months-2","Phase 4: Ongoing Optimization (Months 2+)",[64,1318],"Step 7: Implement Monthly Reviews\nEstablish regular performance assessments to evaluate optimization effectiveness and identify opportunities for range adjustments based on business requirements or market changes. Step 8: Strategic Range Expansion\nBased on proven performance data, consider expanding discount ranges to capture additional optimization opportunities while maintaining profitability thresholds.",{"id":1341,"title":269,"titles":1342,"content":1343,"level":374},"\u002Fcancel-flows\u002Fadaptive-offers#frequently-asked-questions",[64],"What are the minimum traffic requirements for effective optimization?\nOur models are constantly improving and adapting - what works best in winter may not be best in summer, so the AI continuously adjusts to seasonal patterns and changing customer behavior. You'll typically see initial optimization within a week (after at least 20 sessions) with the model becoming increasingly effective over 4-6 weeks as it learns your unique retention patterns. What is the expected timeline for measurable results?\nInitial performance improvements typically manifest within 2-3 weeks of implementation, with substantial optimization occurring by 6-8 weeks. Timeline variation depends on traffic volume and customer diversity. How does configuration modification affect ongoing optimization?\nDiscount range adjustments can be implemented at any time. However, as a good practice, we recommend changes to be done at least after a 2-4 week stabilization period. What happens if a customer sees the discount offer multiple times?\nOffer consistency is designed to maintain customer experience integrity and maximize conversion effectiveness. Once the system identifies optimal discount parameters for specific customer profiles, it maintains consistent presentation to ensure reliable and trustworthy interactions.",{"id":74,"title":73,"titles":1345,"content":1346,"level":368},[],"Measure the long-term value of retained subscribers Boosted Revenue measures the long-term success of your retention efforts by tracking all revenue generated after a customer initiates a Churnkey Cancel Flow but chooses to stay subscribed. This metric helps you evaluate whether your retention strategies create lasting customer relationships, not just short-term saves.",{"id":1348,"title":1349,"titles":1350,"content":1351,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Faverage-boosted-revenue#why-it-matters","Why It Matters",[73],"While retention rates show how many customers you keep, Boosted Revenue reveals whether these customers remain valuable, active subscribers. A high retention rate with low Boosted Revenue could indicate that your offers are attractive initially but don't address the underlying reasons customers want to cancel. For example: A 90% discount might result in high retention rates but low Boosted Revenue if customers cancel when regular pricing resumesA pause offer followed by steady payments indicates you've helped customers through a temporary situationConsistent payments after a modest discount suggest you've found the right price point for long-term retention",{"id":1353,"title":1354,"titles":1355,"content":1356,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Faverage-boosted-revenue#how-boosted-revenue-works","How Boosted Revenue Works",[73],"When a customer initiates a Churnkey Cancel Flow but continues their subscription, either by accepting an offer or exiting the flow, their subsequent payments become Boosted Revenue. This represents sustainable revenue that would have been lost without effective retention strategies.",{"id":1358,"title":1359,"titles":1360,"content":1361,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Faverage-boosted-revenue#calculation","Calculation",[73],"Boosted Revenue begins with the first payment after a customer engages with the Churnkey Cancel Flow: Boosted Revenue = Sum of all payments processed after retention Importantly, Boosted Revenue is calculated using actual paid invoices, not projected or expected revenue. This ensures the metric accurately reflects real business impact and aligns with your long-term revenue goals. Only successful payments contribute to the Boosted Revenue total, providing an accurate measure of retention effectiveness.",{"id":1363,"title":1364,"titles":1365,"content":422,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Faverage-boosted-revenue#example-scenarios","Example Scenarios",[73,1359],{"id":1367,"title":1368,"titles":1369,"content":1370,"level":958},"\u002Fcancel-flows\u002Fanalytics\u002Faverage-boosted-revenue#successful-long-term-retention-pause","Successful Long-term Retention (Pause)",[73,1359,1364],"A customer has a $50\u002Fmonth subscription: January 1: Regular payment ($50)January 15: Customer initiates Churnkey Cancel FlowJanuary 15: Customer accepts a 2-month pauseMarch 15: The subscription resumesMarch 15: Payment processed ($50)April 15: Payment processed ($50)May 15: Payment processed ($50) Their Boosted Revenue is $150, indicating successful re-engagement after addressing their temporary need to pause.",{"id":1372,"title":1373,"titles":1374,"content":1375,"level":958},"\u002Fcancel-flows\u002Fanalytics\u002Faverage-boosted-revenue#strategic-discount-retention","Strategic Discount Retention",[73,1359,1364],"A customer has a $100\u002Fmonth subscription: March 1: Regular payment ($100)March 20: Customer initiates Churnkey Cancel FlowMarch 20: Customer accepts 20% discount for 3 monthsApril 1: The payment processed ($80)May 1: Payment processed ($80)June 1: Payment processed ($80)July 1: Regular price resumes ($100)August 1: Payment processed ($100)September 1: Payment processed ($100) Their Boosted Revenue of $540 shows the discount successfully retained a valuable long-term customer.",{"id":1377,"title":1378,"titles":1379,"content":1380,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Faverage-boosted-revenue#average-boosted-revenue","Average Boosted Revenue",[73],"Average Boosted Revenue measures the typical revenue generated by successfully reactivated customers. A customer is considered reactivated when they: Initiate a Churnkey Cancel FlowChoose to continue their subscription (via offer acceptance or flow exit)Pay at least one invoice after going through the flow The average is calculated only from these reactivated customers: Average Boosted Revenue = Total Boosted Revenue ÷ Number of Reactivated Customers For example, if 100 customers go through your Cancel Flow: 60 customers cancel40 customers choose to stay30 customers pay at least one more invoice (reactivated)These 30 customers generate $15,000 in Boosted Revenue The Average Boosted Revenue would be $500 ($15,000 ÷ 30 reactivated customers). More about Reactivation Rates ->",{"id":1382,"title":1383,"titles":1384,"content":1385,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Faverage-boosted-revenue#revenue-categories","Revenue Categories",[73],"The dashboard segments Boosted Revenue to help you evaluate different retention strategies:",{"id":1387,"title":1388,"titles":1389,"content":1390,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Faverage-boosted-revenue#offer-based-retention","Offer-Based Retention",[73,1383],"Tracks long-term revenue from customers who accepted specific retention offers: Discount offers (strategic price adjustments)Pause offers (temporary subscription suspension)Other custom offers (plan changes, billing frequency adjustments, etc.)",{"id":1392,"title":1393,"titles":1394,"content":1395,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Faverage-boosted-revenue#flow-exit-retention","Flow Exit Retention",[73,1383],"Measures revenue from customers who exit the Churnkey Cancel Flow without canceling or accepting an offer, indicating the flow itself helped address their concerns.",{"id":1397,"title":269,"titles":1398,"content":422,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Faverage-boosted-revenue#frequently-asked-questions",[73],{"id":1400,"title":1401,"titles":1402,"content":1403,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Faverage-boosted-revenue#how-do-i-calculate-monthly-roi-from-saved-customers","How do I calculate monthly ROI from saved customers?",[73,269],"The Boosted Revenue chart displays the total recurring revenue from retained customers for each specific month. Each bar represents the Boosted Revenue generated in that particular month from all customers who were previously saved through your Cancel Flows. Here's how to interpret and calculate ROI from the chart: When you view the Boosted Revenue chart in your Analytics dashboard, each month's bar shows the revenue earned that month from your retention efforts. To calculate your monthly ROI, simply divide the Boosted Revenue by your Churnkey subscription cost. Example ROI Calculation: Let's say your Boosted Revenue chart shows and Churnkey costs $2,500\u002Fmonth: October: $87,000November: $94,000December: $99,000 Your monthly ROI would be: October ROI: $87,000 ÷ $2,500 = 34.8xNovember ROI: $94,000 ÷ $2,500 = 37.6xDecember ROI: $99,000 ÷ $2,500 = 39.6x This means that for every dollar you spend on Churnkey, you're generating approximately $35-40 in retained revenue each month. This example is based on real data from an actual Churnkey customer. Important distinction: Boosted Revenue tracks all recurring payments from customers who were retained through Churnkey Cancel Flows. This is purely recurring revenue—it only counts actual paid invoices, not one-time payments or projected revenue. This makes it ideal for understanding the ongoing value of your retention efforts. Why this metric matters: As your retention program matures, you'll typically see Boosted Revenue grow month-over-month as more customers are saved and continue paying. This demonstrates the compounding value of effective retention strategies over time.",{"id":78,"title":77,"titles":1405,"content":1406,"level":368},[],"The percentage of customers that your Cancel Flows saved from cancellation When customers starts a Cancel Flow, there are two categories of results. They'll either cancel their subscription or keep it by accepting an offer or by abandoning the flow. Customers who went through the flow without canceling their subscription are grouped in the Saved Customers and the others in the Cancelled. The Save Rate shows the percentage of sessions that ended up as Saved compared to all of your valid sessions. Note that the Bounced Sessions are not counted in the calculation of the Save Rate.",{"id":1408,"title":1349,"titles":1409,"content":1410,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fsave-rate#why-it-matters",[77],"The Save Rate will show you the effectiveness of your flows in avoiding cancellations. Use it to have a first understanding of the performance of your flows.",{"id":1412,"title":1359,"titles":1413,"content":1414,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fsave-rate#calculation",[77],"The Save Rate is the percentage of sessions that did not end in cancellation over all the valid sessions that occurred. Save Rate = (Amount of sessions that did not end on cancellation) \u002F (Total of Valid Sessions)",{"id":1416,"title":1417,"titles":1418,"content":1419,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fsave-rate#examples-of-calculation","Examples of Calculation",[77,1359],"Consider an example company that had 400 sessions in the last 7 days. Here is the breakdown of their sessions: 200 ended in cancellation50 pauses accepted50 discounts applied10 bounced sessions90 abandoned sessions Their Save Rate will be calculated as: Save Rate  = ( All sessions that did not end up in cancellation ) \u002F ( Total amount of valid sessions )  Save Rate = ( 50 Pause Accepted + 50 Discount Applied + 90 Abandoned Sessions ) \u002F ( 400 Total Sessions - 10 Bounced Sessions )  Save Rate = ( 190 ) \u002F ( 390 ) = 48.71%",{"id":82,"title":81,"titles":1421,"content":1422,"level":368},[],"The percentage of customers who pay at least one invoice after being saved \"Reactivation Rate\" is the percentage of \"Saved Customers\" who paid at least one invoice after exiting your Cancel Flow.",{"id":1424,"title":1349,"titles":1425,"content":1426,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Freactivation-rate#why-it-matters",[81],"The ultimate goal is to make customers stay longer using the products and services they love. Avoiding cancellation is the first step to that goal—but the real impact to your business comes when they generate more revenue after a Cancel Flow session. Every paid invoice after avoiding a cancellation means that the Customer's Lifetime Value (LTV) is increasing. The Reactivation Rate helps you understand how effective your Cancel Flows are at increasing your LTV.",{"id":1428,"title":465,"titles":1429,"content":1430,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Freactivation-rate#how-it-works",[81],"Churnkey will keep track of every Saved Customer. Whenever they pay their first invoice after being saved, they'll be counted as Reactivated. Consider a customer billed on the 10th of January as an example: On January 15th, they start the Cancel FlowThe customer accepts a discount of 50% for one monthThe Save Rate is increasedOn February 10th, the customer successfully pays an invoice with their 50% discountThe Reactivation Rate is increased If for any reason the customer opted to cancel their subscription before February 10th, Churnkey will be notified by your payment provider and we will not count this customer as Reactivated.",{"id":1432,"title":1433,"titles":1434,"content":1435,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Freactivation-rate#example-of-calculation","Example of Calculation",[81],"Consider a company with the following statistics: Total Sessions: 100Save Rate: 50%Customers that generated their first invoice after being saved: 20 Reactivation Rate = ( Customers that generated 1st invoice ) \u002F ( Saved Customers ) Reactivation Rate = ( 20 ) \u002F ( 50% * 100 ) Reactivation Rate = ( 20 ) \u002F ( 50 ) = 40%",{"id":86,"title":85,"titles":1437,"content":1438,"level":368},[],"Track each interaction that a customer has with your Cancel Flows Sessions track every customer's interaction with your Cancel Flow, from initial trigger to final decision. Sessions are the primary data structure Churnkey uses to track, analyze, and report on customer retention efforts.",{"id":1440,"title":1205,"titles":1441,"content":1442,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fsession-outcomes#how-it-works",[85],"Whenever a customer starts the Cancel Flow, a session will be created to store the customer's information and their interactions, for example, their Survey Cancellation response. The result of a session, called Session Outcomes, will be used to generate metrics such as the Save Rate, Offer Performance, and help in understanding cancellation reasons.",{"id":1444,"title":1445,"titles":1446,"content":1447,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fsession-outcomes#where-to-find","Where to find",[85],"By the bottom of the Cancel Flow's Analytics page, you'll find the Activity Stream. Within it, every session will have its own record with: Customer email  To help you identify who this session belongs to.Subscription Information  Such as Start Date and Price.Session's Date  The exact time in the customer's timezone when the session occurred.Cancellation Survey Response  To allow you to understand why your customers are leaving.Customer's Feedback  To help you connect with their true feelings about your product. If the customer does not reach the Cancellation Survey or the Freeform Feedback, their session won't have any of this data.",{"id":1449,"title":1450,"titles":1451,"content":1452,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fsession-outcomes#session-outcomes","Session Outcomes",[85],"The Session Outcome is the customer's resulting action of a session. As mentioned in the Save Rate, the outcomes can be separated into two major groups: Cancelled and Saved. The Saved Customers are distributed in more detailed results as Abandoned, Paused, Discount, Contact Support, and Redirected.",{"id":1454,"title":1455,"titles":1456,"content":1457,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fsession-outcomes#cancelled","Cancelled",[85,1450],"The customer went through all the steps of the flow and opted to cancel their subscription",{"id":1459,"title":1460,"titles":1461,"content":1462,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fsession-outcomes#abandoned","Abandoned",[85,1450],"The customer started the Cancel Flow but closed it without taking any action.",{"id":1464,"title":1465,"titles":1466,"content":1467,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fsession-outcomes#paused","Paused",[85,1450],"The customer accepted to pause their subscription for a certain period.",{"id":1469,"title":124,"titles":1470,"content":1471,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fsession-outcomes#discount",[85,1450],"The customer accepted a coupon offer.",{"id":1473,"title":1474,"titles":1475,"content":1476,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fsession-outcomes#contact-support","Contact Support",[85,1450],"It's mandatory to have the handleSupportRequest integration. Your implemented support tool will be called once the customer clicks the button.",{"id":1478,"title":1479,"titles":1480,"content":1481,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fsession-outcomes#redirected","Redirected",[85,1450],"This is the result of \"Send to Custom Page\". After clicking the button, the customer will be redirected within the same browser's tab to the configured link.",{"id":1483,"title":1484,"titles":1485,"content":1486,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fsession-outcomes#bounced-sessions","Bounced Sessions",[85],"Customers may start multiple Cancel Flows in a short period of time. With multiple sessions, Churnkey only takes into consideration the most relevant session for the metrics. Sessions that are not relevant are tagged as Bounced and are not considered in your metrics. The timeframe to mark a session as bounced is 24 hours. This means that the difference between the bounced and most relevant session can be a maximum of 24h.",{"id":1488,"title":1489,"titles":1490,"content":1491,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fsession-outcomes#example-of-a-bounced-session","Example of a Bounced Session",[85,1484],"Consider a customer with an active subscription who created two sessions within a timeframe of 5 minutes: Started a Cancel Flow at 9:00 AMThe customer did not take any action and closed the Cancel FlowSession recorded and marked as AbandonedThe Abandoned Session increases the Save RateAfter 5 minutes at 9:05 AM, the customer starts the Cancel Flow againNow, they opt to Cancel their subscriptionNew session is generated with Cancelled as the outcomePrevious Abandoned session is marked as BouncedThe Save Rate decreases because of the Cancelled Session The customer generated an Abandoned Session by step 2. Until this moment, we counted this session as a Save in the Save Rate. When they opted to Cancel by step 4, we marked their previous session as Bounced and removed it from the metrics. From here, we only consider the Cancelled Session for the Metrics.",{"id":90,"title":89,"titles":1493,"content":1494,"level":368},[],"Analyze and interpret the selection rates and trends for cancellation reasons in your Cancel Flows. The Cancellation Trends dashboard provides comprehensive insights into why customers cancel their subscriptions, helping you analyze selection rates for each cancellation reason, compare segments, and monitor changes over time.",{"id":1496,"title":1349,"titles":1497,"content":1498,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fcancellation-trends#why-it-matters",[89],"Cancellation data reveals critical insights into customer motivations and pain points, enabling strategic business decisions that directly impact retention and growth. For instance, if the cancellation reason \"Budget\" consistently accounts for a significant portion of cancellations, it likely indicates customers are struggling to see sufficient value relative to your pricing. This presents an opportunity to clearly communicate and reinforce your product's value by highlighting key features, benefits, and successes in your marketing and onboarding materials. Alternatively, if cancellations due to \"Missing Features\" are prominent, this indicates a gap between customer needs and your current offerings. Leveraging this insight, you can prioritize product enhancements and development efforts around the features most requested by your customers. Acting on this feedback directly addresses customer concerns, reduces churn, and ensures your product evolves in alignment with customer expectations.",{"id":1500,"title":465,"titles":1501,"content":1502,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fcancellation-trends#how-it-works",[89],"For each cancellation reason displayed in the Cancellation Survey (e.g., \"Too expensive\", \"Missing features\"), Churnkey tracks how frequently customers select each option relative to how often it was presented. Unlike traditional distributions where all options share the same denominator, each cancellation reason's selection rate is calculated exclusively from sessions where that specific reason was displayed. Important: The number of times a reason is displayed can vary significantly, especially when you make changes to your cancel flow. Common scenarios include: Adding or removing cancellation options from your surveyA\u002FB testing different reason sets for different customer segments Each percentage therefore represents the selection rate within that reason's unique set of presentations - not a universal comparison across all reasons.",{"id":1504,"title":1359,"titles":1505,"content":1506,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fcancellation-trends#calculation",[89],"Overall = (Selected ÷ Displayed) × 100 Displayed: The number of times a reason was presented to customers in the cancellation flow.Selected: The number of times customers actually chose that reason.Overall: The percentage of times the reason was selected out of the times it appeared.",{"id":1508,"title":1509,"titles":1510,"content":1511,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fcancellation-trends#example-calculation","Example Calculation",[89,1359],"Let's walk through a real scenario:",{"id":1513,"title":1514,"titles":1515,"content":1516,"level":958},"\u002Fcancel-flows\u002Fanalytics\u002Fcancellation-trends#first-version-of-the-flow","First Version of the Flow",[89,1359,1509],"100 sessions collected, with two options: ReasonTimes DisplayedTimes SelectedOverall (%)Budget1007070%Missing Features1003030%",{"id":1518,"title":1519,"titles":1520,"content":1521,"level":958},"\u002Fcancel-flows\u002Fanalytics\u002Fcancellation-trends#second-version-of-the-flow","Second Version of the Flow",[89,1359,1509],"Another 100 sessions, with a new option: ReasonTimes DisplayedTimes SelectedOverall (%)Budget1005050%Other1005050%",{"id":1523,"title":1524,"titles":1525,"content":1526,"level":958},"\u002Fcancel-flows\u002Fanalytics\u002Fcancellation-trends#combined-results-as-shown-in-the-dashboard","Combined Results (as shown in the dashboard)",[89,1359,1509],"ReasonTotal Times DisplayedTotal Times SelectedOverall (%)Budget20012060%Missing Features1003030%Other1005050% Each percentage is calculated only for the sessions where that reason was shown. The \"Overall\" percentages are not intended to be summed, as each represents a unique base of presentations.",{"id":1528,"title":1529,"titles":1530,"content":422,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fcancellation-trends#using-the-cancellation-trends-dashboard","Using the Cancellation Trends Dashboard",[89],{"id":1532,"title":1533,"titles":1534,"content":1535,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fcancellation-trends#select-segment","Select Segment",[89,1529],"You can filter the data by customer segment using the Select Segment dropdown. This allows you to analyze cancellation reasons based on each cancel flow. Selecting a segment updates all displayed metrics and trends to reflect only that group.",{"id":1537,"title":1538,"titles":1539,"content":1540,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fcancellation-trends#compare-segments","Compare Segments",[89,1529],"You can hover over any cancellation reason on the right to see how it compares across different segments. When the Primary Flow is selected:\nHovering will show a tooltip that compares how often that reason was selected in the Primary Flow versus all other segments combined. This helps you understand if certain reasons are more common in your main cancellation path.When another segment is selected:\nThe tooltip will compare that segment’s results to the Primary Flow, highlighting differences in cancellation behavior between customer groups. This makes it easier to spot trends and tailor your cancellation experience based on what each segment is telling you. Example: The dashboard shows how cancellation reasons and their trends compare across segments. Hovering over a reason displays a tooltip with detailed comparison.",{"id":1542,"title":1543,"titles":1544,"content":1545,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fcancellation-trends#trend-last-30-days","Trend Last 30 Days",[89,1529],"For each reason, the interface displays the trend (increase or decrease) in selection rate over the last 30 days compared to the previous period. This helps you quickly identify which reasons are becoming more or less common. To see the \"Trend Last 30 Days\" metric, you must select a date range of at least 30 days. If the selected period is shorter, this trend will not be shown or may be inaccurate.",{"id":94,"title":93,"titles":1547,"content":1548,"level":368},[],"Transform customer feedback into actionable insights with AI-powered categorization and analysis",{"id":1550,"title":5,"titles":1551,"content":1552,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#overview",[93],"Feedback AI transforms the freeform customer feedback collected during your Cancel Flows into structured, actionable insights. Instead of manually reading through hundreds of individual responses, the AI automatically categorizes feedback into meaningful themes, helping you identify the most pressing issues driving customer churn and the opportunities with the greatest impact on retention. Customer feedback from cancel flows contains valuable intelligence about your product, service, and customer experience. However, analyzing this feedback at scale becomes challenging as your business grows. A single month might generate hundreds of responses, making it difficult to identify patterns and prioritize improvements. Feedback AI solves this challenge by automatically processing and categorizing all customer feedback, revealing trends that might otherwise remain hidden. This analysis helps you understand not just what customers are saying, but what they're not saying, and more importantly, which issues represent the highest revenue impact. For example, you might discover that \"missing feature\" requests represent 30% of your churned MRR, while \"pricing concerns\" only account for 5%. This insight allows you to prioritize product development efforts where they'll have the most significant impact on reducing churn and increasing customer lifetime value.",{"id":1554,"title":1555,"titles":1556,"content":1557,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#how-feedback-ai-works","How Feedback AI Works",[93],"Feedback AI automatically processes freeform customer feedback from your Cancel Flows using natural language understanding. The system categorizes responses into seven core themes that capture the most common reasons customers consider canceling: Low Usage: Customers who aren't getting enough value from your productMissing Feature: Requests for functionality that doesn't existCompetitor Switch: Customers moving to alternative solutionsUsability Issue: Problems with user experience or interfacePricing Concern: Feedback about cost, value, or billingCustomer Service Issues: Problems with support or service qualityTechnical Issues: Bugs, performance problems, or system failures Each piece of feedback is analyzed and assigned to the most relevant category, creating a comprehensive view of what's driving churn in your business.",{"id":1559,"title":149,"titles":1560,"content":1561,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#getting-started",[93],"To access Feedback AI: Navigate to Cancel Flows in the left menuClick Analytics in the top menuScroll down to the Feedback AI section The feature automatically analyzes all freeform feedback collected from your Cancel Flows, so no additional setup is required. When you first visit Feedback AI, you'll see the main dashboard displaying category trends for the past six months. If you don't see any data immediately, it means customers haven't yet provided freeform feedback that can be analyzed. Check back as feedback is collected through your Cancel Flows.",{"id":1563,"title":1564,"titles":1565,"content":1566,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#how-to-analyze-your-feedbacks","How to Analyze Your Feedbacks",[93],"The Feedback AI dashboard provides three main ways to explore your customer feedback: category analysis, search functionality, and date filtering. Here's how each feature works:",{"id":1568,"title":1569,"titles":1570,"content":1571,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#understanding-categories","Understanding Categories",[93,1564],"The main dashboard displays your feedback organized into the seven predefined categories. Each category shows three key metrics: Lost MRR: Total monthly recurring revenue from customers in this categoryResponse Count: Number of feedback responses in this categoryPercentage of Total MRR: Visual representation of this category's impact For example, you might see that \"Missing Feature\" represents $15,000 in lost MRR across 45 responses, accounting for 35% of your feedback-related churn.",{"id":1573,"title":1574,"titles":1575,"content":1576,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#exploring-individual-feedback","Exploring Individual Feedback",[93,1564],"Click on any category to open a detailed view where you can: Read individual responses: See the actual customer feedback to understand specific concernsView trends over time: See how feedback in each category has changedTrack MRR impact: Monitor revenue changes associated with each feedback themeExport data: Download category information for further analysis",{"id":1578,"title":1579,"titles":1580,"content":1581,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#searching-for-specific-content","Searching for Specific Content",[93,1564],"Use the search functionality to find feedback that doesn't fit the standard categories. Search for specific terms, phrases, or concepts to understand how customers talk about particular aspects of your product or service. Search results display the same metrics as category analysis (MRR impact and response count), making it useful for investigating emerging themes or analyzing feedback related to recent changes.",{"id":1583,"title":1584,"titles":1585,"content":1586,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#filtering-by-date-range","Filtering by Date Range",[93,1564],"All Feedback AI analysis includes date range filtering. This allows you to focus on specific time periods and understand how feedback patterns change following product updates, pricing changes, or other business initiatives.",{"id":1588,"title":1589,"titles":1590,"content":1591,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#best-practices-for-using-feedback-ai","Best Practices for Using Feedback AI",[93],"To get the most value from Feedback AI, follow these best practices for analyzing your data, taking action, and measuring your impact.",{"id":1593,"title":1594,"titles":1595,"content":1596,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#interpreting-your-data","Interpreting Your Data",[93,1589],"Focus on Revenue Impact: The most powerful aspect of Feedback AI is its ability to prioritize based on actual revenue impact. Pay attention to both MRR impact and response frequency when analyzing categories. High MRR, Low Responses: Indicates issues affecting fewer but higher-value customersLow MRR, High Responses: Suggests problems affecting a broader base of smaller customersHigh MRR, High Responses: Represents your highest-priority issues requiring immediate attention Track Trends Over Time: Look for patterns in your feedback data. New spikes in \"Technical Issues\" might indicate recent bugs, while increasing \"Missing Feature\" requests could signal growing customer sophistication or competitive pressure. Combine Analysis with Individual Review: Use the category breakdown to identify high-impact issues, then dive into specific responses to understand exactly what customers are experiencing and what solutions they're seeking.",{"id":1598,"title":1599,"titles":1600,"content":1601,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#taking-action-on-insights","Taking Action on Insights",[93,1589],"Prioritize by Revenue Impact: Use category analysis to prioritize your product roadmap, support training, and retention campaigns based on actual revenue impact rather than just response volume. Examples of Strategic Actions: If \"Missing Feature\" represents your largest category, prioritize developing the most requested functionalityIf \"Customer Service Issues\" shows significant MRR impact, invest in additional support training or expanded service hoursIf \"Pricing Concerns\" are growing, review your pricing strategy or value proposition Export and Share Data: Export category data and search results to share insights with your team, track progress over time, and integrate feedback analysis into broader business planning.",{"id":1603,"title":1604,"titles":1605,"content":1606,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#establishing-effective-workflows","Establishing Effective Workflows",[93,1589],"Set Regular Review Schedule: Establish a consistent schedule for reviewing Feedback AI insights, such as monthly or quarterly business reviews. This ensures you're consistently acting on customer feedback and can track the impact of your retention improvements. Collaborate Across Teams: Share Feedback AI insights with relevant teams across your organization: Product teams: Need to understand feature requests and usability issuesCustomer success teams: Should know about service issues and support needsMarketing teams: Can benefit from understanding competitive threats and pricing concerns Measure Improvement Impact: Use date filtering to measure how your retention efforts impact feedback patterns. After implementing improvements to address specific issues, monitor whether those categories show reduced MRR impact and response frequency over time.",{"id":1608,"title":470,"titles":1609,"content":422,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#faq",[93],{"id":1611,"title":1612,"titles":1613,"content":1614,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#how-often-is-feedback-processed-and-updated","How often is feedback processed and updated?",[93,470],"Feedback AI processes all customer feedback once daily in batch updates.",{"id":1616,"title":1617,"titles":1618,"content":1619,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#can-the-ai-categorization-be-wrong","Can the AI categorization be wrong?",[93,470],"While the AI categorization is highly accurate, it's trained on common feedback patterns and might occasionally miscategorize unusual or ambiguous responses. The system is designed to err on the side of providing useful insights rather than perfect accuracy, and you can always use the search functionality to find specific types of feedback that might not fit standard categories.",{"id":1621,"title":1622,"titles":1623,"content":1624,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#what-if-customers-provide-feedback-that-doesnt-fit-the-standard-categories","What if customers provide feedback that doesn't fit the standard categories?",[93,470],"The AI system continuously learns from new feedback patterns and may identify emerging themes that don't fit the current seven categories. Use the search functionality to explore feedback that seems unique or doesn't appear in the main categories. This can help you identify emerging issues or opportunities before they become major themes.",{"id":1626,"title":1627,"titles":1628,"content":1629,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#how-much-feedback-is-needed-for-meaningful-insights","How much feedback is needed for meaningful insights?",[93,470],"Feedback AI can provide insights with as few as a dozen responses, but the analysis becomes more meaningful and statistically significant with larger datasets. The system displays confidence levels and sample sizes to help you understand the reliability of your insights.",{"id":1631,"title":1632,"titles":1633,"content":1634,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#does-feedback-ai-take-into-consideration-empty-feedback","Does Feedback AI take into consideration empty feedback?",[93,470],"No, we discard empty feedback responses in the analysis. We recommend adding a minimum character requirement to freeform feedback fields to increase the likelihood of collecting meaningful responses for analysis.",{"id":1636,"title":1637,"titles":1638,"content":1639,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Ffeedback-ai#can-i-see-feedback-in-languages-other-than-english","Can I see feedback in languages other than English?",[93,470],"Yes, Feedback AI automatically processes and categorizes customer feedback in all languages. The system analyzes the content regardless of the language used, ensuring you get comprehensive insights from your global customer base.",{"id":98,"title":97,"titles":1641,"content":1642,"level":368},[],"Visualize how customers move from primary cancellation reasons to specific follow-up selections with the Sankey diagram",{"id":1644,"title":5,"titles":1645,"content":1646,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-flow#overview",[97],"The Response Flow is a Sankey diagram that shows the paths customers take through your Structured Follow-Up Questions. The left column represents primary survey choices. The right column represents the follow-up options selected within each primary choice. Colored flows connect the two, with the width of each flow proportional to the number of customer responses along that path. This visualization answers a question that raw numbers alone cannot: where exactly are customers going after they select a primary reason, and how do those paths compare? Instead of reading a table of counts, you see the distribution at a glance. If 70% of \"Too Expensive\" respondents flow into \"Not seeing enough ROI\", that pattern is immediately visible as a dominant band in the diagram.",{"id":1648,"title":1649,"titles":1650,"content":1651,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-flow#where-to-find-it","Where to Find It",[97],"Navigate to Cancel Flows in your Churnkey dashboardClick Analytics in the top navigationScroll to the Response Flow section The diagram appears automatically when you have Structured Follow-Up Questions configured and customers have submitted responses. If no follow-up responses exist yet, this section will not display data.",{"id":1653,"title":1654,"titles":1655,"content":1656,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-flow#reading-the-diagram","Reading the Diagram",[97],"Each primary survey choice on the left is represented as a labeled block. Each follow-up option on the right is a separate block. The flows between them are color-coded to match the primary survey choice they originate from, making it easy to trace which follow-up responses belong to which primary reason. Width indicates volume. A wider flow means more customers selected that particular path. A narrow flow means fewer did. This lets you quickly identify the dominant sub-reasons within each primary category without reading exact numbers. Hover for details. Moving your cursor over any flow reveals a tooltip with three data points: Response count: the absolute number of customers who followed that path. Percentage: the share of total responses that this path represents. MRR: the total monthly recurring revenue of the customers who followed that path. This is the metric that connects survey feedback to financial impact.",{"id":1658,"title":1659,"titles":1660,"content":422,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-flow#using-the-response-flow-effectively","Using the Response Flow Effectively",[97],{"id":1662,"title":1663,"titles":1664,"content":1665,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-flow#identifying-dominant-paths","Identifying Dominant Paths",[97,1659],"Start by looking at the widest flows. These represent the most common customer journeys through your survey and follow-up questions. If one follow-up option within a primary category dominates, it tells you that the primary reason is not as general as it seems. Most customers mean something very specific. For example, if your \"Missing Features\" primary choice has four follow-up options but 65% of customers select \"Need a reporting dashboard\", then \"Missing Features\" is effectively a proxy for one particular feature request. This insight should influence both your product roadmap and the offer you associate with that follow-up option.",{"id":1667,"title":1668,"titles":1669,"content":1670,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-flow#comparing-across-primary-choices","Comparing Across Primary Choices",[97,1659],"The diagram lets you compare the distribution of follow-up responses across different primary choices. Some primary choices might fan out evenly across their follow-up options, indicating a genuinely diverse set of sub-reasons. Others might concentrate heavily into one or two follow-ups, indicating that the primary choice has a dominant underlying cause. Primary choices with even distributions benefit from the Freeform + Structured response mode, because the customer's true reason might not fit neatly into any single follow-up option. Primary choices with concentrated distributions benefit from targeted offers on the dominant follow-up option, because the investment will reach the largest share of respondents.",{"id":1672,"title":1673,"titles":1674,"content":1675,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-flow#tracking-revenue-exposure","Tracking Revenue Exposure",[97,1659],"Hover over the paths that represent your highest MRR to understand where the greatest financial risk sits. A path with 10 responses at $200 MRR each represents more revenue at risk than a path with 50 responses at $20 MRR each. The tooltip makes this comparison straightforward. Use this information to prioritize which follow-up paths get the strongest offers. If the \"Not seeing enough ROI\" path within \"Too Expensive\" carries $15,000 in monthly recurring revenue, that path deserves a carefully considered retention offer with a compelling value proposition.",{"id":1677,"title":1678,"titles":1679,"content":1680,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-flow#exporting-data","Exporting Data",[97],"The Response Flow supports two export formats. PNG. Download the Sankey diagram as an image file. Useful for including in presentations, executive reports, or team communications where visual context helps convey the story. CSV. Download the underlying data as a spreadsheet. Each row contains the primary choice, the follow-up option, the response count, the percentage, and the MRR. This format supports deeper analysis in tools like Excel, Google Sheets, or your business intelligence platform. Both export options are accessible from the export controls at the top of the Response Flow section.",{"id":102,"title":101,"titles":1682,"content":1683,"level":368},[],"Drill into individual follow-up responses with the Treemap visualization, search, trend analysis, and MRR impact sorting",{"id":1685,"title":5,"titles":1686,"content":1687,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-explorer#overview",[101],"The Response Explorer provides a Treemap visualization of your Structured Follow-Up Questions data, showing the distribution of follow-up responses across all of your survey choices. Where the Response Flow shows paths and proportions, the Response Explorer lets you drill down into individual customer responses, search across your data, and analyze trends over time. Each cell in the treemap represents a unique follow-up option. The size of the cell reflects either the number of responses or the MRR impact, depending on the view you select. Larger cells represent follow-up options that are either more frequently selected or carry greater financial weight.",{"id":1689,"title":1649,"titles":1690,"content":1691,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-explorer#where-to-find-it",[101],"Navigate to Cancel Flows in your Churnkey dashboardClick Analytics in the top navigationScroll to the Response Explorer section The treemap appears when you have Structured Follow-Up Questions configured and customers have submitted responses.",{"id":1693,"title":1694,"titles":1695,"content":1696,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-explorer#switching-between-views","Switching Between Views",[101],"The Response Explorer offers two views, toggled from the controls at the top of the visualization. Response Count sizes each treemap cell by the number of customers who selected that follow-up option. This view tells you which sub-reasons are most common. It is useful for understanding the breadth of an issue, specifically how many customers cite a particular reason. MRR Impact sizes each treemap cell by the total monthly recurring revenue of the customers who selected that follow-up option. This view tells you which sub-reasons carry the most financial weight. It is useful for understanding the depth of an issue, specifically how much revenue is at risk from customers citing a particular reason. These two views frequently tell different stories. A follow-up option might appear small in the Response Count view because only 8% of respondents select it, but appear large in the MRR Impact view because those respondents are disproportionately high-value customers. Always check both views before making decisions about offer assignments or product priorities.",{"id":1698,"title":1699,"titles":1700,"content":1701,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-explorer#drilling-down-into-responses","Drilling Down into Responses",[101],"Click any cell in the treemap to open the drill-down modal for that follow-up option. The modal provides four tools for understanding the responses in detail.",{"id":1703,"title":1704,"titles":1705,"content":1706,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-explorer#trend-chart","Trend Chart",[101,1699],"The top of the modal displays a line chart showing the response count for that follow-up option over time. Use this chart to detect shifts in customer behavior. A steady increase might indicate a worsening product issue or a change in the competitive landscape. A sudden spike might correlate with a recent pricing change, a product update, or an external event. The trend chart is particularly valuable when paired with dates of known changes. If you released a new pricing structure three weeks ago and \"Overall price is too high\" responses doubled that same week, the trend chart makes the correlation immediately visible.",{"id":1708,"title":1709,"titles":1710,"content":1711,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-explorer#search-and-filter","Search and Filter",[101,1699],"A search bar at the top of the drill-down modal lets you search across three dimensions: Response text. Search within the freeform text that customers provided alongside their structured selection (when using the Freeform + Structured mode). This helps you find specific themes or keywords within a category. Question. Filter by the primary survey question that led to this follow-up option. Useful if you have multiple flows or segments generating follow-up data. Email. Search for a specific customer by their email address. Use this when you want to review the cancellation context for a particular account, perhaps before reaching out to them directly.",{"id":1713,"title":1714,"titles":1715,"content":1716,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-explorer#sorting-options","Sorting Options",[101,1699],"Sort the individual response records within the drill-down by: Newest. Most recent responses first. This is the default and helps you see the latest customer feedback. Oldest. Earliest responses first. Useful for understanding the historical baseline of a particular follow-up option. Highest MRR. Customers with the highest monthly recurring revenue first. This is the most actionable sort order when you want to prioritize outreach or retention efforts by revenue impact. Lowest MRR. Customers with the lowest MRR first. Useful for understanding whether a follow-up option primarily affects smaller accounts.",{"id":1718,"title":1719,"titles":1720,"content":1721,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-explorer#individual-response-cards","Individual Response Cards",[101,1699],"Each response in the drill-down is displayed as a card with the customer's details. A card contains: Customer email. Identifies who submitted the response. Primary survey selection. The cancellation reason they chose on the first screen. Follow-up selection. The specific sub-reason they chose on the follow-up screen. Freeform text (if provided). Any additional context the customer typed. Subscription details. Plan type, start date, and billing information. MRR. The monthly recurring revenue at risk for this customer. These cards give you everything you need to understand a single customer's situation. Use them to craft personalized outreach, to investigate patterns across multiple cards, or to provide context for product and support teams.",{"id":1723,"title":1678,"titles":1724,"content":1725,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-explorer#exporting-data",[101],"Download the Response Explorer data as a CSV file from the export controls at the top of the section. The export includes all follow-up response records with their associated customer details, primary choices, follow-up selections, freeform text, MRR values, and timestamps. The CSV export is useful for: Deeper analysis. Import the data into a spreadsheet or business intelligence tool for custom pivots, charts, or statistical analysis that goes beyond what the treemap visualization shows. Cross-team sharing. Share the raw data with product, support, or leadership teams who need to work with the information in their own tools and workflows. Historical tracking. Build a longitudinal dataset by exporting periodically, allowing you to track follow-up response trends across months or quarters independent of the dashboard's date range.",{"id":1727,"title":1728,"titles":1729,"content":422,"level":374},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-explorer#practical-workflows","Practical Workflows",[101],{"id":1731,"title":1732,"titles":1733,"content":1734,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-explorer#weekly-review-workflow","Weekly Review Workflow",[101,1728],"Open the Response Explorer at the start of each week. Toggle to the MRR Impact view and identify the largest cells. These are the follow-up options representing the most revenue at risk. Click into the top two or three cells, sort by Highest MRR, and review the individual response cards. Look for patterns in the freeform text and subscription details that might inform a new offer, a product change, or a targeted outreach campaign.",{"id":1736,"title":1737,"titles":1738,"content":1739,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-explorer#emerging-issue-detection","Emerging Issue Detection",[101,1728],"Switch to the Response Count view and click into follow-up options that appear to be growing. Inside the drill-down, check the Trend Chart. If a follow-up option shows a clear upward trajectory over the past two to four weeks, it signals an emerging issue that deserves attention before it becomes a dominant churn driver.",{"id":1741,"title":1742,"titles":1743,"content":1744,"level":417},"\u002Fcancel-flows\u002Fanalytics\u002Fresponse-explorer#customer-outreach-prioritization","Customer Outreach Prioritization",[101,1728],"When you want to reach out to at-risk customers directly, open the drill-down for a follow-up option, sort by Highest MRR, and work down the list. The combination of the customer's stated reason, their freeform feedback, and their subscription value gives your customer success team a strong foundation for a meaningful, personalized conversation.",{"id":112,"title":111,"titles":1746,"content":1747,"level":368},[],"Support multiple languages. A 5-minute video on internationalization and multi-language support:",{"id":1749,"title":1750,"titles":1751,"content":1752,"level":374},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fmulti-language-support#pre-built-translations","Pre-Built Translations",[111],"In addition to English, we have pre-built support for French, Spanish, German, and 45 additional languages. You can simply pass in an i18n parameter with lang set to one of 'fr', 'de', 'es', 'en' and all of the buttons and pre-set text will be switched to the specified language. Note that these pre-built translations are just for all of the Churnkey-generated text, such as buttons and step labels. For the text within the Cancel Flow, you can customize this following the directions below in (II). window.churnkey.init('show', {\n  customerId: 'CUSTOMER_ID',\n  ..., \u002F\u002F additional Churnkey parameters\n  i18n: {\n    lang: 'fr', \u002F\u002F 'fr', 'de', 'es', 'en'\n    \u002F\u002F specific overrides under messages (optional); all i18n phrases can be found in (III) below\n    messages: {\n      fr: {\n        next: 'Prochain',\n        back: 'Retour',\n        ...\n        weReadEveryAnswer: 'Nous lisons chaque réponse'\n      }\n    }\n  }\n})",{"id":1754,"title":1755,"titles":1756,"content":1757,"level":374},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fmulti-language-support#overriding-built-in-buttons-and-offer-text","Overriding Built-in Buttons and Offer Text",[111],"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.",{"id":1759,"title":1760,"titles":1761,"content":422,"level":417},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fmulti-language-support#using-cancel-flow-settings-to-override-default-english-phrases","Using Cancel Flow settings to override default English phrases",[111,1755],{"id":1763,"title":1764,"titles":1765,"content":1766,"level":417},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fmulti-language-support#using-the-i18n-parameter-for-overriding-default-english-phrases","Using the i18n parameter for overriding default English phrases",[111,1755],"window.churnkey.init('show', {\n  i18n: {\n    messages: {\n      en: {\n        goToAccount: 'Return to Billing', \u002F\u002F english override\n      },\n    },\n  },\n});",{"id":1768,"title":1769,"titles":1770,"content":1771,"level":417},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fmulti-language-support#using-i18n-for-translating-into-non-english-languages","Using i18n for translating into non-English languages",[111,1755],"For complete translation, all phrases from the global list below (all i18n phrases) must be provided. window.churnkey.init('show', {\n  i18n: {\n    lang: 'fr',\n    messages: {\n      fr: {\n        next: 'Prochain',\n        back: 'Retour',\n        ...\n        weReadEveryAnswer: 'Nous lisons chaque réponse'\n      }\n    }\n  }\n})",{"id":1773,"title":1774,"titles":1775,"content":1776,"level":374},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fmulti-language-support#creating-customer-segments-for-different-languages","Creating Customer Segments for Different Languages",[111],"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', {\n  customerAttributes: {\n    language: 'fr',\n  },\n  i18n: {\n    lang: 'fr',\n  },\n});",{"id":1778,"title":1779,"titles":1780,"content":1781,"level":374},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fmulti-language-support#all-i18n-phrases","All i18n Phrases",[111],"Below are all phrases that can be supplied. By default, Churnkey provides English, German, French, and Spanish translations. en: {\n    next: 'Next',\n    back: 'Back',\n    nevermind: 'Go Back',\n    goToAccount: 'Go to Account',\n    getHelp: 'Something Wrong? Contact Us...',\n    declineOffer: 'Decline Offer',\n    confirmAndCancel: 'Confirm & Cancel',\n    pauseSubscription: 'Pause Subscription',\n    cancelSubscription: 'Cancel',\n    discountSubscription: 'Accept This Offer', \u002F\u002F button to accept discount offer\n    claimOffer: 'Claim your limited-time offer', \u002F\u002F shown above discount offers\n    discountOff: 'off', \u002F\u002F e.g. \"10% _off_\"\n    discountFor: 'for', \u002F\u002F e.g. \"20% off _for_ 2 months\"\n    discountForever: 'for life', \u002F\u002F e.g. \"10% off _for life_\"\n    discountOneTime: 'your next renewal', \u002F\u002F e.g. \"10% off _your next renewal_\"\n    day: 'Day | Days', \u002F\u002F e.g. \"1 day\" \"2 days\"\n    month: 'month | months', \u002F\u002F e.g. \"1 month\" \"2 months\"\n    year: 'year | years', \u002F\u002F e.g. \"1 year\" \"2 years\"\n    error: 'Sorry, something went wrong', \u002F\u002F generic error message\n    genericErrorDescription: 'Please contact us.', \u002F\u002F generic error message\n    cancelNow: 'Cancel Subscription →', \u002F\u002F button to cancel subscription immediately\n    applyingDiscount: 'Applying discount...', \u002F\u002F shown while applying discount\n    applyingCancel: 'Cancelling subscription...', \u002F\u002F shown while cancelling subscription\n    applyingResume: 'Resuming subscription...', \u002F\u002F shown while resuming a paused subscription\n    applyingPause: 'Pausing subscription...', \u002F\u002F shown while pausing subscription\n    discountApplied: 'Discount applied.', \u002F\u002F shown when discount is applied\n    discountAppliedMessage: \"We're so happy you're still here\", \u002F\u002F shown when discount is applied\n    pauseApplied: 'Subscription paused.', \u002F\u002F shown when subscription is paused\n    pauseAppliedMessage: \"You won't be billed until your subscription resumes\", \u002F\u002F shown when subscription is paused\n    pauseAppliedResumeMessage: 'Your subscription will resume on', \u002F\u002F shown when subscription is paused\n    pauseScheduledMessage: 'Your subscription will be paused beginning', \u002F\u002F shown when subscription is scheduled in the future\n    until: 'until',\n    cancelApplied: 'Subscription cancelled.', \u002F\u002F shown when subscription is canceled\n    cancelAppliedMessage: \"You won't be billed again\", \u002F\u002F shown when subscription is canceled\n    cancelAppliedDateMessage: 'Your subscription will end on', \u002F\u002F shown when subscription is canceled\n    howLongToPausePrompt: 'Choose how long you want to pause...', \u002F\u002F shown above the pause subscription prompt\n    whatCouldWeHaveDone: 'What could we have done better?', \u002F\u002F shown above the feedback prompt\n    weReadEveryAnswer: 'We read every answer...', \u002F\u002F shown as placeholder in the feedback prompt\n    applyingCustomerAction: 'This will just take a second.', \u002F\u002F shown while applying customer action\n    loading: 'Loading...', \u002F\u002F shown while loading\n    pauseWallCardPunch: 'Want access?',\n    pauseWallCta: 'Resume Subscription Now',\n    pauseWallCardHeading: 'Resume your subscription',\n    scheduledToReactivate: \"It's scheduled to reactivate on\",\n    resumeApplied: 'Subscription resumed', \u002F\u002F shown when subscription is paused\n    resumeAppliedMessage: 'You will be billed at your next subscription renewal period.', \u002F\u002F shown when subscription is paused\n    resumeNextChargeMessage: \"Upon reactivation, you'll be charged at your original rate of \",\n    resumeNextChargeMessageWithoutAmount: \"Upon reactivation, you'll be charged at your usual rate.\",\n    resumeAccountDataInfo: 'Your account data is being kept safe for when your subscription resumes.',\n    subscriptionPauseNotice: 'Looks like your subscription is still paused',\n    failedPaymentNotice: 'Your account access is limited right now',\n    chargedMultipleTimeNotice:\n      \"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 👇\",\n    failedPaymentCardPunch: 'Update your card to restore access.',\n    resumeHey: 'Hey',\n    invoicePaidTitle: 'Invoice paid successfully',\n    logout: 'Logout →', \u002F\u002F used on both Failed Payment and Pause Walls\n    \u002F\u002F ex You have a 20% off for 3 months discount valid until October 15. Your existing discount will be overridden upon accepting a new offer.\n    note: 'Note:', \u002F\u002F shown before the discount note\n    discount: 'discount',\n    discountNoticeHeadline: \"Note: you'll also lose an active discount.\",\n    discountNoticePrepend: \"If you cancel now, you'll lose your current\",\n    discountOverride: \"if you take this offer, you'll lose your current\",\n    discountValidUntil: \"It's good until\", \u002F\u002F shown on active discount\n    updateBilling: 'Update Card',\n    \u002F\u002F Trial Extension\n    extendTrialCTA: 'Extend Trial By ',\n    extendTrialPunch: 'Your trial ends on ',\n    extendTrialOfferTitle: 'Trial Extension',\n    trialExtended: 'Trial extended.',\n    trialExtendedMessage: 'Your trial has been extended successfully',\n    applyingTrialExtension: 'Extending your trial',\n    \u002F\u002F Plan Change\n    switchPlanCTA: 'Switch Plan',\n    changePlanHighlights: 'Highlights',\n    changePlanOfferPunch: 'Discounted Secret Plans',\n    planChanged: 'Plan changed.',\n    planChangedMessage: 'Your new plan is now in effect',\n    applyingPlanChange: 'Changing your plan...',\n    \u002F\u002F Left Theme Step Tags\n    surveyStepTag: 'Your Feedback',\n    freeFormStepTag: 'Your Feedback',\n    finalConfirmation: 'Final Confirmation',\n    offerDiscountTag: 'Special Offer',\n    offerChangePlanTag: 'Consider Other Plans',\n    offerExtendTrialTag: 'Extend Your Trial',\n    offerRedirectTag: 'Let Us Help',\n    offerContactTag: 'Let Us Help',\n    offerPauseTag: 'Subscription Pause',\n    completeTag: 'Subscription Cancelled',\n    errorTag: 'An Error Occurred',\n    offerAccepted: 'Offer Accepted',\n  } html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}",{"id":116,"title":115,"titles":1783,"content":1784,"level":368},[],"Enable AI-powered multilingual support for your Cancel Flows with a single toggle.",{"id":1786,"title":5,"titles":1787,"content":1788,"level":368},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fautomatic-translation#overview",[],"Automatic Translations provide instant multilingual support for your Cancel Flows. With Automatic Translations, your Cancel Flows will perform better and be more personalized without the need for manual translation work and unique segments for each language you want to support. There's also a significant performance benefit to translating your Cancel Flows: save rates for translated Cancel Flows performed 46% better than those that weren't translated.",{"id":1790,"title":149,"titles":1791,"content":1792,"level":374},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fautomatic-translation#getting-started",[5],"The new feature is available in every plan, and to enable it: Navigate to Cancel Flows > SettingsLocate the Automatic Translations sectionChoose your target languages for translation Once configured, your Cancel Flows will automatically translate content based on the customer's browser information. The system detects the customer's preferred language automatically.",{"id":1794,"title":1795,"titles":1796,"content":1797,"level":417},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fautomatic-translation#language-support","Language Support",[5,149],"The system uses the language code set by the Navigator API in your customer's browser to display their preferred language. This means your Cancel Flows will automatically appear in the language that matches your customer's browser settings. The platform offers extensive language support with 50 languages available. From our data, here are the most commonly used languages: Language CodeLanguage NameLocal NameenEnglishEnglishesSpanishEspañolpt-BRPortuguese (Brazil)Português (Brasil)frFrenchFrançaisdeGermanDeutschitItalianItalianojaJapanese日本語zh-CNChinese (Simplified)中文ruRussianРусскийarArabicالعربية Language CodeLanguage NameLocal NameenEnglishEnglishzh-CNChinese (Simplified)中文hiHindi (India)हिन्दीesSpanishEspañolarArabicالعربيةbnBengaliবাংলাfrFrenchFrançaispt-BRPortuguese (Brazil)Português (Brasil)ruRussianРусскийurUrduاردوidIndonesianBahasa IndonesiadeGermanDeutschjaJapanese日本語pcmNigerian PidginNaijáswSwahiliKiswahilimrMarathi (India)मराठीteTelugu (India)తెలుగుtrTurkishTürkçeviVietnameseTiếng ViệtkoKorean한국어taTamilதமிழ்jvJavaneseBasa JawahaHausa (Nigeria)HausaitItalianItalianofa-IRPersian (Iran)فارسیthThaiไทยguGujarati (India)ગુજરાતીpaPunjabi (India)ਪੰਜਾਬੀms-MYMalay (Malaysia)Bahasa MelayumyBurmeseမြန်မာစာyueCantonese粵語yoYoruba (Nigeria)YorùbáigIgbo (Nigeria)IgboknKannada (India)ಕನ್ನಡmlMalayalam (India)മലയാളംmaiMaithili (India)मैथिलीsdSindhi (Pakistan)سنڌيneNepaliनेपालीsiSinhalaසිංහලkmKhmerភាសា​ខ្មែរamAmharic (Ethiopia)አማርኛomOromo (Ethiopia)Afaan OromooplPolishPolskiukUkrainianУкраїнськаbhoBhojpuri (India)भोजपुरीroRomanianRomânăelGreekΕλληνικάnlDutchNederlandsazAzerbaijaniAzərbaycancsCzechčeština",{"id":1799,"title":1800,"titles":1801,"content":1802,"level":374},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fautomatic-translation#testing-and-preview","Testing and Preview",[5],"To preview your translated Cancel Flows, navigate to Cancel Flows > Flows and select any flow for editing. In the dropdown menu for each flow choose \"Preview,\" then use the language selector dropdown in the top-left corner to preview the flow in different languages. If the translation doesn't meet your requirements, you can click the Regenerate button in the top right corner to generate a new translation.",{"id":1804,"title":470,"titles":1805,"content":422,"level":374},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fautomatic-translation#faq",[5],{"id":1807,"title":1808,"titles":1809,"content":1810,"level":417},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fautomatic-translation#will-automatic-translations-affect-cancel-flow-loading-times","Will Automatic Translations affect Cancel Flow loading times?",[5,470],"No. Translations are pre-processed before being served to your customers, ensuring there is no impact on the flow's loading performance.",{"id":1812,"title":1813,"titles":1814,"content":1815,"level":417},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fautomatic-translation#can-i-edit-the-ai-translations","Can I edit the AI translations?",[5,470],"Currently, the automatic translation system generates complete translations that cannot be manually edited. We are actively developing a feature that will allow partial customization of translations, enabling you to adjust specific terms to better match your business context. This enhancement will be available in a future release.",{"id":1817,"title":1818,"titles":1819,"content":1820,"level":417},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fautomatic-translation#how-are-metrics-handled-for-translated-flows","How are metrics handled for translated flows?",[5,470],"Metrics for translated flows are consolidated under the original flow. For example, if you have a flow called \"Annual Plan Customers\" translated into Spanish and Portuguese, all interactions and conversions will be aggregated under the main flow.",{"id":1822,"title":1823,"titles":1824,"content":1825,"level":417},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fautomatic-translation#is-it-possible-to-manually-force-a-translation-to-a-customer","Is it possible to manually force a translation to a customer?",[5,470],"Yes, you can override the automatic language detection when initializing your flows. For example, if your platform's interface is in English, or a user has set a preference for English—but the customer's browser is set to Portuguese—you can ensure consistency by explicitly setting the language to English using the corresponding language code: i18n: {lang: 'en'}. Note that the language must be enabled in the Automatic Translation settings for this override to work.",{"id":1827,"title":1828,"titles":1829,"content":1830,"level":417},"\u002Fcancel-flows\u002Fmulti-language-support\u002Fautomatic-translation#manual-translations-vs-automatic-translations","Manual Translations vs. Automatic Translations",[5,470],"Manual Translations allow you to customize predefined texts and buttons in your Cancel Flows. While Automatic Translations provide default translations, you can modify specific text elements to better align with your brand's terminology and voice. For example, if the automatic Spanish translation uses \"Cambiar Plan\" for a call-to-action button, you can override it with \"Cambiar Suscripción\" to better match your preferred terminology. When customers view the Spanish version of your flow, they'll see your customized text instead of the default translation. Manual Translations take precedence over Automatic Translations, ensuring that your brand's specific terminology and voice are consistently maintained across all languages.",{"id":125,"title":124,"titles":1832,"content":1833,"level":368},[],"Offer targeted subscription discounts to reduce churn and increase customer retention Discount Offers help you recover 25-40% of customers who would otherwise cancel completely. Instead of losing these customers entirely, you retain them at a reduced rate that's still profitable—turning complete revenue loss into partial revenue retention.",{"id":1835,"title":1836,"titles":1837,"content":1838,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#what-discount-offers-do-for-your-business","What Discount Offers Do for Your Business",[124],"Discount Offers work at the most critical moment — when customers have already decided to cancel but haven't completed the process yet. At this point, customers know your product's value but are reconsidering the price. A well-timed discount creates a \"pause and reconsider\" moment that can transform a cancellation into extended revenue.",{"id":1840,"title":1841,"titles":1842,"content":1843,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#the-strategic-advantage","The Strategic Advantage",[124,1836],"Unlike public promotions that train customers to wait for deals, cancellation discounts are embedded within your retention flow and only appear to customers actively leaving. This preserves your regular pricing while providing a powerful last-chance retention tool. Based on analysis of over 3 million cancellation sessions, customers who accept discount offers stay 5.1 months longer on average, and 11% remain subscribers even after their discount expires—proving these aren't just delayed cancellations but genuine retention wins.",{"id":1845,"title":1846,"titles":1847,"content":1848,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#real-revenue-impact","Real Revenue Impact",[124,1836],"100 customers attempt to cancel monthlyWithout discounts: Lose 100 customers = $0 recovered revenueWith strategic discount offers: Save 25 customers = 75% of their original revenue retained plus 5+ additional months of subscription valueExtended benefit: 11% of discount customers become long-term subscribers at full price The bottom line: A customer paying 75% for 5+ months is significantly more valuable than a customer paying 0% forever.",{"id":1850,"title":1851,"titles":1852,"content":1853,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#key-benefits","Key Benefits",[124,1836],"Discount Offers provide powerful churn reduction by presenting attractive alternatives to cancellation at the critical decision moment when customers are most likely to reconsider their choice. The system supports flexible discount types including both percentage-based and fixed amount discounts with fully configurable durations to match your business model. The platform delivers seamless multi-provider support that works consistently across Stripe, Chargebee, Braintree, and Paddle payment providers, ensuring you can implement retention strategies regardless of your payment infrastructure. Built-in anti-gamification controls use cooldown periods to prevent customers from repeatedly exploiting discount offers, protecting your revenue while still providing genuine retention value. Before going live, comprehensive test mode validation ensures proper implementation with full testing capabilities that let you verify discount application, customer communication, without affecting real customer data.",{"id":1855,"title":1856,"titles":1857,"content":1858,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#how-discount-offers-work","How Discount Offers Work",[124],"The discount offer process involves three main components working together to deliver targeted retention incentives:",{"id":1860,"title":1861,"titles":1862,"content":1863,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#_1-customer-interaction-flow","1. Customer Interaction Flow",[124,1856],"When a customer attempts to cancel their subscription, the cancel flow presents a discount offer based on your configuration settings. The customer can then accept the discount to continue their subscription with reduced pricing, or decline the offer to proceed with the normal cancellation process.",{"id":1865,"title":1866,"titles":1867,"content":1868,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#_2-discount-application","2. Discount Application",[124,1856],"Once a customer accepts a discount offer, the system immediately applies the discount through your payment provider's API. By default, any existing active discounts are replaced with the new discount terms, and the customer receives immediate confirmation of their discount activation with clear details about their new pricing structure. Consider the implementation of the handleDiscount if you're looking to have a different behavior when applying coupons.",{"id":1870,"title":1871,"titles":1872,"content":1873,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#_3-analytics-and-tracking","3. Analytics and Tracking",[124,1856],"The system automatically tracks comprehensive metrics including discount acceptance and decline rates, revenue impact calculations, and customer retention performance. This data allows you to monitor offer performance across different customer segments and optimize your retention strategy based on real-world results.",{"id":1875,"title":1876,"titles":1877,"content":1878,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#setting-up-your-first-discount-offer-5-minutes","Setting Up Your First Discount Offer (5 Minutes)",[124],"Most successful businesses start with a simple, effective approach: 30% off for 3 months. Based on real customer data, 30% for 3 months appears to be the minimum starting point for meaningful acceptance rates—anything less typically results in very low acceptance rates.",{"id":1880,"title":1881,"titles":1882,"content":1883,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#step-1-add-discount-offer-to-your-cancel-flow","Step 1: Add Discount Offer to Your Cancel Flow",[124,1876],"Navigate to your Cancel Flow and add a Discount Offer step to your flow configuration. In the Cancel Flow builder, locate the Add Step button and click it to reveal the available step options. From the dropdown menu, select \"Apply Stripe Coupon\" to add this retention mechanism to your cancellation flow. Once you've selected the Discount Offer step, you'll see configuration options appear. For your first discount offer setup, choose \"Create Coupon\" rather than using existing coupons, as this approach gives you full control over the discount parameters and is recommended for first-time setup.",{"id":1885,"title":1886,"titles":1887,"content":1888,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#step-2-configure-your-discount-parameters","Step 2: Configure Your Discount Parameters",[124,1876],"Configure the core discount parameters that determine both the customer experience and business impact. Use these proven starting settings that work well for most businesses: Discount Type: Select Percentage rather than fixed amount. Percentage discounts scale automatically with your pricing tiers and are generally more compelling to customers across different subscription levels. Discount Amount: Set to 30%. Based on extensive customer data, 30% appears to be the minimum starting point for meaningful acceptance rates—anything less typically results in very low acceptance rates. This percentage provides compelling savings while maintaining sustainable unit economics. Discount Duration: Configure for 3 months. This timeframe gives customers sufficient time to experience your product's value while limiting the long-term revenue impact on your business. Why these settings are effective: 30% meets the minimum threshold for meaningful customer response3 months allows customers to experience value while containing revenue impactPercentage-based discounts work consistently across different pricing tiers",{"id":1890,"title":1891,"titles":1892,"content":1893,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#step-3-customize-your-offer-presentation","Step 3: Customize Your Offer Presentation",[124,1876],"The way you present your discount offer directly impacts acceptance rates. Configure the presentation elements that customers will see during their cancellation attempt. Header Text: Create compelling header text that emphasizes the value proposition. Example: \"Stay with us for 30% off the next 3 months.\" This immediately communicates both the benefit (30% savings) and the commitment level (3 months). Description: Provide clear context about the discount terms and benefits. Example: \"Continue your subscription and save $calculated amount over the next 3 months—no long-term commitment required after the discount period ends.\" This helps customers understand exactly what they're receiving and reduces uncertainty.",{"id":1895,"title":1896,"titles":1897,"content":1898,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#step-4-test-your-configuration","Step 4: Test Your Configuration",[124,1876],"Before customers see your discount offer, test it using two different approaches depending on what you want to validate.",{"id":1900,"title":1901,"titles":1902,"content":1903,"level":958},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#testing-approach-1-visual-preview-look-and-feel","Testing Approach 1: Visual Preview (Look and Feel)",[124,1876,1896],"Use the Preview button in the top right corner of the Cancel Flow builder to see exactly how your discount offer will appear to customers. This preview mode shows the complete customer experience including your header text, description, discount amount, and button styling. What this tests: Visual presentation and messagingOverall flow and user experience Use this when: You want to review and refine the customer-facing presentation before testing functionality.",{"id":1905,"title":1906,"titles":1907,"content":1908,"level":958},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#testing-approach-2-test-mode-discount-application","Testing Approach 2: Test Mode (Discount Application)",[124,1876,1896],"Use Test Mode to validate that Churnkey can correctly apply discounts to actual subscriptions in your payment provider's test environment. Enable Test Mode in your Cancel Flow settings and initiate a cancellation flow with a test customer. Important: In Test Mode, Churnkey won't apply your configured live coupon. Instead, you'll see a dropdown menu to select from your available test coupons in your payment provider. This allows you to test the discount application mechanics without affecting live data. What this tests: Integration with your payment providerDiscount application functionalityCoupon validation and error handlingSession completion Critical validation steps: Select a test coupon from the dropdown menuVerify the discount applies correctly in your payment provider's test dashboardConfirm that Churnkey displays appropriate success\u002Fconfirmation messagingCheck that the session is properly tracked in your analyticsTest cooldown period enforcement by using the same test customer to attempt another discount (should be skipped based on your cooldown settings) Use this when: You want to ensure the technical integration works correctly before going live with real customers.",{"id":1910,"title":1911,"titles":1912,"content":1913,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#step-5-configure-anti-gamification-controls","Step 5: Configure Anti-Gamification Controls",[124,1876],"Prevent customers from repeatedly canceling to access discount offers by implementing cooldown periods that maintain the integrity of your retention strategy. Navigate to Settings > Offer Redemption Settings and configure the Discount Cooldown Period to 3 months. This prevents the same customer from receiving multiple discounts in quick succession, which could otherwise lead to customers gaming the system by repeatedly triggering cancellation flows. The 3-month cooldown period strikes the optimal balance between preventing abuse and allowing genuine retention opportunities for customers who may face legitimate concerns at different points in their subscription lifecycle. Note that the total cooldown time is determined by both the duration of the discount coupon and this cooldown setting.",{"id":1915,"title":1916,"titles":1917,"content":1918,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#understanding-your-results","Understanding Your Results",[124],"Once your discount offers are live, monitor these key metrics to understand performance and identify optimization opportunities.",{"id":1920,"title":1921,"titles":1922,"content":1923,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#key-metrics-to-track","Key Metrics to Track",[124,1916],"Offer Acceptance Rate: The percentage of customers who see your discount offer and accept it. This will vary based on your industry, discount amount, and customer base. Boosted Revenue: How much revenue you're recovering compared to complete customer loss. Calculate as: (Customers saved × Discounted revenue) vs. (Customers saved × $0)Example: Save 25 customers at 75% revenue = 18.75x better than losing them entirely Customer Lifetime Value (LTV): Whether customers who accept discounts remain long-term subscribers. Monitor retention rates of discount customers vs. regular customersTrack if customers continue after the discount period ends Reactivation Rate: Portion of saved customers who have paid an invoice since their initial cancellation flow. Shows the long-term success of your discount offers beyond the immediate retentionIndicates whether customers who accept discounts become genuinely engaged subscribers",{"id":1925,"title":1926,"titles":1927,"content":1928,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#monitoring-your-performance","Monitoring Your Performance",[124,1916],"Track these key metrics in your Churnkey analytics to understand how your discount offers are performing: Offer Acceptance Rate: Monitor how many customers who see your discount offer choose to accept it rather than cancel. Save Rate: Track the overall effectiveness of your cancellation flow in retaining customers who would otherwise cancel. Boosted Revenue: Measure the revenue recovered through discount offers compared to complete customer loss. Reactivation Rate: Monitor the portion of saved customers who continue to pay invoices after their initial flow experience. Performance varies significantly based on your industry, customer base, discount amounts, and implementation. Use your own data to establish performance baselines rather than external benchmarks.",{"id":1930,"title":1931,"titles":1932,"content":1933,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#common-optimization-scenarios","Common Optimization Scenarios",[124,1916],"High acceptance rates but low boosted revenue:\nYour discounts may be too generous, reducing the revenue benefit per retained customer. Try reducing the discount percentage or duration to improve unit economics while maintaining acceptable acceptance rates. Low acceptance rates but decent boosted revenue:\nYour discounts might not be compelling enough to reach more customers. Test higher discount percentages or better messaging to improve customer acceptance while maintaining good revenue recovery. Good acceptance rates but poor post-discount retention:\nFocus on customer success during the discount period. Consider graduated discount reduction instead of abrupt ending.",{"id":1935,"title":1936,"titles":1937,"content":1938,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#optimizing-your-discount-strategy-over-time","Optimizing Your Discount Strategy Over Time",[124],"Don't try to perfect your discount offers immediately. Start simple, gather data, and optimize based on real customer behavior.",{"id":1940,"title":1941,"titles":1942,"content":1943,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#month-1-start-conservative-establish-baseline","Month 1: Start Conservative & Establish Baseline",[124,1936],"Recommended Initial Settings: Discount: 30% off for 3 monthsCooldown period: 3 monthsFocus: Ensure technical setup works correctly What to monitor: Initial acceptance rates to establish your baselineTechnical functionality (discount application, analytics tracking)Customer feedback on offer presentation Success criteria: Discount offers display correctly to customersDiscounts apply successfully in your payment providerAnalytics accurately track acceptance\u002Fdecline rates",{"id":1945,"title":1946,"titles":1947,"content":1948,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#month-2-3-test-variations-refine-messaging","Month 2-3: Test Variations & Refine Messaging",[124,1936],"Testing approach: A\u002FB test discount amounts: 20% vs 30% to find your sweet spotTest duration variations: 1 month vs 6 monthsExperiment with presentation copy and call-to-action language Optimization areas: Header text: Test value-focused vs urgency-focused messagingDescription: Experiment with benefit emphasis vs commitment clarityTiming: Try different positions in your cancellation flow Expected improvements: Acceptance rates should improve with better messaging and optimizationBoosted revenue should become more predictable based on your customer behaviorCustomer acceptance patterns become clearer",{"id":1950,"title":1951,"titles":1952,"content":1953,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#month-4-6-implement-advanced-features","Month 4-6: Implement Advanced Features",[124,1936],"Enable sophisticated features: Customer segmentation: Different offers for high-value vs low-value customersAdvanced testing: Run systematic A\u002FB tests across different customer cohorts Strategic refinements: Adjust discount amounts based on customer subscription valueCreate seasonal campaigns with different discount strategies Performance optimization: Optimize for customer lifetime value, not just immediate save ratesBalance retention effectiveness with long-term unit economics",{"id":1955,"title":1956,"titles":1957,"content":1958,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#month-6-mature-strategy-continuous-improvement","Month 6+: Mature Strategy & Continuous Improvement",[124,1936],"Advanced implementations: Multiple discount tiers for different customer segmentsIntegration with other retention strategies (surveys, feature upgrades)Predictive modeling to customize offers before customers attempt to cancel Continuous monitoring: Monthly performance reviews with stakeholder teamsQuarterly discount strategy assessmentsCompetitive analysis to ensure market positioning Long-term optimization: Customer journey mapping to prevent cancellations earlierProduct improvements based on discount customer feedbackRevenue forecasting incorporating retention strategy impact",{"id":1960,"title":1961,"titles":1962,"content":1963,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#common-scenarios-solutions","Common Scenarios & Solutions",[124],"Use these troubleshooting guides to address typical discount offer challenges and optimize performance.",{"id":1965,"title":1966,"titles":1967,"content":1968,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#my-acceptance-rates-are-low-less-than-15","\"My acceptance rates are low (less than 15%)\"",[124,1961],"Potential causes and solutions: Discount amount not compelling enough Test higher discount percentages (try 30-40% if currently using 15-20%)Compare your discount to competitors' promotional offersConsider the customer's annual subscription value when setting discount amounts Poor offer presentation Review your header text - ensure it clearly communicates the valueCheck that customers understand there's no long-term commitment beyond the discount period Timing in cancellation flow Move the discount offer earlier in the cancellation processEnsure customers see the offer before providing cancellation feedbackTest presenting the offer immediately vs after capturing cancellation reason Customer segment mismatch High-value customers may need larger discounts to be motivatedLong-term customers might respond better to loyalty-focused messagingRecent customers may prefer shorter-term commitments (1-3 months vs 6+ months)",{"id":1970,"title":1971,"titles":1972,"content":1973,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#acceptance-rates-are-high-35-but-revenue-impact-is-negative","\"Acceptance rates are high (35%+) but revenue impact is negative\"",[124,1961],"Analysis and optimization: Discounts are too generous Make sure to keep discounts in a profitable range for your businessReduce discount percentage while maintaining duration (30% → 20%)Shorten discount duration while maintaining percentage (6 months → 3 months)Test graduated discounts that decrease over time instead of flat rates Customer lifetime value consideration Calculate whether discounted customers have higher long-term retentionMonitor if customers continue subscribing after discount expiresFactor in reduced churn costs when evaluating revenue impact Pricing strategy alignment Ensure discount pricing doesn't conflict with your regular promotional pricingConsider if the discount makes your regular pricing seem overvaluedReview whether discounted customers refer others or upgrade plans",{"id":1975,"title":1976,"titles":1977,"content":1978,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#customers-keep-canceling-after-discount-ends","\"Customers keep canceling after discount ends\"",[124,1961],"Retention strategies during discount period: Onboarding and engagement Increase customer success outreach during the discount periodTrack feature adoption and usage patterns of discount customers Value demonstration Send regular communications highlighting features and benefitsProvide usage analytics showing customer's engagement with your productShare success stories from similar customers who found long-term value Transition planning Send advance notice before discount expires (30 days, 7 days)Offer graduated pricing transitions instead of immediate full priceProvide options to switch to annual billing for continued savings",{"id":1980,"title":1981,"titles":1982,"content":1983,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#customers-complain-about-discount-restrictions","\"Customers complain about discount restrictions\"",[124,1961],"Communication and expectation management: Clear terms presentation Explicitly state discount duration and what happens afterwardUse plain language instead of legal terms in offer descriptionsProvide examples of what customers will pay during and after discount Cooldown period feedback Offer alternative retention options for customers in cooldown periodsConsider shorter cooldown periods if business impact allows Technical issues Provide easy access to discount status in customer account dashboardCreate clear support documentation for discount-related questions",{"id":1985,"title":1986,"titles":1987,"content":422,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#advanced-configuration-options","Advanced Configuration Options",[124],{"id":1989,"title":1990,"titles":1991,"content":1992,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#multiple-discount-tiers","Multiple Discount Tiers",[124,1986],"You can create multiple discount offers within a single cancel flow that gradually increase in value as customers progress through the cancellation process. Start with a conservative offer to gauge initial interest, such as 15% off for 1 month, designed to capture customers who are easily retained with minimal commitment. If the first offer is declined, present a stronger offer with more compelling terms, such as 25% off for 3 months, which appeals to customers who need more substantial savings and are willing to commit longer. Finally, configure a final offer as your last retention attempt before cancellation, perhaps 40% off for 6 months, providing maximum value and extended commitment to save customers who are most determined to cancel.",{"id":1994,"title":1995,"titles":1996,"content":1997,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#conditional-discount-logic","Conditional Discount Logic",[124,1986],"Configure offers based on customer characteristics to deliver personalized retention experiences that match your customers' specific situations. Consider subscription age when designing offers, providing different incentives for new customers who may need more encouragement versus long-term customers who demonstrate proven value to your business. Factor in subscription value by offering higher discounts to premium plan subscribers whose retention justifies more aggressive discounting. Additionally, adjust offers based on previous discount history, ensuring customers who have already used discounts receive appropriately modified offers that maintain the effectiveness of your retention strategy.",{"id":1999,"title":578,"titles":2000,"content":422,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#best-practices",[124],{"id":2002,"title":2003,"titles":2004,"content":2005,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#discount-strategy-optimization","Discount Strategy Optimization",[124,578],"Effective discount strategy requires careful balance between retention effectiveness and revenue protection. Start conservative by beginning with modest discount percentages and shorter durations to assess impact on both customer behavior and your bottom line. This approach allows you to establish baseline performance data without over-committing to discounts that may not be necessary. Monitor performance closely by tracking key metrics including save rate, revenue impact, and customer lifetime value to understand the true effectiveness of your discount strategy. Regular analysis of these metrics helps you identify optimal discount levels and durations that maximize retention while maintaining healthy unit economics. Consider seasonal adjustments to your discount strategy, potentially offering higher discounts during peak churn seasons or competitive periods when customer retention becomes more challenging. Segment-based offers allow you to customize discount amounts based on customer value and subscription history, ensuring your most valuable customers receive appropriate incentives without over-discounting lower-value segments.",{"id":2007,"title":2008,"titles":2009,"content":2010,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#avoiding-common-pitfalls","Avoiding Common Pitfalls",[124,578],"Over-discounting represents one of the most common mistakes in retention strategy. Avoid discounts that significantly impact profitability or create unsustainable customer expectations about future pricing. Focus on finding the minimum effective discount that achieves retention goals while preserving long-term revenue health. Insufficient cooldown periods can lead to discount abuse where customers repeatedly cancel to access offers. Implement appropriate cooldown periods that prevent this behavior while still allowing genuine retention opportunities for customers facing legitimate concerns. Poor offer presentation undermines even well-designed discount strategies. Ensure discount offers are clearly communicated with compelling value propositions that emphasize benefits rather than just price reductions. Inadequate testing can lead to embarrassing failures or missed opportunities, so always validate discount functionality thoroughly in test mode before live deployment.",{"id":2012,"title":2013,"titles":2014,"content":2015,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#revenue-impact-considerations","Revenue Impact Considerations",[124,578],"When implementing discount offers, always consider customer lifetime value by weighing the long-term revenue impact of retained customers against the immediate cost of discounts. Often, the revenue from retained customers over their extended lifetime far exceeds the discount cost, making aggressive retention strategies financially justified. Calculate the churn prevention ROI by analyzing the return on investment for discount-based retention efforts compared to alternative retention strategies or customer acquisition costs. Ensure competitive positioning remains consistent by confirming that discount offers align with your market positioning and value proposition rather than undermining your brand's perceived value.",{"id":2017,"title":269,"titles":2018,"content":2019,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fdiscounts#frequently-asked-questions",[124],"Can customers receive multiple discounts over time?\nYes, but cooldown periods control the frequency. You can configure how many months customers must wait between discount redemptions to prevent abuse. How are discount offers tracked in analytics?\nAll discount interactions are tracked in your Churnkey analytics, including offer presentations, acceptances, declines, and revenue impact calculations. Can I offer different discounts to different customer segments?\nYes, you can configure conditional logic to present different discount offers based on subscription characteristics, customer history, and other factors. What happens in test mode?Test mode allows you to validate discount functionality using test coupons from your payment provider without affecting live customer data. How quickly are discounts applied after customer acceptance?\nDiscounts are applied immediately through your payment provider's API. Make sure to update your Subscription Management Page to provide a consistent experience to the customer. You can rely on your Payment Provider information, or use our listeners to update the state of your platform after a discount. Can I customize the discount offer presentation?\nYes, you can customize the header text, description, and call-to-action button to match your brand voice and optimize for conversion. What happens if discount application fails?\nThe system includes comprehensive error handling. If discount application fails, you're going to receive an email notification with details of the issue.",{"id":129,"title":128,"titles":2021,"content":2022,"level":368},[],"Use the Switch Subscription Plan offer to present exclusive, non-public plans to customers during cancellation Hidden Plans let you present subscription plans that don't exist on your public pricing page to customers who are about to cancel. This is not a separate feature. Hidden Plans are a strategic use of the Switch Subscription Plan offer. The difference is that the plan you offer during cancellation is one your customers have never seen before.",{"id":2024,"title":2025,"titles":2026,"content":2027,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fhidden-plans#why-hidden-plans-work","Why Hidden Plans Work",[128],"A customer who is about to cancel has already decided your current plans don't fit. But an offer that feels exclusive and tailored to their exact pain point creates a reason to reconsider. A few examples of what a hidden plan can look like: A \"Lite\" plan for low-usage customers.A stripped-down plan at a lower price lets them stay without paying for what they don't use. A longer billing cycle with a built-in discount.An annual plan at a meaningful discount, only available at the moment of cancellation. A seasonal plan for intermittent users.A plan tied to specific usage periods (holidays, tax season, school terms) that feels built for them. A hybrid plan combining features from different tiers.Cherry-pick the features a segment of customers actually needs to close the gap between your standard offerings and what they want.",{"id":2029,"title":1210,"titles":2030,"content":422,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fhidden-plans#configuration",[128],{"id":2032,"title":2033,"titles":2034,"content":2035,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fhidden-plans#step-1-create-the-plan-in-your-payment-provider","Step 1: Create the Plan in Your Payment Provider",[128,1210],"Hidden plans need to exist in your payment provider before Churnkey can offer them. In Stripe: Go to your Stripe Dashboard and navigate to Product catalogClick Add product (or add a new price to an existing product)Fill in the plan details: name, price, billing intervalSave the product Tip: Use a clear naming convention for hidden plans in your payment provider (e.g., \"Retention - Lite Monthly\" or \"Hidden - Annual Discount\") so your team can identify them in the billing dashboard. This is an internal name only. You can set a separate customer-facing nickname in Churnkey's Plan Switching Offer Details.",{"id":2037,"title":2038,"titles":2039,"content":2040,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fhidden-plans#step-2-configure-the-offer-in-churnkey","Step 2: Configure the Offer in Churnkey",[128,1210],"Once the plan exists in your payment provider, configure it as a Switch Subscription Plan offer inside your cancel flow. Navigate to Cancel Flows > FlowsSelect the step where you want to present the hidden offer (Initial Offer, Cancellation Survey, or Final Offer)Choose \"Switch Subscription Plan\" as the offer typeSelect your hidden plan from the dropdown. It pulls directly from your payment provider's plan list. Then customize how the plan appears to customers. Go to Cancel Flows > Settings and scroll to Plan Switching Offer Details. For each hidden plan, you can set: Customer-Facing Nickname: Give it a name that communicates value, not an internal product ID (e.g., \"Essential Plan\" or \"Flexible Annual\"). Tagline: A short line that tells the customer why this plan exists. Example: \"Core features at a price that works for you.\" Feature list: Highlight what's included. Focus on the features that matter most to the customer segment you're targeting. Pricing display: Use the MSRP field to show a crossed-out \"regular\" price if you want to emphasize the savings. The Price Override field lets you display custom pricing text. For the full set of configuration options, refer to the Switch Subscription Plan documentation.",{"id":2042,"title":578,"titles":2043,"content":2044,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fhidden-plans#best-practices",[128],"Create plans for specific cancellation reasons. Look at your cancellation survey data first. If \"too expensive\" dominates, a lower-priced plan fits. If \"I don't use it enough\" is common, a reduced-feature plan is more appropriate. Keep it to one plan per offer step. Presenting a single, focused recommendation converts better than showing multiple options. Make the exclusivity clear in your copy. Use phrases like \"available only for existing customers\" or \"not on our pricing page\" to reinforce the sense of a special deal. Match the hidden plan to the customer's current billing currency. Churnkey automatically filters plans by currency, but make sure you've created the hidden plan with the right currency in your payment provider. Otherwise, it won't appear in the dropdown for customers using a different currency. Test with Preview and Test Mode before going live. Use the Preview button to check the visual presentation, and Test Mode to verify the plan switch applies correctly in your payment provider's test environment.",{"id":133,"title":132,"titles":2046,"content":2047,"level":368},[],"Temporarily suspend subscription payments without canceling the service",{"id":2049,"title":5,"titles":2050,"content":2051,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fpause-subscription#overview",[132],"The Pause Subscription feature allows customers to temporarily suspend their subscription without canceling it. This is particularly useful for businesses that experience seasonal fluctuations or need to accommodate temporary customer inactivity.",{"id":2053,"title":1210,"titles":2054,"content":2055,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fpause-subscription#configuration",[132],"The Pause Subscription feature can be implemented in any of the three offer stages of your Cancel Flow (Initial Offer, Cancellation Survey, or Final Offer). Before implementing, you'll need to configure the offer settings: Go to Cancel Flows > Settings > Offer Redemption SettingsConfigure the cooldown period between pause offers\nThis is the minimum time a customer must wait before seeing another pause offerThe total waiting period is calculated as: Pause Duration + Cooldown PeriodExample: If you set a 3-month cooldown and a customer chooses a 2-month pause, they'll need to wait 5 months before seeing another pause offer",{"id":2057,"title":578,"titles":2058,"content":2059,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fpause-subscription#best-practices",[132,1210],"Limit pause duration to a maximum of 3 months.\nWhile a 1-month pause is often the most effective option, you can offer up to 3 months if needed. This timeframe gives customers enough time to reconsider while maintaining their connection to your service. Longer pauses might lead to reduced engagement and lower chances of retention. Set a reasonable cooldown period.\nA 3-month cooldown is recommended to prevent abuse while still allowing customers to use the pause offer when genuinely needed. This balance helps maintain the offer's effectiveness as a retention tool. Use the pause offer as a first line of defense.\nPosition it early in your cancel flow, before more aggressive retention tactics. This approach respects the customer's initial decision while giving them a gentle opportunity to reconsider. Monitor and adjust based on your data.\nTrack how customers use the pause offer and their behavior after returning. This data will help you fine-tune the duration and cooldown period to maximize retention while maintaining a positive customer experience. Consider your customer's journey.The pause offer works best when it aligns with your customer's natural usage patterns. For example, if your service is seasonal, you might want to adjust the cooldown period to account for these patterns.",{"id":2061,"title":2062,"titles":2063,"content":2064,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fpause-subscription#subscription-pause-in-action","Subscription Pause in Action",[132],"When a customer accepts a pause offer, Churnkey automatically communicates with Stripe to pause the payment collection on their subscription. As an example: Customer's billing date: Every 5th of the monthJanuary 10th: Customer triggers the Cancel FlowJanuary 10th: Accepts a 2-month pause offerChurnkey immediately goes to Stripe and Pause the Payment Collection until March 9thMarch 9th: Pause period endsApril 5th: Renewal date, customer is billed On Stripe, the subscription will look as:",{"id":2066,"title":2067,"titles":2068,"content":2069,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fpause-subscription#skip-behavior","Skip Behavior",[132,2062],"The Pause Offer will be automatically skipped in two scenarios: Cooldown Period\nThe Pause Offer cannot be used again until the cooldown period has ended. This period is calculated as: The duration of the last accepted pausePlus the cooldown period you've configured in your Offer Redemption Settings Example: Your cooldown period is set to 3 monthsA customer with a monthly subscription (renews on the 5th)On January 10th, they trigger the cancel flow and accept a 1-month pauseTheir subscription is paused until February 9thThe total cooldown period ends on May 9th (1 month pause + 3 months cooldown)If they try to use the pause offer again before May 9th, it will be automatically skipped Non-Monthly Subscriptions\nThe Pause Offer is currently only available for monthly subscriptions. If a customer has a non-monthly subscription (e.g., quarterly, annual), the offer will be automatically skipped. When the Pause Offer is skipped, your customer will seamlessly move to the next step in your cancel flow without any interruption to their experience. Important Note: When initializing Churnkey with only a customerId, the Pause Offer will be applied to all active subscriptions. However, if any of the customer's subscriptions are non-monthly, the offer will be skipped for those subscriptions. Learn more about this behavior in our Customer ID vs Subscription ID guide.",{"id":2071,"title":2072,"titles":2073,"content":2074,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fpause-subscription#use-cases","Use Cases",[132],"While the pause subscription feature can be valuable for any subscription business, certain industries and business models benefit more from this retention strategy. Here are specific scenarios where implementing a pause option has proven particularly effective in reducing churn and maintaining customer relationships. Business TypeUse CaseWhy it worksRecommended StageEducational B2CLearning PlatformsOffer pauses during summer vacations or school breaksStudents or parents may not use the platform during holidays, but plan to return. A pause option keeps them from cancelingInitial Offer  Customers often plan their breaks in advanceFitness & Wellness AppsSeasonal Drop-OffAllow users to pause during winter holidays, travel periods, or injury recoveryMany users fall off routines temporarily and plan to return. Pausing feels supportive and reduces churnInitial Offer  Catch users before they fully disengageContent & StreamingBudget-Conscious ViewersWhen users cite financial reasons, offer a 1–2 month pause with no paymentRetains users who enjoy the content but need short-term reliefCancellation Survey  Target based on financial reasonsSaaS B2BFreelancers or Small TeamsOffer a pause option when clients enter a slow business period or shift projectsHelps retain customers during quiet cycles without pushing them to cancel and lose their previously built workflowsFinal Offer  Business decisions often require more considerationSubscription BoxesTravel or Lifestyle InterruptionsLet customers pause boxes when they're out of town or have too much inventoryKeeps the customer subscribed and avoids frustration from receiving unwanted productsInitial Offer  Prevent unwanted deliveries earlyDigital PublishingExam Season or Life EventsLet readers pause during intense life periods (e.g., exams, childbirth, moving)Prevents emotional, short-term cancellations with long-term consequencesCancellation Survey  Understand the specific life eventLanguage Learning AppsUser Fatigue or BurnoutPrompt a pause offer when users stop engaging or express feeling overwhelmedShows empathy and encourages return when motivation is backCancellation Survey  Target based on engagement patterns",{"id":2076,"title":1159,"titles":2077,"content":422,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fpause-subscription#faqs",[132],{"id":2079,"title":2080,"titles":2081,"content":2082,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fpause-subscription#how-does-the-pause-subscription-work","How does the pause subscription work?",[132,1159],"The pause subscription feature allows customers to temporarily suspend their subscription without canceling it. During the pause period, customers won't be charged, and they can resume their subscription at any time.",{"id":2084,"title":2085,"titles":2086,"content":2087,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fpause-subscription#can-customers-pause-their-subscription-multiple-times","Can customers pause their subscription multiple times?",[132,1159],"Yes, customers can pause their subscription multiple times, but there is a cooldown period between pauses. The cooldown period is configured in the Offer Redemption Settings.",{"id":2089,"title":2090,"titles":2091,"content":2092,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fpause-subscription#what-happens-when-the-pause-period-ends","What happens when the pause period ends?",[132,1159],"When the pause period ends, the subscription automatically resumes, and the customer is charged for the next billing cycle. Customers are notified before the pause period ends to ensure they are aware of the upcoming charges.",{"id":2094,"title":2095,"titles":2096,"content":2097,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fpause-subscription#can-customers-cancel-their-subscription-during-the-pause-period","Can customers cancel their subscription during the pause period?",[132,1159],"Yes, customers can cancel their subscription at any time, even during the pause period. The cancellation will take effect immediately, and they won't be charged for the next billing cycle.",{"id":2099,"title":2100,"titles":2101,"content":2102,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fpause-subscription#how-do-i-configure-the-pause-subscription-feature","How do I configure the pause subscription feature?",[132,1159],"To configure the pause subscription feature, go to Cancel Flows > Settings > Offer Redemption Settings. Here, you can set the minimum cooldown period between pauses and configure the pause offer to appear in the Initial Offer, Cancellation Survey, or Final Offer stages of the cancellation flow.",{"id":2104,"title":2105,"titles":2106,"content":2107,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fpause-subscription#when-a-customer-pauses-are-they-automatically-prevented-from-accessing-their-account-in-my-platform","When a customer pauses, are they automatically prevented from accessing their account in my platform?",[132,1159],"No. When a customer accepts a pause offer through the Cancel Flow, Churnkey applies the pause operation to their subscription in your payment provider (e.g., Stripe). However, this change doesn't automatically restrict their access to your platform. The pause operation only affects the subscription status in your billing system. Your application's access control logic remains separate and would need to be configured to check the subscription status. To automatically restrict access when customers pause their subscription, you can implement the Pause Wall. This feature allows Churnkey to handle the logic of checking subscription status and preventing paused customers from accessing your platform. The Pause Wall integrates with your application to automatically gate access based on subscription status, ensuring consistency between your billing system and platform access controls.",{"id":137,"title":136,"titles":2109,"content":2110,"level":368},[],"Allow customers to change to a different subscription plan during the cancellation flow to retain them with a more suitable option",{"id":2112,"title":5,"titles":2113,"content":2114,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#overview",[136],"Turn potential cancellations into plan optimizations. The Switch Subscription Plan feature transforms customer exit moments into opportunities for better plan alignment. When customers attempt to cancel due to pricing concerns, feature mismatches, or evolving needs, you can instantly present them with alternative plans that better suit their requirements. Key Benefits: Retain revenue at different tiers rather than losing customers entirelyAddress specific cancellation reasons with targeted plan alternativesReduce decision friction with streamlined plan comparison and switchingIncrease customer lifetime value by finding the right plan fit at the right time This retention strategy is particularly powerful for SaaS platforms, content services, and any subscription business with multiple pricing tiers or billing frequencies.",{"id":2116,"title":465,"titles":2117,"content":2118,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#how-it-works",[136],"When a customer encounters a Switch Subscription Plan offer in your cancel flow, they'll see a selection of alternative plans you've configured. Each plan is presented with: Plan name and pricing informationKey features and highlights that differentiate the planPricing details including any promotional pricingA \"Switch Plan\" button to confirm the change Customers can review the available options and choose a plan that better matches their needs, potentially preventing churn by offering a more suitable subscription tier.",{"id":2120,"title":2121,"titles":2122,"content":2123,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#before-you-begin","Before You Begin",[136],"Before setting up plan switching, ensure you have: Active cancel flows configured in your Churnkey dashboardMultiple subscription plans created and active in your billing provider (Stripe, Chargebee, Paddle, or Maxio)Billing provider integration properly connected and testedUnderstanding of your customer segments and which plans would be appropriate alternatives for different cancellation reasons Supported Billing Providers: Plan switching is available for Stripe, Chargebee, Paddle, and Maxio. The feature automatically appears in your offer types only if your billing provider supports subscription plan modifications.",{"id":2125,"title":1210,"titles":2126,"content":422,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#configuration",[136],{"id":2128,"title":2129,"titles":2130,"content":2131,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#setting-up-plan-switching","Setting Up Plan Switching",[136,1210],"The Switch Subscription Plan feature can be implemented in any of the three offer stages of your Cancel Flow: Initial Offer - Present plan options immediately when customers attempt to cancelCancellation Survey - Target specific plan changes based on cancellation reasonsFinal Offer - Make a last attempt with compelling plan alternatives",{"id":2133,"title":2134,"titles":2135,"content":2136,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#dashboard-configuration","Dashboard Configuration",[136,1210],"Navigate to Cancel Flows > FlowsSelect the step where you want to add the plan switching offerChoose \"Switch Subscription Plan\" from the offer typesConfigure which plans customers can switch to:\nSelect from your active subscription plansWhile you can offer multiple plans, we recommend starting with a single plan option for optimal conversion rates",{"id":2138,"title":2139,"titles":2140,"content":2141,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#advanced-plan-configuration","Advanced Plan Configuration",[136,1210],"For detailed customization of how plans appear to customers, you'll need to configure plan display settings in the Advanced Settings: Navigate to Cancel Flows > Settings (or visit app.churnkey.co\u002Fcancellation\u002Fadvanced-settings)Scroll to the Plan Switching Offer Details section In this section, you can configure detailed presentation settings for each subscription plan that you want to offer as an alternative during cancellation flows. Customer-Facing Nickname (up to 40 characters) Override the plan name from your billing provider with a customer-friendly nameExample: \"Professional Plan\" instead of \"prod_abc123\"This is the primary name customers will see Tagline (up to 60 characters) Add a short description that explains the plan's value propositionExample: \"For professional copywriters\" or \"Perfect for growing teams\"Appears directly under the plan name Pricing Display Options: MSRP (optional) - Show a crossed-out \"regular\" price to emphasize savingsPrice Override (optional) - Display custom pricing text instead of the billing provider's priceIf neither is set, the actual price from your billing provider will be displayed Feature List (up to 8 features per plan) Highlight the key benefits and features of each planEach feature is displayed as a separate line under \"Highlights\"Example features: \"1,000 Email Credits\", \"Advanced Analytics\", \"Priority Support\"Use the \"+ Add another line...\" button to add more features",{"id":2143,"title":2144,"titles":2145,"content":2146,"level":958},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#selecting-plans-to-configure","Selecting Plans to Configure",[136,1210,2139],"Add plans to your available options: Use the dropdown to select from your active subscription plansPlans are automatically loaded from your connected billing providerYou can add multiple plans, but we recommend offering only 1 plan to reduce decision paralysis and improve conversion rates",{"id":2148,"title":2149,"titles":2150,"content":2151,"level":958},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#live-preview","Live Preview",[136,1210,2139],"As you configure each plan, a live preview shows exactly how it will appear to customers during the cancellation flow: Plan name and pricing with any overrides appliedTagline positioned under the plan nameFeatures listed under the \"Highlights\" sectionSwitch Plan button styled with your brand colors",{"id":2153,"title":2154,"titles":2155,"content":422,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#customer-experience","Customer Experience",[136],{"id":2157,"title":2158,"titles":2159,"content":2160,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#plan-selection-interface","Plan Selection Interface",[136,2154],"When customers see the plan switching offer, they experience: Clear plan presentation with pricing and key featuresSide-by-side comparison when multiple plans are offered (if more than 1 plan is configured)Highlighted benefits showing what they'll gain or saveSimple selection process with a single click to switch plans",{"id":2162,"title":2163,"titles":2164,"content":2165,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#plan-switching-process","Plan Switching Process",[136,2154],"After a customer selects a new plan: Churnkey communicates with your billing provider to process the plan switchThe plan switch is applied according to your provider settings (immediate or end of billing period)The customer receives confirmation of their new planThe cancellation flow completes successfully with the retention recorded",{"id":2167,"title":578,"titles":2168,"content":2169,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#best-practices",[136],"Offer plans strategically based on cancellation reasons.\nUse the Cancellation Survey step to understand why customers want to cancel, then present targeted plan options. For example, if they select \"Too expensive,\" offer a lower-tier plan or a discounted option. Start with a single, focused plan recommendation.Start with a single plan recommendation to create focus and reduce cognitive load. A single, well-targeted plan recommendation typically yields better conversion rates than multiple options, as it eliminates decision paralysis and guides customers toward a clear next step. While you can offer multiple plans if your business model requires more choice, most businesses achieve optimal results with a strategic single-plan approach. Use the Advanced Plan Configuration to optimize presentation.\nTake advantage of the Plan Switching Offer Details settings to create compelling plan presentations. Use customer-friendly nicknames instead of technical plan IDs, write compelling taglines that address pain points, and highlight the most valuable features for each target audience. Leverage pricing psychology with MSRP and overrides.\nUse the MSRP field to show crossed-out \"regular\" pricing, making the actual price feel like a better deal. Consider using price overrides to display promotional pricing or simplified pricing (e.g., \"$49\u002Fmonth\" instead of \"$49.99\u002Fmonth\"). Craft features that address cancellation reasons.\nIn your plan configurations, focus on features that directly address common cancellation reasons. If customers often cancel due to limits, highlight higher allowances. If they leave for better support, emphasize support tier differences. Offer exclusive or \"secret\" plans during cancellation.\nCreate special plans that are only available during the cancellation flow and not publicly visible on your pricing page. These exclusive offers can include discounted rates, unique feature combinations, or special terms reserved for at-risk customers. This strategy works particularly well for customer segmentation – you might offer different exclusive plans to long-time customers, high-value accounts, or specific user types. The exclusivity creates urgency and makes customers feel valued, while giving you flexibility to retain customers with terms that wouldn't be sustainable for new acquisitions. Test different plan combinations.\nUse A\u002FB testing to determine which plan combinations are most effective at preventing cancellations for different customer segments. Position appropriately in your flow. Early placement works well for price-sensitive customersSurvey-based targeting allows for personalized plan recommendationsFinal offer placement can include your most compelling plan options",{"id":2171,"title":2172,"titles":2173,"content":422,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#integration-options","Integration Options",[136],{"id":2175,"title":2176,"titles":2177,"content":2178,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#default-churnkey-handling","Default Churnkey Handling",[136,2172],"By default, Churnkey handles the entire plan switching process: Presents the configured plans to the customerProcesses the plan change through your billing providerUpdates the customer's subscription immediatelyProvides confirmation to the customer",{"id":2180,"title":2072,"titles":2181,"content":2182,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#use-cases",[136],"The Switch Subscription Plan feature is valuable across various business models and scenarios: Business TypeUse CaseWhy it worksRecommended StageSaaS PlatformsMultiple TiersOffer downgrade when customers cite cost as cancellation reasonRetains revenue at a lower tier rather than losing customer entirelyCancellation Survey  Target based on \"too expensive\" responsesContent PlatformsDifferent Access LevelsPresent basic plan when customers cancel premium subscriptionKeeps customers engaged with core content while reducing cost burdenInitial Offer  Immediate alternative to full cancellationEducational ServicesCourse PackagesSwitch from annual to monthly billing when cash flow is an issueRemoves barrier of large upfront payment while maintaining subscriptionCancellation Survey  Target billing frequency concernsSoftware ToolsUsage-Based TiersMove customers to lower usage tiers when they're not utilizing full capacityBetter matches plan to actual usage, improving value perceptionFinal Offer  Data-driven recommendation based on usageSubscription BoxesFrequency OptionsChange from monthly to quarterly delivery for budget-conscious customersReduces monthly cost while maintaining the subscription relationshipInitial Offer  Immediate cost-reduction optionB2B ServicesSeat-Based PricingOffer plan with fewer seats when team size has decreasedAccommodates changing business needs without losing the accountCancellation Survey  Understand team size changes",{"id":2184,"title":2185,"titles":2186,"content":2187,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#proration-handling","Proration Handling",[136],"Plan changes handle proration and timing differently based on your billing provider: Stripe Plan changes occur immediately when a customer switches plansProration behavior can be enabled or disabled in your Churnkey dashboardNavigate to Cancel Flows > Settings to configure proration preferencesWhen proration is enabled, customers are charged or credited for the time difference between plansWhen disabled, the new plan takes effect immediately without mid-cycle adjustments Chargebee \u002F Paddle You can choose when plan changes take effect:\nImmediately - Plan change happens right away with automatic proration calculationEnd of billing term - Plan change is scheduled for the next billing cycleChargebee automatically calculates proration amounts based on your account settingsThe timing option can be configured in Cancel Flows > Settings Example: Plan Change with ProrationCustomer is on a $30\u002Fmonth plan with 15 days remaining in their billing cycleCustomer attempts to cancel but accepts a switch to a $20\u002Fmonth plan insteadWith proration enabled, Stripe calculates the adjustment:\nRefund: $15 credit for unused time on the $30 plan (15 days × $1\u002Fday)Charge: $10 for the new $20 plan for the remaining 15 days (15 days × $0.67\u002Fday)Net result: $5 credit applied to the customer's account",{"id":2189,"title":2190,"titles":2191,"content":2192,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#currency-considerations","Currency Considerations",[136],"Currency handling during plan switching varies by billing provider: Stripe When using automatic currency detection, plans are displayed in the customer's local currencyChurnkey automatically filters available plans to match the customer's subscription currencyCustomers only see compatible plans in their billing currency Other Providers (Chargebee, Paddle, etc.) Plans have a fixed currency configured in your billing providerThe configured plan currency is what customers will see during the switch offerPlan filtering ensures customers only see plans that match their current subscription currency This approach prevents currency mismatches and ensures a smooth plan switching experience regardless of your billing provider setup.",{"id":2194,"title":2195,"titles":2196,"content":2197,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#analytics-and-reporting","Analytics and Reporting",[136],"Plan switching success is tracked in your Churnkey analytics dashboard: Plan Change Rate: Percentage of customers who switch vs. those who see the offerRevenue Impact: Comparison of new plan value vs. original subscriptionPlan Distribution: Which alternative plans are most popularRetention Attribution: Plan switches counted as successful retentions Use this data to optimize your plan offerings and improve retention rates over time.",{"id":2199,"title":2200,"titles":2201,"content":422,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#troubleshooting","Troubleshooting",[136],{"id":2203,"title":2204,"titles":2205,"content":2206,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#plan-not-appearing-in-options","Plan Not Appearing in Options",[136,2200],"Verify plan is active in your billing provider (Stripe, Chargebee, Paddle, or Maxio)Check currency compatibility - plans must match the customer's subscription currencyEnsure plan is properly configured in Cancel Flows > Settings > Plan Switching Offer DetailsConfirm billing provider integration is active and properly connectedCheck plan type compatibility - some billing providers have restrictions on which plan types can be switched",{"id":2208,"title":2209,"titles":2210,"content":2211,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#plan-configuration-issues","Plan Configuration Issues",[136,2200],"Custom plan IDs not loading: If you're using manual plan IDs (like price_abc123), ensure they exist in your billing provider and are activePreview not updating: Changes are saved automatically - refresh the page if the live preview isn't reflecting your updatesMissing plan details: Plans must be configured in Advanced Settings to appear with custom nicknames, taglines, and features",{"id":2213,"title":2214,"titles":2215,"content":2216,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#plan-switching-failing","Plan Switching Failing",[136,2200],"Review billing provider logs for specific error messagesVerify the plan configuration hasn't been removed from your payment provider",{"id":2218,"title":2219,"titles":2220,"content":2221,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#display-issues","Display Issues",[136,2200],"Plans showing technical IDs: Configure customer-facing nicknames in the Plan Change Offer Details sectionMissing features or taglines: Ensure these are filled out in the plan configuration formPricing appears incorrect: Check your Price Override settings or MSRP configuration",{"id":2223,"title":2224,"titles":2225,"content":2226,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#customer-seeing-wrong-currency-plans","Customer Seeing Wrong Currency Plans",[136,2200],"Verify customer's original subscription currency in your billing providerContact support if currency detection appears incorrect",{"id":2228,"title":1159,"titles":2229,"content":422,"level":374},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#faqs",[136],{"id":2231,"title":2232,"titles":2233,"content":2234,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#which-billing-providers-support-plan-switching","Which billing providers support plan switching?",[136,1159],"Plan switching is supported by Stripe, Chargebee, Paddle, and Maxio. The feature automatically appears in your offer types only if your billing provider supports subscription plan modifications.",{"id":2236,"title":2237,"titles":2238,"content":2239,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#can-customers-switch-between-different-billing-frequencies","Can customers switch between different billing frequencies?",[136,1159],"Yes, if your billing provider supports it. For example, customers can switch from monthly to annual plans or vice versa, depending on what plans you've configured as options.",{"id":2241,"title":2242,"titles":2243,"content":2244,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#can-i-offer-custom-pricing-during-plan-switches","Can I offer custom pricing during plan switches?",[136,1159],"Plan switching uses your existing subscription plans as configured in your billing provider. For custom pricing or temporary discounts, consider using the Apply Coupon offer type instead or in combination with plan switching.",{"id":2246,"title":2247,"titles":2248,"content":2249,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#how-do-i-track-the-success-of-my-plan-switch-offers","How do I track the success of my plan switch offers?",[136,1159],"Churnkey's analytics dashboard shows plan switch conversion rates, revenue impact, and popular plan choices.",{"id":2251,"title":2252,"titles":2253,"content":2254,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#can-customers-switch-plans-multiple-times","Can customers switch plans multiple times?",[136,1159],"We don't have a cooldown configuration for offering plan switches. If your configured flows allow customers to see a plan switching offer multiple times, they'll see it, unless the plan offered is the exact same they currently have.",{"id":2256,"title":2257,"titles":2258,"content":2259,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#where-do-i-configure-how-plans-look-to-customers","Where do I configure how plans look to customers?",[136,1159],"Plan presentation is configured in Cancel Flows > Settings under the Plan Change Offer Details section. Here you can set customer-friendly names, taglines, feature highlights, and custom pricing displays for each plan.",{"id":2261,"title":2262,"titles":2263,"content":2264,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#can-i-show-different-pricing-than-whats-in-my-billing-provider","Can I show different pricing than what's in my billing provider?",[136,1159],"Yes, using the Price Override field in the plan configuration, you can display custom pricing text. You can also use the MSRP field to show crossed-out \"regular\" pricing. The actual billing will still use your billing provider's pricing structure.",{"id":2266,"title":2267,"titles":2268,"content":2269,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#how-many-features-can-i-highlight-per-plan","How many features can I highlight per plan?",[136,1159],"Each plan can have up to 8 feature highlights. These appear under the \"Highlights\" section when customers view plan options during the cancellation flow.",{"id":2271,"title":2272,"titles":2273,"content":2274,"level":417},"\u002Fcancel-flows\u002Foffers\u002Fswitch-subscription#do-i-need-to-configure-every-plan-in-advanced-settings","Do I need to configure every plan in Advanced Settings?",[136,1159],"No, but configuring plans improves the customer experience significantly. Plans without configuration will show with their technical IDs and basic pricing information from your billing provider, which may be less compelling to customers.",{"id":141,"title":140,"titles":2276,"content":2277,"level":368},[],"Extend customer trial periods to reduce churn and increase conversion rates during the critical trial-to-paid transition",{"id":2279,"title":2280,"titles":2281,"content":2282,"level":368},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#what-trial-extensions-do-for-your-business","What Trial Extensions Do for Your Business",[],"Trial Extension offers provide customers with additional time to experience your product's value before making a final cancellation decision. This retention mechanism is particularly powerful for businesses offering more complex products or services that require more time to be carefully evaluated before a purchase decision. Unlike discount offers that reduce revenue, trial extensions preserve your full pricing while providing additional time for customers to experience value. This approach maintains pricing integrity while addressing the most common reason for trial cancellations: customers feeling they haven't had enough time to fully evaluate the product. Customers who extend their trials convert to paid subscriptions at higher rates compared to those who cancel immediately, creating a powerful retention mechanism that doesn't compromise your revenue model.",{"id":2284,"title":2285,"titles":2286,"content":2287,"level":374},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#how-trial-extensions-work","How Trial Extensions Work",[2280],"The trial extension process involves three main components working together to provide additional evaluation time:",{"id":2289,"title":1861,"titles":2290,"content":2291,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#_1-customer-interaction-flow",[2280,2285],"When a trial customer attempts to cancel their subscription, the cancel flow presents a trial extension offer based on your configuration settings. The customer can accept the extension to continue their trial for the specified additional period, or decline the offer to proceed with the normal cancellation process.",{"id":2293,"title":2294,"titles":2295,"content":2296,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#_2-trial-period-extension","2. Trial Period Extension",[2280,2285],"Once a customer accepts a trial extension offer, Churnkey immediately extends the trial period through your payment provider's API. The customer's original trial end date is updated to reflect the additional time, and they receive immediate confirmation of their extended trial with clear details about the new trial end date. Consider implementing the handleTrialExtension callback if you need custom behavior when trial extensions are accepted.",{"id":2298,"title":1871,"titles":2299,"content":2300,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#_3-analytics-and-tracking",[2280,2285],"The system automatically tracks comprehensive metrics such as Trial Extension Acceptance Rate and Boosted Revenue after Trial Extension. This data allows you to monitor offer performance and optimize your trial strategy based on your customers' behavior.",{"id":2302,"title":2303,"titles":2304,"content":2305,"level":374},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#setting-up-your-first-trial-extension-offer-5-minutes","Setting Up Your First Trial Extension Offer (5 Minutes)",[2280],"A straightforward approach to get started is offering a 14-day extension. Based on customer behavior patterns, 14 days provides sufficient time for customers to experience value while maintaining urgency around the trial process.",{"id":2307,"title":2308,"titles":2309,"content":2310,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#step-1-add-trial-extension-offer-to-your-cancel-flow","Step 1: Add Trial Extension Offer to Your Cancel Flow",[2280,2303],"Navigate to Cancel Flows > Flows, then edit one of your flows. In the Cancel Flow builder, locate the step where you want to offer your Trial Extension. While editing, in the Cancel Flow section, select Trial Extension from the dropdown menu below Choose Offer Type. Trial extensions can be added as: Initial OfferFinal Offer Trial extensions can also be tied to specific Cancellation Survey answers through the Offers section. Once you've selected the Trial Extension Offer step, you'll see configuration options appear where you can specify the number of days to extend the trial period.",{"id":2312,"title":2313,"titles":2314,"content":2315,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#step-2-configure-your-extension-duration","Step 2: Configure Your Extension Duration",[2280,2303],"Configure the trial extension duration to determine how much additional time customers receive to evaluate your product. Here are recommended starting settings that work well for most businesses: Extension Duration: Set to 14 days. This timeframe provides sufficient time for customers to implement and experience your product's value while maintaining appropriate urgency around the evaluation process. Why 14 days works well: Gives customers adequate time to overcome implementation challengesAllows for proper feature exploration and value realizationMaintains evaluation urgency without being too restrictiveAligns with typical customer onboarding and adoption timelines The platform allows you to offer up to 365 days in a Trial Extension. However, we recommend offering up to 14 days based on customer behavior patterns and retention best practices.",{"id":2317,"title":1891,"titles":2318,"content":2319,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#step-3-customize-your-offer-presentation",[2280,2303],"The way you present your trial extension offer directly impacts acceptance rates. Configure the presentation elements that customers will see during their cancellation attempt. Header Text: Create compelling header text that emphasizes the additional evaluation opportunity. Example: \"Get 14 more days to explore everything we offer.\" This immediately communicates the benefit (additional time) and the opportunity (complete evaluation). Description: Provide clear context about the extension terms and what customers can accomplish during the additional time. Example: \"Continue your trial for 14 more days with full access to all features—no payment required until new trial end date.\" This helps customers understand exactly what they're receiving and reduces uncertainty about billing.",{"id":2321,"title":1896,"titles":2322,"content":2323,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#step-4-test-your-configuration",[2280,2303],"Before customers see your trial extension offer, test it using two different approaches depending on what you want to validate.",{"id":2325,"title":1901,"titles":2326,"content":2327,"level":958},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#testing-approach-1-visual-preview-look-and-feel",[2280,2303,1896],"Use the Preview button in the top right corner of the Cancel Flow builder to see exactly how your trial extension offer will appear to customers. This preview mode shows the complete customer experience including your header text, description, extension duration, and button styling. What this tests: Visual presentation and messagingOverall flow and user experienceExtension duration display Use this when: You want to review and refine the customer-facing presentation before testing functionality.",{"id":2329,"title":2330,"titles":2331,"content":2332,"level":958},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#testing-approach-2-test-mode-trial-extension-application","Testing Approach 2: Test Mode (Trial Extension Application)",[2280,2303,1896],"Use Test Mode to validate that Churnkey can correctly extend trial periods for actual subscriptions in your payment provider's test environment. Enable Test Mode in your Cancel Flow settings and initiate a cancellation flow with a test customer who has an active trial. What this tests: Integration with your payment providerTrial extension functionalityCustomer data refresh after extensionSession completion and tracking Critical validation steps: Verify the trial extension applies correctly in your payment provider's test dashboardConfirm that Churnkey displays appropriate success\u002Fconfirmation messagingCheck that the session is properly tracked in your analyticsValidate that customer data reflects the new trial end date Use this when: You want to ensure the technical integration works correctly before going live with real customers.",{"id":2334,"title":2335,"titles":2336,"content":2337,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#step-5-anti-abuse-protection","Step 5: Anti-Abuse Protection",[2280,2303],"Trial extensions are limited to one per customer in live mode to prevent abuse while ensuring genuine customers receive the additional evaluation time they need. How the limit works: When a customer accepts a trial extension, Churnkey keeps track of it internally. If the same customer attempts to cancel again during the extended trial period, the trial extension offer will not appear. This prevents customers from repeatedly extending trials to avoid payment while still allowing legitimate users who need more time to fully evaluate your product. Example scenario: Customer john@example.com (Stripe ID: cus_123) starts a 14-day trialAfter 10 days, they attempt to cancel and see the trial extension offerThey accept the 14-day extension, extending their trial by 14 additional daysIf they attempt to cancel again during the extended trial period, the trial extension offer will not appearInstead, they'll see other configured offers (discounts, pause options) or proceed directly to cancellation Test mode behavior: When testing your trial extension configuration in test mode, you can accept trial extensions multiple times for the same customer. This allows you to thoroughly test your configuration without the one-extension limit interfering with your testing process. In test mode, this behavior can be changed by using report: true while triggering the Cancel Flow. Once you switch to live mode, the one-extension limit automatically applies to protect against abuse in your production environment.",{"id":2339,"title":1916,"titles":2340,"content":2341,"level":374},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#understanding-your-results",[2280],"Once your trial extensions are live, monitor these key metrics to understand performance and identify optimization opportunities.",{"id":2343,"title":1921,"titles":2344,"content":2345,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#key-metrics-to-track",[2280,1916],"Your trial extension metrics tell you whether the feature is working and where to focus optimization efforts. Monitor these metrics to understand performance and identify improvement opportunities. Extension Acceptance Rate: The percentage of trial customers who see your extension offer and choose to accept it. This reveals how compelling your offer presentation is and whether the extension duration aligns with customer needs. Low acceptance rates suggest messaging problems or insufficient extension duration, while high acceptance rates indicate the offer resonates with customers attempting to cancel. Trial-to-Paid Conversion Rate: The conversion rate for customers who accepted trial extensions compared to those who canceled without extending. This metric demonstrates the actual retention value of trial extensions beyond just delaying cancellation. Focus on this as your primary success metric—acceptance without conversion means the feature isn't driving real business impact. Extended Trial Engagement: How actively customers use your product during the extended trial period compared to their original trial period. Track key engagement metrics like feature usage, login frequency, and time spent in-product. Higher engagement during extensions strongly correlates with conversion likelihood and helps identify customers who genuinely benefit from additional evaluation time. Extended Trial Utilization: The percentage of extended trial customers who remain active throughout the additional period versus those who abandon the product despite accepting the extension. Low utilization indicates customers are extending trials without serious evaluation intent, suggesting you need stronger qualification criteria or better onboarding during extensions. Time to Conversion: Whether extended trial customers convert early, mid-way, or late in their extended period. This data helps optimize extension duration—if most conversions happen in the first week of a 14-day extension, shorter extensions might maintain urgency while reducing non-converting extension time. Performance benchmarks vary significantly based on your industry, original trial length, product complexity, and customer onboarding effectiveness. A SaaS analytics platform with a 7-day trial will see different metrics than a CRM with a 30-day trial. Rather than comparing against external benchmarks, establish your own performance baselines and track improvement over time.",{"id":2347,"title":1931,"titles":2348,"content":2349,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#common-optimization-scenarios",[2280,1916],"High acceptance rates but low conversion improvement:\nYour extension duration might be too short for customers to experience meaningful value. Consider testing longer extension periods to allow for deeper product engagement. Also analyze the most frequently selected Cancellation Survey answers to understand why customers are canceling. Low acceptance rates but good conversion from extensions:\nYour offer presentation might not be compelling enough to reach more customers. Test improved messaging that better communicates the value of additional evaluation time. Good acceptance rates but low engagement during extension:\nFocus on customer success during the extended trial period. Consider triggered email campaigns or in-app guidance to help customers maximize their additional trial time.",{"id":2351,"title":2352,"titles":2353,"content":2354,"level":374},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#optimizing-your-trial-extension-strategy-over-time","Optimizing Your Trial Extension Strategy Over Time",[2280],"Start with a simple approach, gather data, and optimize based on real customer behavior patterns.",{"id":2356,"title":2357,"titles":2358,"content":2359,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#month-1-establish-baseline-performance","Month 1: Establish Baseline Performance",[2280,2352],"Recommended Initial Settings: Extension Duration: 14 daysFocus: Ensure technical setup works correctly and gather baseline data What to monitor: Initial acceptance rates to establish your baselineTechnical functionality (trial extension application, analytics tracking)Customer engagement patterns during extended trials Success criteria: Trial extensions display correctly to trial customersExtensions apply successfully in your payment providerAnalytics accurately track acceptance\u002Fdecline rates and conversion outcomes",{"id":2361,"title":2362,"titles":2363,"content":2364,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#months-2-3-optimize-duration-and-messaging","Months 2-3: Optimize Duration and Messaging",[2280,2352],"Testing approach: A\u002FB test extension durations: 7 days vs 14 days vs 21 days to find your optimal timeframeTest presentation variations: benefit-focused vs urgency-focused messagingExperiment with positioning in your cancellation flow Optimization areas: Extension duration: Find the sweet spot between sufficient evaluation time and conversion urgencyHeader text: Test value-focused vs time-focused messagingDescription: Experiment with feature emphasis vs evaluation guidanceTiming: Try different positions in your cancellation flow Expected improvements: Acceptance rates should improve with better messaging and optimal durationConversion rates should become more predictable based on your customer behaviorCustomer engagement patterns during extensions become clearer",{"id":2366,"title":2367,"titles":2368,"content":2369,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#months-4-6-implement-advanced-features","Months 4-6: Implement Advanced Features",[2280,2352],"Enable sophisticated features: Customer segmentation: Different extension durations for different trial types or customer segmentsAdvanced testing: Run systematic A\u002FB tests across different customer cohorts Strategic refinements: Adjust extension durations based on product complexity or customer onboarding timelinesCreate conditional logic for different trial scenarios Performance optimization: Optimize for trial-to-paid conversion, not just immediate extension acceptanceBalance conversion effectiveness with trial program integrity",{"id":2371,"title":1956,"titles":2372,"content":2373,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#month-6-mature-strategy-continuous-improvement",[2280,2352],"Advanced implementations: Multiple extension options for different customer needsIntegration with customer success workflows during extended trialsPredictive modeling to identify customers most likely to benefit from extensions Continuous monitoring: Monthly performance reviews with product and sales teamsQuarterly trial strategy assessments including extension impactCustomer feedback analysis to refine extension positioning Long-term optimization: Extended trial onboarding improvements based on customer behaviorProduct experience optimization during extension periodsConversion forecasting incorporating trial extension strategy impact",{"id":2375,"title":2376,"titles":2377,"content":2378,"level":374},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#payment-provider-support","Payment Provider Support",[2280],"Trial extensions are supported across major payment providers with specific implementation details: Stripe: Full support for trial period extensions with immediate trial_end date updates. Extensions apply to all subscription types and billing models. Chargebee: Complete trial extension support with automatic trial period modification. Works with all subscription plans and billing configurations. Paddle: Full trial extension functionality with immediate trial period updates. Supports all subscription types and payment methods. Braintree: Trial extensions are not supported due to platform limitations. Customers using Braintree will not see trial extension offers in their cancellation flows.",{"id":2380,"title":2381,"titles":2382,"content":2383,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#integration-requirements","Integration Requirements",[2280,2376],"Active Trial Status: Customers must have an active trial with a future trial end date for extensions to be available. Valid Subscription State: The customer's subscription must be in a trial state within your payment provider for the extension to apply successfully.",{"id":2385,"title":1961,"titles":2386,"content":2387,"level":374},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#common-scenarios-solutions",[2280],"Use these troubleshooting guides to address typical trial extension challenges and optimize performance.",{"id":2389,"title":2390,"titles":2391,"content":2392,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#_1-my-trial-customers-dont-see-extension-offers","1. \"My trial customers don't see extension offers\"",[2280,1961],"Customer not on active trial Verify that the customer has an active trial subscription with a future end dateCheck that the customer hasn't already converted to a paid subscriptionEnsure the customer's trial hasn't already expired Payment provider configuration Verify that your payment provider supports trial extensions (Braintree is not supported)Validate that customer subscriptions are properly configured as trials Flow configuration issues Ensure the trial extension offer is configured in your cancel flowIf using a Segmented Flow, verify the customer matches the configured audience",{"id":2394,"title":2395,"titles":2396,"content":2397,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#_2-extensions-apply-but-customers-still-cancel","2. \"Extensions apply but customers still cancel\"",[2280,1961],"Extension duration insufficient Test longer extension periods to allow for deeper product evaluationConsider customer onboarding complexity when setting durationMonitor customer engagement during extended trial periods",{"id":2399,"title":269,"titles":2400,"content":422,"level":374},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#frequently-asked-questions",[2280],{"id":2402,"title":2403,"titles":2404,"content":2405,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#can-customers-extend-their-trial-multiple-times","Can customers extend their trial multiple times?",[2280,269],"No, customers can only extend their trial once per subscription in live mode. This prevents abuse while providing genuine evaluation opportunity for customers who need additional time.",{"id":2407,"title":2408,"titles":2409,"content":2410,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#what-happens-in-test-mode","What happens in test mode?",[2280,269],"Test mode allows multiple trial extensions for testing purposes and doesn't enforce the one-extension limit that applies in live mode.",{"id":2412,"title":2413,"titles":2414,"content":2415,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#how-quickly-are-trial-extensions-applied","How quickly are trial extensions applied?",[2280,269],"Trial extensions are applied immediately through your payment provider's API. Customer trial end dates are updated in real-time, and customers receive immediate confirmation of their extended trial period.",{"id":2417,"title":2418,"titles":2419,"content":2420,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#what-analytics-are-available-for-trial-extensions","What analytics are available for trial extensions?",[2280,269],"Churnkey tracks extension acceptance rates, trial-to-paid conversion performance, customer engagement during extended trials, and overall retention impact in your analytics dashboard.",{"id":2422,"title":2423,"titles":2424,"content":2425,"level":417},"\u002Fcancel-flows\u002Foffers\u002Ftrial-extension#can-i-offer-different-extension-durations-to-different-customers","Can I offer different extension durations to different customers?",[2280,269],"Yes, you can implement Segmented Cancel Flows to offer different extension durations based on customer characteristics, subscription type, or other factors.",{"id":150,"title":149,"titles":2427,"content":2428,"level":368},[],"Recover failed payment revenue, automatically. Utilize automated, customizable email campaigns to recover revenue that would otherwise be lost to failed payments.",{"id":2430,"title":2431,"titles":2432,"content":2433,"level":374},"\u002Ffailed-payment-recovery\u002Fpayment-recovery#dashboard","Dashboard",[149],"The Payment Recovery dashboard allows for everything from at-a-glance overview of bigger picture Payment Recovery progress to close monitoring of individual, active campaigns. In your Payment Recovery dashboard, monitor your recovery breakdowns and track active email campaigns that are in progress.",{"id":2435,"title":2436,"titles":2437,"content":2438,"level":417},"\u002Ffailed-payment-recovery\u002Fpayment-recovery#lifetime-performance","Lifetime Performance",[149,2431],"At a glance, see how many active campaigns there are, cumulative emails sent, and emails scheduled.",{"id":2440,"title":2441,"titles":2442,"content":2443,"level":374},"\u002Ffailed-payment-recovery\u002Fpayment-recovery#payment-recovery-advanced-settings","Payment Recovery Advanced Settings",[149],"Setup of the details of your campaigns in the Payment Recovery \u003C Advanced Settings page.",{"id":2445,"title":2446,"titles":2447,"content":2448,"level":417},"\u002Ffailed-payment-recovery\u002Fpayment-recovery#payment-recovery-setup","Payment Recovery Setup",[149,2441],"Configure your Payment Recovery experience, payment update flow, and email domain addresses.",{"id":2450,"title":2451,"titles":2452,"content":2453,"level":417},"\u002Ffailed-payment-recovery\u002Fpayment-recovery#bcc-dunning-emails","BCC Dunning Emails",[149,2441],"Send Payment Recovery emails to your CRM or support channels by automatically adding a BCC'd address to monitor your campaign, who is receiving these, and easily keep record where it matters.",{"id":2455,"title":2456,"titles":2457,"content":2458,"level":417},"\u002Ffailed-payment-recovery\u002Fpayment-recovery#stripe-settings-update","Stripe Settings Update",[149,2441],"We recommend turning off emails from Stripe but allowing for the Stripe Smart retries at 4 attempts per 30 days. For recommended Stripe settings, see our FAQ.",{"id":2460,"title":2461,"titles":2462,"content":2463,"level":374},"\u002Ffailed-payment-recovery\u002Fpayment-recovery#precision-retries","Precision Retries",[149],"A smarter retry schedule for failed cards. Precision retries are a scheduled failed payment retry system that takes advantage of days when payments are more likely to succeed. These days include traditional paydays such as the first and fourteenth of the month. Over time precision retries will adapt to the needs of your customer base. Enable and disable Precision Retries under Payment Recovery\u003CAdvanced Settings. Payment Recovery Advanced Settings page allows for campaign details setup, adding BCC emails of campaigns, Precision Retries, and Stripe Settings. Get started with Precision Retries today by reaching out to support@churnkey.co for more details.",{"id":2465,"title":2466,"titles":2467,"content":2468,"level":374},"\u002Ffailed-payment-recovery\u002Fpayment-recovery#campaign-initiation","Campaign Initiation",[149],"Payment recovery campaigns are initiated Learn more about customizing your Payment Recovery Campaigns → Contact us today at support@churnkey.co to get started with Payment Recovery.",{"id":154,"title":153,"titles":2470,"content":2471,"level":368},[],"Customize your Payment Recovery Campaigns to match the unique needs of your individual customers. Available for Stripe users, Churnkey Payment Recovery campaigns are customizable in nearly every way to help you reach your customers in a targeted, engaging, and credible approach. By running failed payment campaigns, you help your customers stay subscribed by making it easy to update their payment information.",{"id":2473,"title":2474,"titles":2475,"content":2476,"level":374},"\u002Ffailed-payment-recovery\u002Fcampaign-customization#segmented-campaigns","Segmented Campaigns",[153],"From the Payment Recovery | Campaigns page, update your Primary Dunning Campaign and add additional campaigns for better target the individual needs of your customers. Develop segmented campaigns to reach customers on a more personalized level. Segments can be created using predefined options like subscription plan, billing interval, subscription age, and more, all seamlessly integrated from Stripe. For even greater customization, segment customers using your own custom attributes, offering nearly limitless possibilities.",{"id":2478,"title":2479,"titles":2480,"content":422,"level":374},"\u002Ffailed-payment-recovery\u002Fcampaign-customization#campaign-customization","Campaign Customization",[153],{"id":2482,"title":2483,"titles":2484,"content":2485,"level":417},"\u002Ffailed-payment-recovery\u002Fcampaign-customization#email-sequences","Email Sequences",[153,2479],"Payment recovery campaigns can include up to 10 automated emails, each one entirely customizable. Select the 3 dots icon to delete an email from your sequence or add an additional email using the blue button.",{"id":2487,"title":2488,"titles":2489,"content":2490,"level":417},"\u002Ffailed-payment-recovery\u002Fcampaign-customization#sending-details","Sending Details",[153,2479],"Sending details allow for customizations surrounding the identity of the sender of the email.",{"id":2492,"title":2493,"titles":2494,"content":2495,"level":417},"\u002Ffailed-payment-recovery\u002Fcampaign-customization#timing-and-frequency","Timing and Frequency",[153,2479],"Tailor your email sequence over several days, strategically spacing out delivery times.",{"id":2497,"title":2498,"titles":2499,"content":2500,"level":417},"\u002Ffailed-payment-recovery\u002Fcampaign-customization#send-delay","Send Delay",[153,2479],"Adjust the delay between the subscription end date of the initial failed payments and email dispatch — up to 60 days for comprehensive outreach.",{"id":2502,"title":2503,"titles":2504,"content":2505,"level":417},"\u002Ffailed-payment-recovery\u002Fcampaign-customization#time-of-delivery","Time of Delivery",[153,2479],"To maximize open rates, we automatically pull in customer location details from Stripe to personalize email delivery times to each customer. Mornings are usually preferred, but experimenting with varied times can enhance engagement.",{"id":2507,"title":2508,"titles":2509,"content":2510,"level":417},"\u002Ffailed-payment-recovery\u002Fcampaign-customization#email-domain-verification","Email Domain Verification",[153,2479],"Personalize sender details to foster trust and recognition. Consider using a name from the company or a billing representative's name e.g. Jamis from Acme, Inc. Wildcard verification allows reactivation emails to be sent from many different email addresses.",{"id":2512,"title":2513,"titles":2514,"content":2515,"level":417},"\u002Ffailed-payment-recovery\u002Fcampaign-customization#sender-name-best-practices","Sender Name Best Practices",[153,2479],"Sending the emails from a diverse range of email addresses and names will help to improve the odds that a customer has worked with that employee before. By varying these sending details for each email of the campaign, emails gain credibility and deliverability.",{"id":2517,"title":2518,"titles":2519,"content":2520,"level":417},"\u002Ffailed-payment-recovery\u002Fcampaign-customization#email-content","Email Content",[153,2479],"Customize the content of each individual email of your campaign.",{"id":2522,"title":2523,"titles":2524,"content":2525,"level":417},"\u002Ffailed-payment-recovery\u002Fcampaign-customization#call-to-action-cta","Call to Action (CTA)",[153,2479],"Incorporate actionable elements in emails, directing recipients to hosted pages for seamless interaction. These actionable buttons will redirect customers to your payment update domain and should reflect the action you want your customer to take upon opening the email; they could be customized to say Update Your Card, or View Offer, or anything that will entice your customer. You can customize your domain by heading to Churnkey | Payment Recovery | Advanced Settings and providing the Payment Recovery page host. Make sure the domain is recognizable for your customers to ensure credibility; we recommend this domain to be billing.your_company.com or similar. Please note, you can have more than one CTA button in an email, but a call to action will be automatically appended to a reactivation email if one is not provided in the email content section, to ensure customers have a link to a page where they can reactivate their subscription.",{"id":2527,"title":2528,"titles":2529,"content":2530,"level":417},"\u002Ffailed-payment-recovery\u002Fcampaign-customization#insert-variable","Insert Variable",[153,2479],"We automatically pull in customer data from Stripe so that you can customize your emails to individual customers to help entice them to resubscribe. Out-of-the-box, insert Email Merge Fields of First Name and Plan Name are available. Custom attributes are also available for usage in email content after they have been added. Using custom attributes can help to personalize emails to specific customer plan usage so that you can call out information such as plan subscription start date, or the number of times a product was used, or any other relevant information. In addition, you can entice customers to stay by letting them know of any data that will be lost when they deactivate their subscriptions.",{"id":2532,"title":2533,"titles":2534,"content":2535,"level":417},"\u002Ffailed-payment-recovery\u002Fcampaign-customization#personalized-offers","Personalized Offers",[153,2479],"By offering a discount or coupon in a reactivation coupon, reduce cancellation risk and incentivize customers to update their payment methods.",{"id":2537,"title":2538,"titles":2539,"content":2540,"level":417},"\u002Ffailed-payment-recovery\u002Fcampaign-customization#offer-types","Offer Types",[153,2479],"Tailor your offerings to suit individual preferences with three versatile options: coupons, one-time fixed amounts, or percentage discounts on their upcoming payment. In practice, we've seen lifetime discounts work well to bring customers back to resubscribe.",{"id":2542,"title":2543,"titles":2544,"content":2545,"level":417},"\u002Ffailed-payment-recovery\u002Fcampaign-customization#testing-your-payment-recovery-campaigns","Testing your Payment Recovery Campaigns",[153,2479],"Using the eye icon under Email Content, preview your email.\n  \n  Update card demo page. View by selecting the call to action button in email preview.\n  \n  Using the paper airplane icon under Email Content, send yourself a test email.\n  \n  Payment recovery page. View by selecting the call to action button in the test email. Test your failed payment emails by selecting the eye icon to preview your email and the paper airplane icon to send a test email. Please note, when selecting the 'View Offer' button in the email preview, you will be taken to a demo dunning page that displays a 20% off coupon and will not reflect the offer that will be shown to your customers and is selected in your email settings; the demo page will not include custom Call to Action text or offer. In order to see these customization, you can BCC your email address in order to receive these emails. Find this setting on the Payment Recovery | Advanced Settings page.",{"id":158,"title":157,"titles":2547,"content":2548,"level":368},[],"Block access to your application when payments fail The Failed Payment Wall helps you recover revenue by automatically blocking access to your application when a customer's payment fails. It displays a streamlined UI for customers to update their payment information and immediately retry failed charges. Currently available for Stripe only",{"id":2550,"title":1200,"titles":2551,"content":2552,"level":374},"\u002Ffailed-payment-recovery\u002Ffailed-payment-wall#quick-start",[157],"To implement the Failed Payment Wall: Ensure the Churnkey script is loadedAdd the below code to your application initializationCustomize the wall behavior (optional) window.churnkey.check('failed-payment', {\n  \u002F\u002F Required - Authentication & identification\n  customerId: 'CUSTOMER_ID',\n  authHash: 'HMAC_HASH',\n  appId: 'YOUR_APP_ID',\n  provider: 'stripe',\n  \n  \u002F\u002F Optional - Wall behavior\n  mode: 'live',           \u002F\u002F Use 'test' for development\n  softWall: false,        \u002F\u002F Allow users to exit the wall\n  gracePeriodDays: 0,     \u002F\u002F Days before enforcing hard wall\n  forceCheck: false,      \u002F\u002F Skip caching (not recommended)\n  subscriptionId: 'SUBSCRIPTION_ID' \u002F\u002F Target specific subscription\n})",{"id":2554,"title":1205,"titles":2555,"content":2556,"level":374},"\u002Ffailed-payment-recovery\u002Ffailed-payment-wall#how-it-works",[157],"The Failed Payment Wall activates automatically when: A customer has invoices with open status in the last 60 daysTheir most recent invoice is not paid When activated, it: Blocks access to your applicationDisplays a payment update formProcesses the new payment methodRestores application access on success",{"id":2558,"title":1210,"titles":2559,"content":422,"level":374},"\u002Ffailed-payment-recovery\u002Ffailed-payment-wall#configuration",[157],{"id":2561,"title":1214,"titles":2562,"content":2563,"level":417},"\u002Ffailed-payment-recovery\u002Ffailed-payment-wall#core-options",[157,1210],"OptionTypeDefaultDescriptionmodestring'live''live' or 'test' environmentsoftWallbooleanfalseAllow customers to exit the wallgracePeriodDaysnumber0Days to show dismissible wall before enforcingforceCheckbooleanfalseBypass payment status cacheignoreInvoicesWithoutAttemptbooleanfalseOnly show wall for failed charge attempts",{"id":2565,"title":2566,"titles":2567,"content":2568,"level":417},"\u002Ffailed-payment-recovery\u002Ffailed-payment-wall#custom-display-logic","Custom display logic",[157,1210],"Control when to show the wall based on your business rules: window.churnkey.check('failed-payment', {\n  \u002F\u002F ... other options\n  shouldShowFailedPaymentWall(overdueInvoice, customer) {\n    \u002F\u002F Examples:\n    if (overdueInvoice.amountDue > 50000) {\n      return false           \u002F\u002F Skip for high-value invoices\n    }\n    if (customer.isVIP) {\n      return 'soft'         \u002F\u002F Allow VIPs to dismiss\n    }\n    return true             \u002F\u002F Show wall for others\n  }\n}) Return values: true - Show wall (follows softWall setting)false - Don't show wall'soft' - Show dismissible wall'hard' - Show enforced wall",{"id":2570,"title":1224,"titles":2571,"content":2572,"level":417},"\u002Ffailed-payment-recovery\u002Ffailed-payment-wall#optional-ui-elements",[157,1210],"Add extra buttons to the wall: window.churnkey.check('failed-payment', {\n  \u002F\u002F ... other options\n  handleLogout() { },         \u002F\u002F Add logout button\n  handleSupportRequest() { }, \u002F\u002F Add support button\n  handleCancel() { }         \u002F\u002F Add cancel subscription button\n})",{"id":2574,"title":1229,"titles":2575,"content":2576,"level":417},"\u002Ffailed-payment-recovery\u002Ffailed-payment-wall#event-callbacks",[157,1210],"Monitor wall activity: EventDescriptiononFailedPaymentWallActivated()Wall is displayedonUpdatePaymentInformation(customer)Payment updated successfullyonFailedPaymentWallClose()Wall is dismissed (soft wall only)onError(error, type)Error occurred Error types: FAILED_PAYMENT_WALL_INITIALIZATION_ERRORFAILED_PAYMENT_WALL_UPDATE_CARD_ERRORFAILED_PAYMENT_WALL_CANCEL_ERROR",{"id":2578,"title":56,"titles":2579,"content":2580,"level":374},"\u002Ffailed-payment-recovery\u002Ffailed-payment-wall#testing",[157],"Test the implementation using Stripe's test cards: Create a test customerAdd the test card: 4000000000000341 (Stripe's \"Decline After Attaching\" card)Create and add a subscription item to an invoiceEnable auto-charging for the invoiceFinalize the invoice to trigger the failed paymentVerify the Failed Payment Wall appears Watch a complete walkthrough: html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sH4kE, html code.shiki .sH4kE{--shiki-light:#FF5370;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sIO_5, html code.shiki .sIO_5{--shiki-light:#F76D47;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sjIoE, html code.shiki .sjIoE{--shiki-light:#E53935;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}",{"id":162,"title":161,"titles":2582,"content":2583,"level":368},[],"Send Payment Recovery emails to multiple billing contacts The Billing Contact API lets you configure multiple billing contacts for each customer, improving Payment Recovery rates for team accounts and organizations with multiple administrators. By default, Churnkey sends Payment Recovery emails only to the primary email address stored in the Stripe Customer object. With the Billing Contact API, you can specify additional contacts who should receive Payment Recovery communications.",{"id":2585,"title":2586,"titles":2587,"content":2588,"level":374},"\u002Ffailed-payment-recovery\u002Fbilling-contact-api#add-billing-contacts","Add billing contacts",[161],"You can update billing contacts for a single customer or in bulk.",{"id":2590,"title":2591,"titles":2592,"content":2593,"level":417},"\u002Ffailed-payment-recovery\u002Fbilling-contact-api#single-customer-update","Single customer update",[161,2586],"Update billing contacts for a single customer. Endpoint POST https:\u002F\u002Fapi.churnkey.co\u002Fv1\u002Fapi\u002Fevents\u002Fcustomer-update\u002Fset-users Authentication All requests require your App ID and Data API Key, which you can find in your Churnkey Dashboard. {\n    \"Content-Type\": \"application\u002Fjson\"\n    \"x-ck-api-key\": \"YOUR_DATA_API_KEY\"\n    \"x-ck-app\": \"YOUR_APP_ID\"\n} Request body {\n    \"customerId\": string;    \u002F\u002F Stripe customer ID\n    \"users\": User[];        \u002F\u002F Array of user objects\n} The users array accepts objects with the following parameters: ParameterTypeRequiredDescriptionuserIdstringYesYour internal user identifierdata.emailstringYesUser's email addressdata.phonestringNoUser's phone numberdata.namestringYesUser's full namedata.billingAdminbooleanYesSet to true to send Payment Recovery emailsdata.[custom]anyNoAdditional fields available for email merge tags Example request {\n  \"customerId\": \"cus_xyz\",\n  \"users\": [\n    {\n      \"userId\": \"user_123\",\n      \"data\": {\n        \"name\": \"Sarah Wilson\",\n        \"email\": \"sarah@example.com\",\n        \"phone\": \"+1235557890\",\n        \"billingAdmin\": true,\n        \"role\": \"Account Owner\"\n      }\n    },\n    {\n      \"userId\": \"user_456\",\n      \"data\": {\n        \"name\": \"Alex Chen\",\n        \"email\": \"alex@example.com\",\n        \"billingAdmin\": true,\n        \"role\": \"Billing Admin\"\n      }\n    }\n  ]\n}",{"id":2595,"title":2596,"titles":2597,"content":2598,"level":417},"\u002Ffailed-payment-recovery\u002Fbilling-contact-api#bulk-update","Bulk update",[161,2586],"Update billing contacts for multiple customers at once. Endpoint POST https:\u002F\u002Fapi.churnkey.co\u002Fv1\u002Fapi\u002Fevents\u002Fcustomer-update\u002Fset-users\u002Fbulk The bulk endpoint accepts an array of customer objects, using the same structure as the single customer update. Example request [\n  {\n    \"customerId\": \"cus_xyz1\",\n    \"users\": [\n      {\n        \"userId\": \"user_123\",\n        \"data\": {\n          \"name\": \"Sarah Wilson\",\n          \"email\": \"sarah@example.com\",\n          \"phone\": \"+1235557890\",\n          \"billingAdmin\": true\n        }\n      }\n    ]\n  },\n  {\n    \"customerId\": \"cus_xyz2\",\n    \"users\": [\n      {\n        \"userId\": \"user_456\",\n        \"data\": {\n          \"name\": \"Alex Chen\",\n          \"email\": \"alex@example.com\",\n          \"billingAdmin\": true\n        }\n      }\n    ]\n  }\n]",{"id":2600,"title":1205,"titles":2601,"content":2602,"level":374},"\u002Ffailed-payment-recovery\u002Fbilling-contact-api#how-it-works",[161],"When you set billingAdmin: true for a user, they'll receive Payment Recovery communications when a payment fails. This helps ensure that the right people in your customers' organizations are notified about payment issues, improving recovery rates. Any additional fields you include in the data object will be available as merge fields in your email templates. html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sRZlD, html code.shiki .sRZlD{--shiki-light:#6182B8;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html pre.shiki code .sK3rv, html code.shiki .sK3rv{--shiki-light:#39ADB5;--shiki-default:#116329;--shiki-dark:#7EE787}html pre.shiki code .s10kg, html code.shiki .s10kg{--shiki-light:#9C3EDA;--shiki-default:#116329;--shiki-dark:#7EE787}html pre.shiki code .st6vK, html code.shiki .st6vK{--shiki-light:#E2931D;--shiki-default:#116329;--shiki-dark:#7EE787}html pre.shiki code .syaID, html code.shiki .syaID{--shiki-light:#F76D47;--shiki-default:#116329;--shiki-dark:#7EE787}html pre.shiki code .sIfGp, html code.shiki .sIfGp{--shiki-light:#39ADB5;--shiki-default:#0550AE;--shiki-dark:#79C0FF}",{"id":165,"title":52,"titles":2604,"content":2605,"level":368},[],"Optimize your payment recovery campaigns through controlled experiments that maximize revenue recovery. Split test your Payment Recovery campaigns to discover which strategies work best for recovering failed payments and maximizing revenue retention. A\u002FB testing enables you to make data-driven decisions about your recovery campaigns through controlled experiments with statistical confidence.",{"id":2607,"title":5,"titles":2608,"content":2609,"level":374},"\u002Ffailed-payment-recovery\u002Fab-testing#overview",[52],"The A\u002FB Testing feature for Payment Recovery allows you to create controlled experiments comparing different campaign variations to optimize recovery rates and revenue. By testing different approaches side-by-side, you can identify the most effective strategies for recovering failed payments from your customers. When you create an A\u002FB test, Churnkey automatically splits incoming failed payments between two campaign variations, tracks their performance, and provides detailed analytics with statistical significance testing to help you determine which variation performs better. This takes the guesswork out of campaign optimization and ensures your recovery efforts are as effective as possible. The system uses rigorous statistical analysis with a 95% confidence level to determine whether differences between variations are meaningful or simply due to random chance. This means you can trust the results to guide important business decisions about your payment recovery strategy.",{"id":2611,"title":2612,"titles":2613,"content":2614,"level":374},"\u002Ffailed-payment-recovery\u002Fab-testing#metric-hierarchy-and-statistical-foundation","Metric Hierarchy and Statistical Foundation",[52],"Recovery Rate: Primary Statistical Metric\nRecovery rate serves as the primary metric for all statistical analysis, significance testing, and winner determination in Churnkey's A\u002FB testing engine. The statistical algorithms use recovery rate percentages to perform two-proportion z-tests and determine statistical significance at the 95% confidence level. Revenue Amount: Secondary Business Context\nRevenue data is tracked and displayed as secondary information for business impact assessment. While valuable for understanding financial implications, revenue metrics do not drive the core statistical analysis or winner determination process. Why Recovery Rate is Primary: Recovery rate provides the most statistically reliable foundation for A\u002FB testing because it represents a clear success\u002Ffailure outcome for each campaign, enabling robust statistical significance testing across different sample sizes and campaign volumes.",{"id":2616,"title":2617,"titles":2618,"content":2619,"level":374},"\u002Ffailed-payment-recovery\u002Fab-testing#understanding-ab-testing-benefits","Understanding A\u002FB Testing Benefits",[52],"Payment Recovery A\u002FB testing addresses several key challenges that businesses face when trying to optimize their failed payment campaigns. Without testing, you might wonder whether different email timing, messaging approaches, or offer strategies could improve your recovery rates. A\u002FB testing provides definitive answers to these questions. Recovery Rate Optimization: By testing different campaign approaches, you can identify which strategies lead to higher payment recovery rates. Recovery rate is the primary metric that drives all statistical analysis and winner determination, as even small improvements can translate to significant revenue gains over time. Revenue Impact Analysis: The system tracks not just recovery rates but also the actual revenue recovered by each variation. This revenue data provides important business context and impact assessment, helping you understand the financial implications of each approach while the statistical engine focuses exclusively on recovery rate performance for determining test outcomes. Customer Experience Insights: Different campaign variations may resonate differently with your customer base. A\u002FB testing reveals which messaging, timing, and approach customers respond to most positively. Risk Mitigation: Rather than implementing changes across all your recovery campaigns at once, A\u002FB testing lets you validate new approaches on a subset of failed payments before broader rollout.",{"id":2621,"title":2622,"titles":2623,"content":2624,"level":374},"\u002Ffailed-payment-recovery\u002Fab-testing#setting-up-your-first-ab-test","Setting Up Your First A\u002FB Test",[52],"Creating an A\u002FB test for Payment Recovery requires an existing segmented campaign. Primary campaigns cannot be A\u002FB tested because they serve as the default fallback for all customers who don't match specific segments. This ensures your Payment Recovery system maintains complete coverage while allowing targeted testing.",{"id":2626,"title":2627,"titles":2628,"content":2629,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#prerequisites-for-ab-testing","Prerequisites for A\u002FB Testing",[52,2622],"Before creating an A\u002FB test, ensure you meet these requirements: Segmented Payment Recovery campaign that's both published and activeCampaign targets specific customer groups (subscription value, lifecycle stage, or billing history)Sufficient volume to detect meaningful improvements (typically requiring several thousand failed payments)Primary campaigns only (cannot be A\u002FB tested as they serve as default fallback) Volume Requirements: Your segment should generate consistent failed payment volume to reach statistical significance within a reasonable timeframe. Lower volumes will require longer test durations to achieve reliable results.",{"id":2631,"title":2632,"titles":2633,"content":2634,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#creating-the-test","Creating the Test",[52,2622],"Follow these steps to create your A\u002FB test: Navigate to your Payment Recovery campaignsLocate the segmented campaign you want to testSelect \"Create A\u002FB Test\" from the campaign's action menuProvide a descriptive name that clearly identifies what you're testing The system automatically creates a duplicate of your original campaign, which becomes the second variation in your test.",{"id":2636,"title":2637,"titles":2638,"content":2639,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#configuring-test-variations","Configuring Test Variations",[52,2622],"Once created, you have two campaign variations: your original campaign (Variant A) and a newly created copy (Variant B). The copy initially matches your original campaign exactly, so you'll need to modify one or both variations to test different approaches. Variation Configuration: Make the changes you want to test in one or both campaign variations. Common test variations include: Email timing adjustmentsMessage content modificationsRecovery attempt sequence alterations Ensure both variations remain focused on the same customer segment to maintain test validity. Publishing Requirements: Both campaign variations must be published and active before you can start the A\u002FB test. The system will prevent test activation if either campaign has configuration issues.",{"id":2641,"title":2642,"titles":2643,"content":2644,"level":374},"\u002Ffailed-payment-recovery\u002Fab-testing#running-your-ab-test","Running Your A\u002FB Test",[52],"Once both campaign variations are published and active, you can start your A\u002FB test. The system begins automatically splitting incoming failed payments that match your segment between the two variations. This split is random and evenly distributed to ensure unbiased results.",{"id":2646,"title":2647,"titles":2648,"content":2649,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#test-activation-and-management","Test Activation and Management",[52,2642],"Starting your test is straightforward through the campaign interface. Once activated, the system displays the test status as \"Active\" and shows when the test began. Failed payments matching your segment are randomly assigned to either Variant A or Variant B, ensuring each variation receives a representative sample. Test Controls: While your test is running, you have several management options: Pause the test to make adjustments or temporarily halt the experimentResume a paused test to continue data collectionView Results to see the test current status Pausing stops new assignments but doesn't affect campaigns already in progress. Duration Considerations: Allow sufficient time for meaningful data collection: Payment Recovery campaigns often take days or weeks to complete their full cycleYou need enough completed campaigns to draw statistical conclusionsMost tests require several weeks to months of data collection for reliable results",{"id":2651,"title":2652,"titles":2653,"content":2654,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#monitoring-test-progress","Monitoring Test Progress",[52,2642],"The A\u002FB test interface provides real-time monitoring of your test's progress. You can track how many campaigns have been assigned to each variation, how many are still active, and preliminary results as they develop. However, avoid making decisions based on early data that hasn't reached statistical significance. Data Collection: The system tracks comprehensive metrics for each variation: Total campaigns triggeredNumber of campaigns that successfully recovered paymentsRecovery ratesTotal revenue recovered This data feeds into the statistical analysis that determines test outcomes.",{"id":2656,"title":1916,"titles":2657,"content":2658,"level":374},"\u002Ffailed-payment-recovery\u002Fab-testing#understanding-your-results",[52],"A\u002FB test results provide detailed analytics comparing the performance of your two campaign variations. The results dashboard shows key metrics, statistical analysis, and actionable insights to guide your campaign optimization decisions.",{"id":2660,"title":2661,"titles":2662,"content":2663,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#statistical-significance-analysis","Statistical Significance Analysis",[52,1916],"The most important aspect of your results is the statistical confidence level, which indicates whether observed differences between variations are meaningful or likely due to random chance. Churnkey's A\u002FB testing engine uses a two-proportion z-test exclusively on recovery rate data with a 95% confidence level to analyze results. Statistical Engine Focus: The system performs all significance testing calculations using recovery rate percentages from each variation. Revenue amounts, while displayed for business context, do not participate in the statistical analysis or winner determination process. Confidence Interpretation: When results show 95% or higher confidence based on recovery rate comparison, you can trust that the winning variation is genuinely better at recovering failed payments, not just lucky. Results below 95% confidence indicate the difference isn't statistically significant, meaning more data is needed or the variations perform similarly. P-Value Understanding: The p-value shows the probability that the observed recovery rate difference occurred by chance. Values below 0.05 (corresponding to 95% confidence) indicate statistical significance. Lower p-values represent stronger evidence of a real difference between variations in their ability to recover failed payments.",{"id":2665,"title":2666,"titles":2667,"content":2668,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#key-performance-metrics","Key Performance Metrics",[52,1916],"The results dashboard presents several critical metrics for evaluating test performance: Recovery Rate: The percentage of campaigns that successfully recovered failed payments - this is the primary metric for statistical analysis and winner determinationRevenue Recovered: The total dollar amount recovered by each variation - displayed for business context and impact assessmentCampaign Volume: The number of failed payment campaigns assigned to each variationRecovery Breakdown: Detailed analysis of how payments were recovered (email responses, automated retries, payment wall usage, etc.) Recovery rate serves as the primary statistical metric for optimization and winner determination, as it provides the most reliable basis for statistical significance testing. While revenue data is tracked and displayed for business impact analysis, the core A\u002FB testing engine focuses on recovery rate percentage to determine which variation performs better statistically.",{"id":2670,"title":2671,"titles":2672,"content":2673,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#winner-determination","Winner Determination",[52,1916],"When statistical significance is achieved, the system identifies a winning variation based on recovery rate performance and provides recommendations for next steps. The winner is determined by comparing recovery rate percentages using appropriate statistical testing to ensure the results are reliable. Statistical Focus on Recovery Rate: The A\u002FB testing engine performs its core analysis on recovery rate percentages, applying two-proportion z-tests to determine if differences between variations are statistically significant. While revenue amounts are displayed in the interface, they serve as additional business context rather than the primary decision criteria. Clear Winners: When one variation achieves a significantly higher recovery rate with statistical confidence, the results clearly indicate which approach to adopt. The winning variation should be implemented more broadly in your Payment Recovery strategy. No Clear Winner: Sometimes test results show no statistically significant difference in recovery rates between variations. This doesn't mean the test failed - it means both approaches recover payments at similar rates, giving you flexibility in which to use or suggesting you need to test more dramatically different approaches.",{"id":2675,"title":2676,"titles":2677,"content":2678,"level":374},"\u002Ffailed-payment-recovery\u002Fab-testing#advanced-test-analysis","Advanced Test Analysis",[52],"Beyond basic winner determination, dive deeper into your test results to extract maximum insights for campaign optimization. Advanced analysis helps you understand not just which variation won, but why it performed better and how to apply those learnings.",{"id":2680,"title":2681,"titles":2682,"content":2683,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#revenue-per-recovery-analysis","Revenue Per Recovery Analysis",[52,2676],"While recovery rate remains the primary statistical metric for winner determination, revenue per recovery provides valuable business context about recovery quality. Some campaign approaches might achieve higher recovery rates but for lower-value transactions, while others might recover fewer payments but of higher value. Calculate the average revenue per recovery for each variation by dividing total revenue recovered by the number of successful recoveries. This metric helps evaluate whether a variation is particularly effective with high-value customers or smaller transactions, but should be used for business analysis rather than statistical winner selection, which is based on recovery rate performance.",{"id":2685,"title":2686,"titles":2687,"content":2688,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#recovery-method-effectiveness","Recovery Method Effectiveness",[52,2676],"The results breakdown shows how payments were recovered through different methods (email engagement, automated retries, payment wall, SMS notifications). This analysis reveals which recovery mechanisms each variation optimizes and can guide future campaign design. For example, one variation might excel at driving email engagement while another performs better with automated retry strategies. Understanding these patterns helps you design more targeted campaigns for different customer segments.",{"id":2690,"title":2691,"titles":2692,"content":2693,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#temporal-performance-patterns","Temporal Performance Patterns",[52,2676],"Examine how each variation performed over the test duration. Some campaigns might start strong but decline in effectiveness, while others might improve as customers become familiar with the messaging. These patterns inform decisions about campaign duration and refresh cycles.",{"id":2695,"title":2696,"titles":2697,"content":2698,"level":374},"\u002Ffailed-payment-recovery\u002Fab-testing#test-completion-and-implementation","Test Completion and Implementation",[52],"When your A\u002FB test has collected sufficient data and achieved statistical significance, you can complete the test and implement the winning variation. Proper test completion ensures you capture maximum value from your testing efforts.",{"id":2700,"title":2701,"titles":2702,"content":2703,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#finishing-your-test","Finishing Your Test",[52,2696],"Complete your A\u002FB test through these steps: Select \"Finish A\u002FB Test\" in the campaign interfaceDesignate the winning variation based on your optimization goalsSystem automatically implements the winner as the active campaignLosing variation is deactivated but preserved for reference Winner Selection Considerations: Recovery rate is the exclusive primary metric used by the statistical engine for winner determination and significance testingRevenue data provides business context but does not influence the core statistical analysis or winner selection processStatistical Focus: The A\u002FB testing engine performs all significance calculations based solely on recovery rate percentagesRevenue per recovery metrics offer valuable insights for business impact assessment and implementation planningCustomer experience impact and operational complexity as additional factors for post-test implementation The winning variation automatically receives the highest priority in your campaign sequence, ensuring it becomes the primary handler for the customer segment.",{"id":2705,"title":2706,"titles":2707,"content":2708,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#applying-learnings","Applying Learnings",[52,2696],"Extract actionable insights from your test results to inform broader Payment Recovery strategy. Consider which elements of the winning variation contributed to its success and how those principles might apply to other campaigns. Successful Elements: Identify specific components that drove the winner's superior performance: Email timing optimizationMessage tone effectivenessRecovery sequence design improvements Document these findings for future campaign development. Broader Implementation: Consider expanding successful elements to other campaign segments, but remember that different customer segments may respond differently, requiring additional testing.",{"id":2710,"title":2711,"titles":2712,"content":2713,"level":374},"\u002Ffailed-payment-recovery\u002Fab-testing#best-practices-for-payment-recovery-ab-testing","Best Practices for Payment Recovery A\u002FB Testing",[52],"Maximize the value of your A\u002FB testing efforts by following proven best practices that ensure reliable results and actionable insights.",{"id":2715,"title":2716,"titles":2717,"content":2718,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#test-design-principles","Test Design Principles",[52,2711],"Follow these core principles for effective A\u002FB testing: Single Variable Testing: Focus each test on one key variable to clearly understand what drives performance differencesMeaningful Differences: Ensure variations represent meaningfully different approaches rather than minor tweaksSegment Consistency: Both variations should target the same customer segment for fair comparison Testing multiple changes simultaneously makes it difficult to determine which element caused the observed results. Small tweaks like minor wording changes may not generate detectable differences, while larger strategic changes are more likely to produce clear winners.",{"id":2720,"title":2721,"titles":2722,"content":2723,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#statistical-considerations","Statistical Considerations",[52,2711],"Ensure statistical validity with these requirements: Adequate Sample Size: Collect sufficient data for reliable statistical analysisTest Duration: Run tests for complete campaign cycles (typically 2-4 weeks per campaign) plus time to accumulate sufficient volumeConfidence Thresholds: Results must achieve statistical confidence before making decisionsEarly Stopping: Avoid ending tests early based on preliminary results—this leads to false conclusions Volume-Based Planning: Higher volume segments will reach statistical significance faster, while lower volume segments require longer test durations or broader targeting to achieve reliable results. Effect Size Planning: Test variations that represent meaningfully different approaches rather than minor tweaks. Dramatic changes are more likely to produce detectable differences within reasonable timeframes.",{"id":2725,"title":2726,"titles":2727,"content":2728,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#operational-excellence","Operational Excellence",[52,2711],"Maintain operational excellence through these practices: Documentation: Keep detailed records of test hypotheses, configurations, and resultsProgressive Testing: Build a testing roadmap that systematically explores optimization opportunitiesResults Validation: Run follow-up tests to validate surprising or unexpected results Documentation becomes valuable for future testing and helps avoid repeating unsuccessful experiments. Sequential testing allows continuous campaign performance improvement over time.",{"id":2730,"title":420,"titles":2731,"content":2732,"level":374},"\u002Ffailed-payment-recovery\u002Fab-testing#troubleshooting-common-issues",[52],"Address common challenges that may arise during A\u002FB test setup, execution, or analysis to ensure smooth testing operations.",{"id":2734,"title":2735,"titles":2736,"content":2737,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#test-setup-problems","Test Setup Problems",[52,420],"Cannot Create A\u002FB Test: Issue: Trying to test a primary campaign rather than a segmented campaignSolution: Create a segmented campaign first, then set up your A\u002FB testNote: Primary campaigns serve as the default for all unmatched customers and cannot be A\u002FB tested Test Won't Start:\nEnsure both campaign variations meet these requirements: Published and active statusValid email contentProper segment configurationNo configuration errors The system requires both variations to be fully configured and enabled before allowing test activation.",{"id":2739,"title":2740,"titles":2741,"content":2742,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#data-collection-issues","Data Collection Issues",[52,420],"No Results Showing: Cause: A\u002FB tests require actual failed payment campaigns to generate dataSolutions:\nAllow time for campaigns to be created and complete recovery cyclesCheck if your segment rarely triggersVerify test was recently started and needs more time Uneven Split Between Variations: Normal: Short-term imbalances due to random assignmentConcerning: Persistent skewing over extended periodsPotential causes: Configuration issues or very low campaign volumes",{"id":2744,"title":2745,"titles":2746,"content":2747,"level":417},"\u002Ffailed-payment-recovery\u002Fab-testing#analysis-challenges","Analysis Challenges",[52,420],"Results Never Reach Significance:\nPotential solutions: Run the test longer for larger sample sizesExpand the customer segment to increase volumeTest more dramatically different approachesVerify variations perform differently enough to detect Conflicting Metrics: Issue: One variation has higher recovery rate, another generates more revenueStatistical Solution: The A\u002FB testing engine prioritizes recovery rate for winner determination, as this provides the most reliable statistical foundationBusiness Context: Use revenue data for impact assessment, but recovery rate remains the primary metric for test conclusionsDecision Framework: Select the variation with statistically significant recovery rate improvement, then analyze revenue context for implementation planning When troubleshooting issues persist, the A\u002FB testing system includes detailed logging and diagnostic information. Review campaign assignment logs, check segment matching criteria, and verify that both variations are processing failed payments as expected. For complex issues, consider reaching out to support with specific test details and observed symptoms. The Payment Recovery A\u002FB testing system provides powerful tools for optimizing your failed Payment Recovery strategy through data-driven experimentation. By following these guidelines and best practices, you can systematically improve your recovery rates, maximize revenue retention, and create more effective customer experiences during the Payment Recovery process.",{"id":173,"title":172,"titles":2749,"content":2750,"level":368},[],"Key performance metrics for your Payment Recovery campaigns The Overall Performance section gives you a big-picture view of your Payment Recovery campaigns divided into four key insights: Subscriptions Recovered, Payments Recovered & Recovery Rate, Top Recovery Method, and Actively Recovering & Active Campaigns. The Date Picker on the top right allows you to filter all the metrics for a specific timeframe.",{"id":2752,"title":2753,"titles":2754,"content":2755,"level":374},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#subscriptions-recovered","Subscriptions Recovered",[172],"This metric represents the total number of Successful Payment Recovery Campaigns. A campaign can be understood as the unique email sequence + retries scheduled when a failed payment happens for a certain customer's subscription. This metric counts recovered campaigns (each failed payment recovery attempt), not unique subscriptions. If a single subscription has multiple failed payments over time—for example, January's invoice fails and is recovered, then March's invoice fails and is recovered—each successful recovery counts separately toward this total.",{"id":2757,"title":1349,"titles":2758,"content":2759,"level":417},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#why-it-matters",[172,2753],"This metric shows how many times your customers failed a payment and were retained. Each recovered payment represents a customer relationship and revenue saved. Additionally, you're enabling customers to maintain their data and setup in the platform they rely on.",{"id":2761,"title":465,"titles":2762,"content":2763,"level":417},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#how-it-works",[172,2753],"A failed payment can be recovered essentially by two means: Retries or by having a customer update their Payment Method. Any failed payment recovered regardless of the method will be accounted for in the Subscriptions Recovered metric. Each failed payment creates exactly one recovery campaign. When that campaign successfully recovers the payment, it adds one to the Subscriptions Recovered count.",{"id":2765,"title":2766,"titles":2767,"content":422,"level":417},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#example","Example",[172,2753],{"id":2769,"title":2770,"titles":2771,"content":2772,"level":958},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#example-1-incorrect-card-number","Example 1: Incorrect Card Number",[172,2753,2766],"A failed payment happened with a decline reason of Incorrect Card Number. We schedule the Email\u002FSMS sequence together with RetriesCustomer receives Email\u002FSMS, visits the Payment Method Update page, and updates their cardThe new card is successfully retried and payment is recoveredSubscriptions Recovered increases by one",{"id":2774,"title":2775,"titles":2776,"content":2777,"level":958},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#example-2-insufficient-funds-auto-retry","Example 2: Insufficient Funds - Auto Retry",[172,2753,2766],"A failed payment happened with a decline reason of Insufficient Funds. We schedule the Email\u002FSMS sequence together with RetriesCard is successfully retried by Churnkey and payment is recoveredSubscriptions Recovered increases by one",{"id":2779,"title":2780,"titles":2781,"content":2782,"level":958},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#example-3-insufficient-funds-payment-wall","Example 3: Insufficient Funds - Payment Wall",[172,2753,2766],"A failed payment happened with a decline reason of Insufficient Funds. We schedule the Email\u002FSMS sequence together with RetriesCustomer visits your website, faces the Failed Payment WallUpdates the Payment Method, we successfully retry the card and the payment is recoveredSubscriptions Recovered increases by one Note that, even though the Payment Wall is a passive recovery method, it also contributes to the Subscriptions Recovered metric.",{"id":2784,"title":2785,"titles":2786,"content":2787,"level":374},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#payments-recovered-recovery-rate","Payments Recovered & Recovery Rate",[172],"Payments Recovered is the total amount in dollars of revenue successfully recovered from failed payments. All payments recovered in other currencies are automatically converted to the USD equivalent value based on the latest exchange rates. The Recovery Rate, displayed below Payments Recovered, is the percentage of successful campaigns over your total campaigns scheduled.",{"id":2789,"title":1349,"titles":2790,"content":2791,"level":417},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#why-it-matters-1",[172,2785],"Payments Recovered is your bottom-line impact metric—the actual revenue your payment recovery campaigns have saved. This directly translates to financial impact on your business. It can be used to calculate the ROI you receive on your Churnkey subscription. Considering a business that fits the minimum requirements of churned revenue per month, the minimum ROI we expect exclusively from Payment Recovery, excluding the Other category, is around 5 times. In our $300 subscription, it would mean $1,500 in the Payments Recovered metric. The Recovery Rate represents the efficiency of your campaigns. It's a first indicator of whether your campaigns are well designed to incentivize customers to update their payment method. This metric is heavily correlated to the business context—for B2C companies we expect 55% or higher and for B2B companies 75% or higher.",{"id":2793,"title":465,"titles":2794,"content":2795,"level":417},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#how-it-works-1",[172,2785],"The total number is calculated based on every recovery source. There are currently 5 sources available: Emails\nWhen a customer actively clicks the email's link, visits the Payment Method Update page, then updates their card.SMS\nWhen a customer actively clicks the SMS link, visits the Payment Method Update page, then updates their card.Retries\nRelated to Auto-retries done before every email or Precision Retries. Both retry methods are optional.Failed Payment Wall\nWhen a customer faces the Failed Payment Wall, then updates their Payment Method.Other\nAny action performed outside of Churnkey (e.g., 3rd party software or your Payment Provider's own retry).",{"id":2797,"title":2798,"titles":2799,"content":2800,"level":417},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#example-payments-recovered","Example: Payments Recovered",[172,2785],"During a selected period, your recovery breakdown might look like: Emails: $3,000SMS: $2,000Retries: $4,000Failed Payment Wall: $1,000Other: $1,000 Payments Recovered = $3,000 + $2,000 + $4,000 + $1,000 + $1,000 = $11,000 The Recovery Rate is calculated based on all the successful campaigns versus all the finalized campaigns. Active campaigns—those where the failed payment is still in the recovery process—are not counted in this metric.",{"id":2802,"title":2803,"titles":2804,"content":2805,"level":417},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#example-recovery-rate","Example: Recovery Rate",[172,2785],"Customer A: Failed payment recovered\n→ Successful Campaigns = 1, Total Campaigns = 1Customer B: Failed payment unrecovered\n→ Successful Campaigns = 1, Total Campaigns = 2Customer C: Failed payment recovery attempt still in progress (not counted)\n→ Successful Campaigns = 1, Total Campaigns = 2 Recovery Rate = Successful Campaigns \u002F Total Campaigns = (1) \u002F (2) × 100 = 50%",{"id":2807,"title":2808,"titles":2809,"content":2810,"level":374},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#top-recovery-method","Top Recovery Method",[172],"This shows which recovery method has generated the highest total revenue (in USD) during your selected date range. It can be one of these four: Retries (combines both Auto Retries and Precision Retries)EmailsSMSPayment Wall",{"id":2812,"title":1349,"titles":2813,"content":2814,"level":417},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#why-it-matters-2",[172,2808],"This metric reveals which recovery channel is driving the most dollar value for your business, not just the most campaigns. A method might have fewer campaigns but recover higher-value subscriptions, making it your top revenue driver. This insight helps you understand where your recovery investment is paying off most and where to focus optimization efforts.",{"id":2816,"title":465,"titles":2817,"content":2818,"level":417},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#how-it-works-2",[172,2808],"The system tracks total revenue recovered through each method and ranks them by dollar amount. The method with the highest total revenue wins, regardless of how many campaigns ran through that channel.",{"id":2820,"title":2766,"titles":2821,"content":2822,"level":417},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#example-1",[172,2808],"During a selected period: Emails: 500 campaigns recovered $25,000Retries: 20 campaigns recovered $50,000SMS: 100 campaigns recovered $10,000Payment Wall: 50 campaigns recovered $15,000 In this example, Retries would be your Top Recovery Method with $50,000, even though Emails had 25x more campaigns. This is because Retries recovered higher-value subscriptions, making it your biggest revenue driver.",{"id":2824,"title":2825,"titles":2826,"content":2827,"level":374},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#actively-recovering","Actively Recovering",[172],"This metric shows the total dollar amount (in USD) from failed payments that are currently in active recovery. These are campaigns where the recovery sequence is still running—emails are scheduled or have been sent, retries are pending, but the payment hasn't been recovered yet. All currencies are automatically converted to USD based on the latest exchange rates.",{"id":2829,"title":1349,"titles":2830,"content":2831,"level":417},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#why-it-matters-3",[172,2825],"This is your revenue at risk that can still be saved. It represents your active recovery pipeline—the real money being pursued right now through ongoing campaigns. Unlike historical metrics that show past performance, Actively Recovering gives you a real-time snapshot of recovery efforts in progress. This helps you understand the immediate financial impact of your recovery operations and the potential revenue that could convert to \"Payments Recovered\" in the coming days.",{"id":2833,"title":465,"titles":2834,"content":2835,"level":417},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#how-it-works-3",[172,2825],"A campaign is considered active when all of these conditions are true: A payment has failed and the recovery campaign was createdThe recovery sequence is still running (emails scheduled or sent, retries pending)The payment has not been recovered yetThe campaign has not been deactivated or completed A campaign becomes inactive and stops contributing to this metric when any of these happens: The payment is successfully recovered (moves to \"Payments Recovered\")All emails have been sent and all retry attempts have been exhaustedThe invoice is voided or deletedThe subscription is canceled The metric displays both the total dollar amount at stake and the count of active campaigns currently running.",{"id":2837,"title":2766,"titles":2838,"content":2839,"level":417},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#example-2",[172,2825],"Your dashboard shows \"$8,450 Actively Recovering\" with \"35 Active Campaigns\" displayed below. This means you have 35 customers currently in the recovery process with a combined failed payment value of $8,450. Here's what might be happening: Customer A failed a $500 payment 2 days ago. The first email was sent, two more are scheduled over the next 5 days, and three retry attempts are pending. Status: Active (contributes $500 to the total). Customer B failed a $300 payment 5 days ago. Today, they clicked the email link and successfully updated their payment method. Status: No longer active (moves to \"Payments Recovered\"). Customer C failed a $200 payment 10 days ago. All five emails were sent, all retry attempts failed, and no more recovery actions are scheduled. Status: No longer active (campaign exhausted). In this scenario, only Customer A's $500 would remain in \"Actively Recovering,\" while Customer B's recovery adds to your success metrics and Customer C's represents an unsuccessful campaign.",{"id":2841,"title":269,"titles":2842,"content":422,"level":374},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#frequently-asked-questions",[172],{"id":2844,"title":2845,"titles":2846,"content":2847,"level":417},"\u002Ffailed-payment-recovery\u002Fanalytics\u002Foverview#my-metrics-are-below-the-minimum-mentioned","My metrics are below the minimum mentioned",[172,269],"In this case, you should contact our team via chat so we can deeply analyze your operation and understand what can be optimized. We're happy to help! Don't hesitate to reach out.",{"id":181,"title":149,"titles":2849,"content":2850,"level":368},[],"Churnkey helps you to recover unsubscribed customers with easy, customizable Reactivation Email Campaigns. Use scheduled email campaigns to recover customers who previously canceled their subscriptions. Craft targeted customer segments using out-of-the-box criteria automatically pulled in from Stripe like product preferences, subscription status, and cancellation details. Churnkey helps you go one step farther by allowing for the creation of tailored customer segments built with your own company custom attributes. Start recovering customers today with Churnkey Reactivations.",{"id":2852,"title":2853,"titles":2854,"content":422,"level":374},"\u002Freactivations\u002Freactivations#dashboard-walkthrough","Dashboard Walkthrough",[149],{"id":2856,"title":2436,"titles":2857,"content":2438,"level":417},"\u002Freactivations\u002Freactivations#lifetime-performance",[149,2853],{"id":2859,"title":2860,"titles":2861,"content":2862,"level":417},"\u002Freactivations\u002Freactivations#reactivation-activity","Reactivation Activity",[149,2853],"Observe Customer Status, Campaign Snapshot, Subscription, Subscription End Date,, and Next Email send date. Choose to filter by recently updated, recently cancelled, or cancelled longest ago. Learn more about creating your Reactivation Campaigns →",{"id":185,"title":184,"titles":2864,"content":2865,"level":368},[],"Customize your Reactivation Campaigns to match the individual needs of your customers. Churnkey Reactivation campaigns are customizable in nearly every way to help you reach your customers in a targeted, engaging, and credible approach.",{"id":2867,"title":2483,"titles":2868,"content":2869,"level":374},"\u002Freactivations\u002Fcampaign-customization-guide#email-sequences",[184],"Reactivation campaigns can include up to 10 automated emails, each one entirely customizable. Select the 3 dots icon to delete an email from your sequence or add an additional email using the blue button.",{"id":2871,"title":2488,"titles":2872,"content":422,"level":374},"\u002Freactivations\u002Fcampaign-customization-guide#sending-details",[184],{"id":2874,"title":2493,"titles":2875,"content":2495,"level":417},"\u002Freactivations\u002Fcampaign-customization-guide#timing-and-frequency",[184,2488],{"id":2877,"title":2498,"titles":2878,"content":2500,"level":417},"\u002Freactivations\u002Fcampaign-customization-guide#send-delay",[184,2488],{"id":2880,"title":2503,"titles":2881,"content":2505,"level":417},"\u002Freactivations\u002Fcampaign-customization-guide#time-of-delivery",[184,2488],{"id":2883,"title":2508,"titles":2884,"content":2885,"level":417},"\u002Freactivations\u002Fcampaign-customization-guide#email-domain-verification",[184,2488],"Personalize sender details to foster trust and recognition. Consider using a name from the company or a billing representative's name e.g. Jamis from Acme, Inc. Wildcard verification allows Reactivation emails to be sent from many different email addresses.",{"id":2887,"title":2513,"titles":2888,"content":2889,"level":417},"\u002Freactivations\u002Fcampaign-customization-guide#sender-name-best-practices",[184,2488],"Sending the emails from a diverse range of email addresses and names will help to improve the odds that a customer has worked with that employee before.",{"id":2891,"title":2518,"titles":2892,"content":2893,"level":374},"\u002Freactivations\u002Fcampaign-customization-guide#email-content",[184],"Craft compelling subject lines and customizable preview texts to engage recipients. Offers can be integrated into individual emails, with discounts applicable to failed invoices.",{"id":2895,"title":2523,"titles":2896,"content":2897,"level":417},"\u002Freactivations\u002Fcampaign-customization-guide#call-to-action-cta",[184,2518],"Incorporate actionable elements in emails, directing recipients to hosted pages for seamless interaction. These actionable buttons will redirect customers to your payment update domain and should reflect the action you want your customer to take upon opening the email; they could be customized to say Update Your Card, or View Offer, or anything that will entice your customer. You can customize your domain by heading to Churnkey | Payment Recovery | Advanced Settings and providing the Payment Recovery page host. Make sure the domain is recognizable for your customers to ensure credibility; we recommend this domain to be billing.your_company.com or similar. Please note, you can have more than one CTA button in an email, but a call to action will be automatically appended to a Reactivation email if one is not provided in the email content section, to ensure customers have a link to a page where they can reactivate their subscription.",{"id":2899,"title":2528,"titles":2900,"content":2530,"level":417},"\u002Freactivations\u002Fcampaign-customization-guide#insert-variable",[184,2518],{"id":2902,"title":2533,"titles":2903,"content":422,"level":417},"\u002Freactivations\u002Fcampaign-customization-guide#personalized-offers",[184,2518],{"id":2905,"title":2538,"titles":2906,"content":2540,"level":417},"\u002Freactivations\u002Fcampaign-customization-guide#offer-types",[184,2518],{"id":2908,"title":56,"titles":2909,"content":2910,"level":417},"\u002Freactivations\u002Fcampaign-customization-guide#testing",[184,2518],"Test your Reactivations emails by selecting the eye icon to preview your email and the paper airplane icon to send a test email. Please note, when selecting the 'View Offer' button in the email preview, you will be taken to a demo dunning page that displays a 20% off coupon and will not reflect the offer that will be shown to your customers and is selected in your email settings; the demo page will not include custom Call to Action text or offer. In order to see these customization, you can BCC your email to the emails sending to customers on the Reactivations | Advanced Settings page.",{"id":2912,"title":2913,"titles":2914,"content":2915,"level":368},"\u002Fcustomer-health","Customer Health",[],"Identify which of your customers are happy, which aren’t, and begin the process of proactive retention. Why wait until after a customer decides to churn before taking action to retain them? Proactively understanding your customers' health enables you to take preemptive action such as making feature announcements, enhancing awareness or education of your products, offering personalized discounts or coupons, and upselling satisfied customers. By identifying customers by their health score, you can take individualized churn mitigation action. Prioritizing Customer Health is key to maintaining your company's competitive edge.",{"id":2917,"title":2918,"titles":2919,"content":2920,"level":374},"\u002Fcustomer-health#real-time-data-analysis","Real-Time Data Analysis",[2913],"Customer Health scoring utilizes billing data including how long customers been subscribed, if they have any delinquent payments, card brand, and more, to assess customer health, and ranks them based on their likelihood to churn. We recommend, as a best practice, to include custom events in order to further inform our model and increase the accuracy of our rankings.",{"id":2922,"title":2923,"titles":2924,"content":2925,"level":417},"\u002Fcustomer-health#custom-events-integration","Custom Events Integration",[2913,2918],"To refine and enrich the customer heath predictive model, submit additional data points, which could be events such as specific number of uses of a product or number of logins. Ideally, for our model, we recommend sending 3-6 months worth of data. In the submission, data must include user id, created at, and event. Please note, we need to map your user ID’s, from your database to your customer ID’s from your billing provider. Watch our Loom Video for Custom Events →",{"id":2927,"title":2928,"titles":2929,"content":2930,"level":374},"\u002Fcustomer-health#customer-health-dashboard","Customer Health Dashboard",[2913],"Customer health information, right at your fingertips.",{"id":2932,"title":2913,"titles":2933,"content":2934,"level":417},"\u002Fcustomer-health#customer-health",[2913,2928],"At a glance, see what percentage of your customers fall into Low, Medium, and High risk health buckets. Watch your Sentiment trends over time to how your customers have felt about your company over time based of freeform feedback analysis.",{"id":2936,"title":2937,"titles":2938,"content":2939,"level":417},"\u002Fcustomer-health#watchlist","Watchlist",[2913,2928],"Stay informed about individual customers by monitoring the watchlist. Utilize filters to track the health trends of all customers. Seamlessly export customer data categorized by health buckets for tailored email campaigns. Enhance customer retention by strategically upselling low-risk customers to larger subscription plans, or opt them out from unnecessary email communications to mitigate potential cancellations. Identify medium-risk customers for personalized promotions, nudging them towards annual plans to minimize churn. Safeguard customer satisfaction by excluding high-risk customers from promotional emails, preventing any potential cancellations. Lastly, empower low-risk customers with enticing offers to upgrade their plans, ensuring they receive the best value for their subscription. Get started with Customer Health today!",{"id":194,"title":193,"titles":2941,"content":2942,"level":368},[],"Access your data through our API. Reach out to us at support@churnkey.co to speak about getting API access for your company.",{"id":2944,"title":2945,"titles":2946,"content":2947,"level":374},"\u002Fdata-integrations\u002Fdata-api#authorization","Authorization",[193],"Authorization is done with your data API key, which is distinct from the Cancel Flow API key. Once you have your key, you will pass it along with your application ID as request headers. You can find your API keys and App ID on Churnkey | Settings | Account. HEADERS\n{\n  \"x-ck-api-key\": \"data_your_key...\",\n  \"x-ck-app\": \"appIdHere\",\n  \"content-type\": \"application\u002Fjson\"\n}",{"id":2949,"title":2950,"titles":2951,"content":2952,"level":374},"\u002Fdata-integrations\u002Fdata-api#list-sessions","List Sessions",[193],"This endpoint will return an array of sessions, optionally filtered. This is limited to returning 10,000 sessions per request. GET \"https:\u002F\u002Fapi.churnkey.co\u002Fv1\u002Fdata\u002Fsessions\"\n\nHEADERS\n{\n  \"x-ck-api-key\": \"data_your_key...\",\n  \"x-ck-app\": \"appIdHere\",\n  \"content-type\": \"application\u002Fjson\"\n} Optionally, you can include the following filters as query params. FilterExampleDescriptionlimit10Maximum number of sessions to return (default: 100, max: 10,000)skip10Number of sessions to skip for manual pagination. Use with limit for paginated results.startDate2023-01-01Sessions that occurred after this date according to new Date(startDate)endDate2024-01-01Sessions that occurred before this date according to new Date(endDate)customerIdcus_123789The exact customer ID.trialtrueIf customer was on a subscription trial at the time of sessionbillingIntervalMONTHCustomer subscription billing interval. Possible values are WEEK, MONTH, YEARplanIdbilling_plan_idThe exact billing plan ID.abortedtrueIf session was abandoned before completioncanceledtrueIf customer canceled their subscriptionofferTypePAUSEType of accepted offer if accepted. Possible values are PAUSE, DISCOUNT, CONTACT, PLAN_CHANGE, REDIRECT, TRIAL_EXTENSIONsaveTypeABANDONType of accepted offer if accepted, or ABANDON if session was abandoned before completion. Possible values are PAUSE, DISCOUNT, CONTACT, PLAN_CHANGE, REDIRECT, TRIAL_EXTENSION, ABANDONcouponIdcoupon_abc12The ID of the coupon if the customer accepted a discount offer.pauseDuration2How long the customer paused their subscription for if they accepted a pause offer.blueprintIdid-goes-here-7890ID of the version of the published Cancel Flow.abtestguid-goes-here-7890ID of an ongoing or completed AB test.bouncedtrueBy default, bounced is set to false and bounced sessions are excluded (see note below). A session gets marked as bounced if another more relevant session takes place within 24 hours of that initial session. For instance, if a customer pauses and then decides to cancel within 24 hours, that first session with the pause offer accepted will be marked as bounced.",{"id":2954,"title":2955,"titles":2956,"content":2957,"level":417},"\u002Fdata-integrations\u002Fdata-api#examples-of-list-session-requests","Examples of List Session Requests",[193,2950],"Returns sessions for the month of May 2023GET \"https:\u002F\u002Fapi.churnkey.co\u002Fv1\u002Fdata\u002Fsessions?startDate=2023-05-01&endDate=2023-06-01\"Returns sessions where monthly customers accepted a discountGET \"https:\u002F\u002Fapi.churnkey.co\u002Fv1\u002Fdata\u002Fsessions?offerType=DISCOUNT&billingInterval=MONTH\"",{"id":2959,"title":2960,"titles":2961,"content":2962,"level":417},"\u002Fdata-integrations\u002Fdata-api#manual-pagination-examples","Manual Pagination Examples",[193,2950],"Use limit and skip parameters together to implement manual pagination: First page - Get the first 10 sessions:GET \"https:\u002F\u002Fapi.churnkey.co\u002Fv1\u002Fdata\u002Fsessions?limit=10\"Second page - Get sessions 11-20:GET \"https:\u002F\u002Fapi.churnkey.co\u002Fv1\u002Fdata\u002Fsessions?limit=10&skip=10\"Third page - Get sessions 21-30:GET \"https:\u002F\u002Fapi.churnkey.co\u002Fv1\u002Fdata\u002Fsessions?limit=10&skip=20\"",{"id":2964,"title":2965,"titles":2966,"content":2967,"level":417},"\u002Fdata-integrations\u002Fdata-api#response-data-format","Response Data Format",[193,2950],"[\n  {\n    _id: '63fffe3...',\n    org: '5fc5c5483b...',\n    blueprintId: '63ce7c2152...',\n    segmentId: '63ce7be8d0d...',\n    abtest: '63ce7c35e...',\n    customer: {\n      id: 'cus_123',\n      email: 'janedoe@gmail.com',\n      created: '2022-12-24T03:24:59.000Z',\n      subscriptionId: 'sub_1M...',\n      subscriptionStart: '2022-12-24T03:25:09.000Z',\n      subscriptionPeriodStart: '2023-02-24T03:25:09.000Z',\n      subscriptionPeriodEnd: '2023-03-24T03:25:09.000Z',\n      currency: 'usd',\n      planId: 'creator_monthly',\n      planPrice: 1299,\n      itemQuantity: 1,\n      billingInterval: 'MONTH',\n      billingIntervalCount: 1,\n    },\n    acceptedOffer: {\n      guid: 'dba-ea0be-a20f-...',\n      offerType: 'PAUSE',\n      pauseInterval: 'MONTH',\n      pauseDuration: 3,\n    },\n    provider: 'STRIPE',\n    aborted: false,\n    canceled: false,\n    surveyId: '929f...',\n    surveyChoiceId: 'e32f1...',\n    surveyChoiceValue: 'Missing Features',\n    feedback: 'I want to be able to add animations to the video',\n    discountCooldownApplied: false,\n    customActionHandler: null,\n    presentedOffers: [\n      {\n        guid: '1c85...',\n        accepted: false,\n        presentedAt: '2023-03-02T01:38:28.039Z',\n        declinedAt: '2023-03-02T01:38:35.462Z',\n        surveyOffer: false,\n        offerType: 'PAUSE',\n        pauseConfig: {\n          maxPauseLength: 2,\n          pauseInterval: 'MONTH',\n        },\n      },\n      {\n        guid: 'dba...',\n        accepted: true,\n        presentedAt: '2023-03-02T01:38:41.931Z',\n        acceptedAt: '2023-03-02T01:38:55.903Z',\n        surveyOffer: true,\n        offerType: 'DISCOUNT',\n        discountConfig: {\n          couponId: 'coup_id',\n        },\n      },\n    ],\n    mode: 'LIVE',\n    createdAt: '2023-03-02T01:38:56.005Z',\n    updatedAt: '2023-03-02T01:38:56.684Z',\n    recordingEndTime: '2023-03-02T01:38:56.032Z',\n    recordingStartTime: '2023-03-02T01:38:27.276Z',\n  },\n];",{"id":2969,"title":2970,"titles":2971,"content":2972,"level":374},"\u002Fdata-integrations\u002Fdata-api#aggregated-session-data","Aggregated Session Data",[193],"This endpoint will return an array of grouped session counts. Like the above session listing endpoint, this data can be filtered. Additionally, it can be aggregated by nested groups. GET \"https:\u002F\u002Fapi.churnkey.co\u002Fv1\u002Fdata\u002Fsession-aggregation\"\n\nHEADERS\n{\n  \"x-ck-api-key\": \"data_your_key...\",\n  \"x-ck-app\": \"appIdHere\",\n  \"content-type\": \"application\u002Fjson\"\n} In addition to the above filters, you can group the session aggregation by adding any of the following to the breakdown query param, separating each with a - e.g. ?breakdown=month-saveType FilterDescriptionmonthWhich month the session occurred in.trialIf customer was on a subscription trial at the time of sessionbillingIntervalCustomer subscription billing interval. Possible values are WEEK, MONTH, YEARplanIdThe exact billing plan ID.abortedIf session was abandoned before completioncanceledIf customer canceled their subscriptionofferTypeType of accepted offer if accepted. Possible values are PAUSE, DISCOUNT, CONTACT, PLAN_CHANGE, REDIRECT, TRIAL_EXTENSIONsaveTypeType of accepted offer if accepted, or ABANDON if session was abandoned before completion. Possible values are PAUSE, DISCOUNT, CONTACT, PLAN_CHANGE, REDIRECT, TRIAL_EXTENSION, ABANDONcouponIdThe ID of the coupon if the customer accepted a discount offer.pauseDurationHow long the customer paused their subscription for if they accepted a pause offer.blueprintIdID of the version of the published Cancel Flow.abtestID of an ongoing or completed AB test.",{"id":2974,"title":2975,"titles":2976,"content":2977,"level":417},"\u002Fdata-integrations\u002Fdata-api#examples-of-aggregate-session-requests","Examples of Aggregate Session Requests",[193,2970],"A month-by-month breakdown of the different types of offers accepted since the start of 2023GET \"https:\u002F\u002Fapi.churnkey.co\u002Fv1\u002Fdata\u002Fsession-aggregation?startDate=2023-01-01&breakdown=month-offerType\"A month-by-month breakdown of the survey responses for monthly vs annual customersGET \"https:\u002F\u002Fapi.churnkey.co\u002Fv1\u002Fdata\u002Fsession-aggregation?startDate=2023-01-01&breakdown=month-surveyResponse-billingInterval\"",{"id":2979,"title":2965,"titles":2980,"content":2981,"level":417},"\u002Fdata-integrations\u002Fdata-api#response-data-format-1",[193,2970],"[\n    {\n        \"count\": 70,\n        \"month\": \"2023-02\",\n        \"surveyResponse\": \"Doesn't Fit My Budget\",\n        \"billingInterval\": \"MONTH\"\n    },\n    {\n        \"count\": 4,\n        \"month\": \"2023-03\",\n        \"surveyResponse\": \"Missing Features\",\n        \"billingInterval\": \"MONTH\"\n    },\n    {\n        \"count\": 4,\n        \"month\": \"2023-01\",\n        \"surveyResponse\": \"Technical Issues\",\n        \"billingInterval\": \"MONTH\"\n    },\n    {\n        \"count\": 12,\n        \"month\": \"2023-01\",\n        \"surveyResponse\": \"Stopped Creating\",\n        \"billingInterval\": \"YEAR\"\n    },\n    {\n        \"count\": 5,\n        \"month\": \"2023-01\",\n        \"surveyResponse\": \"Missing Features\",\n        \"billingInterval\": \"MONTH\"\n    },\n    ...\n]",{"id":2983,"title":2984,"titles":2985,"content":2986,"level":374},"\u002Fdata-integrations\u002Fdata-api#gdpr-user-data-access","GDPR User Data Access",[193],"For data service requests for access POST \"https:\u002F\u002Fapi.churnkey.co\u002Fv1\u002Fdata\u002Fdsr\u002Faccess\"\n\nBODY\n{\n  \"email\": \"jane@example.com\"\n}\n\nHEADERS\n{\n  \"x-ck-api-key\": \"data_your_key...\",\n  \"x-ck-app\": \"appIdHere\",\n  \"content-type\": \"application\u002Fjson\"\n}",{"id":2988,"title":2965,"titles":2989,"content":2990,"level":417},"\u002Fdata-integrations\u002Fdata-api#response-data-format-2",[193,2984],"{\n    \"customer\": {\n        \"email\": \"jane@example.com\"\n    },\n    \"counts\": {\n        \"events\": 5,\n        \"sessions\": 5,\n        \"campaigns\": 5,\n        \"invoices\": 10,\n        \"failedPayments\": 5,\n        \"subscriptions\": 5,\n        \"charges\": 5,\n        \"emails\": 0\n    },\n    \"profile\": {\n        \"events\": [...],\n        \"sessions\": [...]\n        \"invoices\": [...],\n        \"campaigns\": [...],\n        \"failedPayments\": [...],\n        \"subscriptions\": [...],\n        \"charges\": [...],\n        \"emails\": [...]\n    }\n}",{"id":2992,"title":2993,"titles":2994,"content":2995,"level":374},"\u002Fdata-integrations\u002Fdata-api#gdpr-user-data-delete-request","GDPR User Data Delete Request",[193],"For data service requests for access POST \"https:\u002F\u002Fapi.churnkey.co\u002Fv1\u002Fdata\u002Fdsr\u002Fdelete\"\n\nBODY\n{\n  \"email\": \"jane@example.com\"\n}\n\nHEADERS\n{\n  \"x-ck-api-key\": \"data_your_key...\",\n  \"x-ck-app\": \"appIdHere\",\n  \"content-type\": \"application\u002Fjson\"\n}",{"id":2997,"title":2998,"titles":2999,"content":3000,"level":417},"\u002Fdata-integrations\u002Fdata-api#successful-response-data-format","Successful Response Data Format",[193,2993],"{\n    \"customer\": {\n        \"email\": \"jane@example.com\"\n    },\n    \"deleted\": true,\n    \"deletedCounts\": {\n        \"events\": 0,\n        \"sessions\": 0,\n        \"campaigns\": 0,\n        \"invoices\": 0,\n        \"failedPayments\": 0,\n        \"subscriptions\": 0,\n        \"charges\": 0,\n        \"emails\": 0\n    },\n    \"counts\": {\n        \"events\": 0,\n        \"sessions\": 0,\n        \"campaigns\": 0,\n        \"invoices\": 0,\n        \"failedPayments\": 0,\n        \"subscriptions\": 0,\n        \"charges\": 0,\n        \"emails\": 0\n    }\n}",{"id":3002,"title":3003,"titles":3004,"content":3005,"level":417},"\u002Fdata-integrations\u002Fdata-api#rejected-response-data-format","Rejected Response Data Format",[193,2993],"{\n    \"customer\": {\n        \"email\": \"jane@example.com\"\n    },\n    \"deleted\": false,\n    \"reasonForRejection\": \"Profile exceeds limit for API deletion. Please contact support@churnkey.co.\",\n    \"counts\": {\n        \"events\": 50000,\n        \"sessions\": 0,\n        \"campaigns\": 0,\n        \"invoices\": 0,\n        \"failedPayments\": 0,\n        \"subscriptions\": 0,\n        \"charges\": 0,\n        \"emails\": 0\n    }\n} html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":198,"title":197,"titles":3007,"content":3008,"level":368},[],"Connect Churnkey with your organization's Slack and to send session notifications to a specific channel. Connect Slack Head to the above page and click \"Connect to Slack\". This will walk you through the configuration process within Slack. Give the Churnkey App for Slack authorization and choose which channel you want session notifications posted to. Once connected, you’ll receive a Slack message whenever one of your customers goes through your Cancel Flow. Example below:",{"id":3010,"title":3011,"titles":3012,"content":3013,"level":417},"\u002Fdata-integrations\u002Fslack#set-your-notification-preferences","Set your notification preferences",[197],"Choose which session results you want to be sent to the previously designated Slack channel as notifications.",{"id":3015,"title":3016,"titles":3017,"content":3018,"level":417},"\u002Fdata-integrations\u002Fslack#privacy-policy","Privacy Policy",[197],"You can view our Privacy Policy here: https:\u002F\u002Fchurnkey.co\u002Flegal\u002Fprivacy-policy",{"id":3020,"title":264,"titles":3021,"content":3022,"level":417},"\u002Fdata-integrations\u002Fslack#support",[197],"For support for the Churnkey App for Slack, please contact us at support@churnkey.co.",{"id":202,"title":201,"titles":3024,"content":3025,"level":368},[],"Churnkey can send webhooks to automatically notify your application whenever a customer completes a session. Webhooks enable Churnkey to push real-time event notifications to your application. Once you’ve registered an endpoint URL, Churnkey will send a secure HTTPS POST request to that URL every time one of your customers completes a Churnkey session. The JSON payload will include session information like their survey response, any freeform feedback they left, and the outcome of the session e.g. subscription pause, cancellation, discount, or if they abandoned the cancellation flow. Webhooks can be managed from your Churnkey account Manage Webhooks",{"id":3027,"title":2072,"titles":3028,"content":3029,"level":374},"\u002Fdata-integrations\u002Fwebhooks#use-cases",[201],"Clients use Churnkey webhooks to: Update customer profile information in CRMs such as Hubspot or Intercom\nExample: marking every customer who cited “technical difficulties” in their survey response for a tutorial email campaignCreate a customized Slack thread where customer success teams can discuss next steps for customer retentionSend “secret plan” offers to customers who already declined offers on Churnkey Please note that for business logic related directly to customer billing and subscriptions, we recommend you use Stripe webhooks. This accounts for the possibility that subscriptions may be modified outside of the Churnkey flow.",{"id":3031,"title":3032,"titles":3033,"content":3034,"level":374},"\u002Fdata-integrations\u002Fwebhooks#webhook-event-data","Webhook Event Data",[201],"As soon as a webhook is enabled, Churnkey will start to stream new event notifications to that URL. These will be HTTPS POST requests with a JSON payload of the below format: type SegmentFilter = {\n  attribute: string;\n  operand: string;\n  value: string[];\n  type: string;\n}\n\ntype Segment = {\n  name: string;\n  filter: SegmentFilter[];\n}\n\ntype ABTest = {\n  name: string;\n  id: string;\n}\n\ntype DiscountConfig = {\n  couponId: string;\n  customDuration: string;\n}\n\ntype PauseConfig = {\n  maxPauseLength: number;\n  pauseInterval: string;\n  datePicker: boolean;\n}\n\ntype TrialExtensionConfig = {\n  trialExtensionDays: number;\n}\n\ntype PlanChangeConfig = {\n  options: string[];\n}\n\ntype RedirectConfig = {\n  redirectUrl: string;\n  redirectLabel: string;\n}\n\ntype PresentedOffer = {\n  guid: string;\n  offerType: 'DISCOUNT' | 'PAUSE' | 'PLAN_CHANGE' | 'CONTACT' | 'TRIAL_EXTENSION' | 'REDIRECT';\n  discountConfig?: DiscountConfig;\n  pauseConfig?: PauseConfig;\n  trialExtensionConfig?: TrialExtensionConfig;\n  planChangeConfig?: PlanChangeConfig;\n  redirectConfig?: RedirectConfig;\n}\n\ntype AcceptedOffer = {\n  guid: string;\n  offerType: 'discount' | 'pause' | 'plan change' | 'contact' | 'trial extension' | 'redirect';\n  pauseEndDate?: Date;\n  pauseInterval?: 'MONTH' | 'WEEK';\n  pauseDuration?: number;\n  pauseEndDate?: Date; \u002F\u002F if specific date chosen\n  newPlanId?: string;\n  newPlanPrice?: number;\n  redirectUrl?: string;\n  couponId?: string;\n  couponType?: 'PERCENT' | 'AMOUNT';\n  couponAmount?: number;\n  couponDuration?: number;\n  trialExtensionDays?: number;\n}\n\ntype Session = {\n  result: 'abort' | 'cancel' | 'pause' | 'discount' | 'plan_change' | 'contact' | 'trial_extension' | 'redirect';\n  presentedOffers: PresentedOffer[];\n  acceptedOffer?: AcceptedOffer;\n  mode: 'TEST' | 'LIVE';\n  segment?: Segment;\n  abTest?: ABTest;\n  subscriptionId: string;\n  feedback?: string; \u002F\u002F freeform feedback\n  surveyResponse?: string;\n  followupQuestion?: string; \u002F\u002F from survey selection\n  followupResponse?: string; \u002F\u002F from survey selection\n}\n\ntype SessionData = {\n  session: Session;\n  \u002F\u002F customer from payment provider with expanded subscription(s)\n  customer: StripeCustomer | ChargebeeCustomer | ...\n}\n\nexport type SessionWebhookPayload = {\n  event: 'session' | 'dunning';\n  data: SessionData;\n}",{"id":3036,"title":3037,"titles":3038,"content":3039,"level":417},"\u002Fdata-integrations\u002Fwebhooks#example-session-webhook-payload","Example Session Webhook Payload",[201,3032],"{\n  \"event\": \"session\",\n  \"data\": {\n    \"customer\": \u003CUP TO DATE CUSTOMER>,\n    \"session\": {\n      \"result\": \"pause\", \u002F\u002F pause, discount, cancel, abort\n      \"feedback\": \"Freeform feedback from customer\",\n      \"surveyResponse\": \"Too Expensive\",\n      \"mode\": \"LIVE\", \u002F\u002F billing mode, LIVE or TEST,\n      \"usedClickToCancel\": true, \u002F\u002F If customer clicked on the Cancel Now button \n      \"customAttributes\": {\n        \"favoriteAnimal\": \"penguin\" \u002F\u002F All customAttributes you sent during the session\n      },\n      \"followupQuestion\": \"From survey question\",\n      \"followupResponse\": \"Customer response to survey follow-up question\",\n      \"acceptedOffer\": {\n        \u002F\u002F details below\n        \"offerType\": \"PAUSE\",\n        \"pauseDuration\": 2,\n      },\n      \u002F\u002F Segment information will be sent if the customer belonged to a segment defined in Churnkey\n      \"segment\": {\n        \"name\": \"My annual customers\",\n        \"filter\": [{\n          \"attribute\": \"BILLING_INTERVAL\",\n          \"operand\": \"INCLUDES\",\n          \"value\": [\"YEAR\"]\n        }]\n      },\n      \"abTest\": {\n        \"name\": \"A\u002FB test for discount percentage\",\n        \"id\": \"Unique ID for AB Test\"\n      }\n    }\n  }\n} If a customer accepts an offer through Churnkey, the data.session object will include an acceptedOffer property with information about the offer they accepted. Depending on the offer type, this object will look different.",{"id":3041,"title":3042,"titles":3043,"content":3044,"level":417},"\u002Fdata-integrations\u002Fwebhooks#webhook-signature-verification","Webhook Signature Verification",[201,3032],"For enhanced security and data integrity, every webhook sent by our service is accompanied by a signature. This allows you to verify that the webhook was genuinely sent from our servers. How We Sign Outgoing Webhooks Payload Stringification: We first convert the webhook payload to a string by using JSON stringify.HMAC Computation: Using the SHA256 algorithm, an HMAC (Hash-based Message Authentication Code) is computed on the stringified payload. The secret key for this HMAC computation is the webhookSecret associated with your organization.Header Attachment: The resulting hash from the HMAC computation is then attached to the outgoing webhook as a header named ck-signature. How to Verify the Webhook Signature To verify the webhook signature on your end: Retrieve the Secret: Get your webhookSecret from our service. This is your org secret key which can be found on Churnkey | Settings | Account.Capture the Payload and Signature: When you receive a webhook, capture the payload (body) and the value of the ck-signature header.Compute the HMAC: Use the SHA256 algorithm to compute an HMAC on the payload, using your webhookSecret as the secret key.Match the Signatures: Compare the computed HMAC value (from the previous step) with the ck-signature value you captured. If the two match, then the webhook is verified and was genuinely sent by our service. Node.js Code Example const crypto = require('crypto');\n\u002F\u002F find your account secret under Cancel Flow API Key: https:\u002F\u002Fapp.churnkey.co\u002Fsettings\u002Faccount\nfunction verifyWebhookSignature(payload, receivedSignature, secret) {\n  \u002F\u002F Compute the HMAC\n  const computedHmac = crypto.createHmac('sha256', secret).update(JSON.stringify(payload)).digest('hex');\n\n  \u002F\u002F Compare the computed HMAC with the received signature\n  return computedHmac === receivedSignature;\n}\n\n\u002F\u002F Usage:\nconst payload = req.body; \u002F\u002F assuming you are using a body-parser middleware in Express.js\nconst receivedSignature = req.headers['ck-signature'];\nconst secret = 'YOUR_WEBHOOK_SECRET'; \u002F\u002F Retrieve this from our service or your configuration\n\nif (verifyWebhookSignature(payload, receivedSignature, secret)) {\n  console.log('Webhook signature is valid!');\n} else {\n  console.log('Webhook signature is invalid or has been tampered with.');\n} For more code examples, please check the server side HMAC signing on Installing Churnkey",{"id":3046,"title":3047,"titles":3048,"content":3049,"level":417},"\u002Fdata-integrations\u002Fwebhooks#pause-offer-accepted","Pause offer accepted",[201,3032],"{\n  \"offerType\": \"PAUSE\",\n  \"pauseDuration\": 2 \u002F\u002F pause duration in months\n}",{"id":3051,"title":3052,"titles":3053,"content":3054,"level":417},"\u002Fdata-integrations\u002Fwebhooks#discount-offer-accepted","Discount offer accepted",[201,3032],"{\n  \"offerType\": \"DISCOUNT\",\n  \"couponId\": \"my_coupon_id\", \u002F\u002F coupon Id in Stripe\u002FChargebee, discount ID in Braintree\n  \"couponType\": \"PERCENT\", \u002F\u002F PERCENT or AMOUNT\n  \"couponAmount\": 30,\n  \"couponDuration\": 2 \u002F\u002F coupon duration in months - null if forever coupon\n}",{"id":3056,"title":3057,"titles":3058,"content":3059,"level":374},"\u002Fdata-integrations\u002Fwebhooks#dunning-webhook-data","Dunning Webhook Data",[201],"If you have webhooks enabled and use dunning, Churnkey will stream email event notifications to your webhook URL. There are multiple events for each email based on the recipient’s actions. Potential action values are DELIVERY, BOUNCE, OPEN, CLICK. {\n  \"event\": \"dunning\",\n  \"data\": {\n    \"email\": {\n      \"emailTo\": \"john.doe@gmail.com\",\n      \"subject\": \"Need help with your subscription?\",\n      \"from\": \"info@acme.com\",\n      \"action\": \"DELIVERY\", \u002F\u002F DELIVERY, BOUNCE, OPEN, or CLICK\n      \"emailCount\": 2, \u002F\u002F This emails order in the campaign\n      \"emailsRemaining\": 3 \u002F\u002F Emails remaining in campaign\n    },\n    \"customer\": \"cus_XXXXXXXXXXXX\",\n    \"invoice\": \"inv_XXXXXXXXXXXX\"\n  }\n} html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":206,"title":205,"titles":3061,"content":3062,"level":368},[],"Track your customers' key-value metrics on a per customer basis. Event tracking products are difficult to test in the field and are filled with feature bloat. Our new event tracking tool lets you easily capture your products key-value metrics on a per customer basis. We’ll stitch together your event data with your customer subscription data for you, to give you a comprehensive view of your customers.",{"id":3064,"title":3065,"titles":3066,"content":3067,"level":374},"\u002Fdata-integrations\u002Fevent-tracking#customer-events","Customer Events",[205],"Events are created through a simple HTTP request.",{"id":3069,"title":3070,"titles":3071,"content":3072,"level":417},"\u002Fdata-integrations\u002Fevent-tracking#server-side","Server Side",[205,3065],"For sending server side events, include your Churnkey API key as a header, as shown below. Get your API key and App ID from Churnkey | Account. const headers = {\n  'x-ck-api-key': 'YOUR_CHURNKEY_API_KEY',\n  'x-ck-app': 'YOUR_CHURNKEY_APP_ID',\n};\n\nconst eventBody = {\n  event: 'Post Created',\n  customerId: 'cus_abc', \u002F\u002F from billing provider e.g. Stripe\n\n  \u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\n  \u002F\u002F OPTIONAL ITEMS BELOW\n  \u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\n  uid: 'user_xyz', \u002F\u002F unique id of the customer e.g. your database id\n  eventDate: '2022-05-01', \u002F\u002F use for backfilling data, default is now\n  eventData: {\n    postType: 'image',\n  },\n\n  \u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\n  \u002F\u002F FOR B2B PRODUCTS\n  \u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\n  user: {\n    uid: 'user_unique_id', \u002F\u002F e.g. your database id for the user\n  },\n};\n\naxios.post('https:\u002F\u002Fapi.churnkey.co\u002Fv1\u002Fapi\u002Fevents\u002Fnew', eventBody, headers); A Quick Note on Naming Convention To keep event naming consistent across your application, we recommend using an object-action convention, capitalized and with a regular space. “Product Clicked”“Application Opened”“Account Created”“User Registered”",{"id":3074,"title":3075,"titles":3076,"content":3077,"level":417},"\u002Fdata-integrations\u002Fevent-tracking#server-side-bulk-create","Server Side Bulk Create",[205,3065],"Ideal for backfilling data for training Churnkey customer health models. You can create up to 100 events in one API request using our bulk event endpoint. Please note that, unlike with single event creation, you cannot include customer and user data updates with the bulk event endpoint. const headers = {\n  'x-ck-api-key': 'YOUR_CHURNKEY_API_KEY',\n  'x-ck-app': 'YOUR_CHURNKEY_APP_ID',\n};\n\nconst events = [\n  {\n    \u002F\u002F ...events as defined above, including event, eventDate, and customerId or uid\n  },\n];\n\naxios.post('https:\u002F\u002Fapi.churnkey.co\u002Fv1\u002Fapi\u002Fevents\u002Fbulk', events, headers);",{"id":3079,"title":3080,"titles":3081,"content":3082,"level":417},"\u002Fdata-integrations\u002Fevent-tracking#client-side","Client Side",[205,3065],"Server side event creation is preferred Client side events can be authenticated by signing the customerId or customerEmail if the customerId is not available. Events can also be created without authentication, and will be marked as “unverified” accordingly. For details on creating the hmacSignature , see Step 2 on our Cancel Flow Installation Guide. \u002F\u002F Optional. See Step 2 of our Installation Guide\nconst hmacSignature = await getSignedCustomerId(customerId); \u002F\u002F Sign customerEmail if customerId is not available\n\nwindow.churnkey.event({\n  customerId: 'cus_a23',\n  customerEmail: 'jane@example.com',\n  appId: 'YOUR_CHURNKEY_APP_ID',\n  authHash: hmacSignature, \u002F\u002F Optional\n  event: 'Post Created',\n  eventData: {\n    postType: 'image',\n  },\n});",{"id":3084,"title":3085,"titles":3086,"content":3087,"level":374},"\u002Fdata-integrations\u002Fevent-tracking#customer-attributes","Customer Attributes",[205],"In addition to events, you can update customer data. This can be done by including a customerData property in the create (single) event endpoint, or by sending an explicit request to events\u002Fcustomer-update. You must include at least one of uid or customerId. For B2B products, you can also pass in a user property as shown below to update user data records. const headers = {\n  'x-ck-api-key': 'YOUR_CHURNKEY_API_KEY',\n  'x-ck-app': 'YOUR_CHURNKEY_APP_ID',\n};\n\nconst dataUpdate = {\n  uid: 'unique_id', \u002F\u002F unique id of the customer e.g. your database id\n  customerId: 'cus_xyz', \u002F\u002F from billing provider e.g. Stripe\n  customerData: {\n    numSeats: 7,\n    name: 'Acme, Inc.',\n  },\n\n  \u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\n  \u002F\u002F FOR B2B PRODUCTS\n  \u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\n  user: {\n    uid: 'user_unique_id', \u002F\u002F e.g. your database id for the user\n    data: {\n      name: 'John Henry',\n      email: 'johnhenry@example.com',\n    },\n  },\n};\n\naxios.post('https:\u002F\u002Fapi.churnkey.co\u002Fv1\u002Fapi\u002Fevents\u002Fcustomer-update', dataUpdate, headers); html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":215,"title":214,"titles":3089,"content":3090,"level":368},[],"Feature support for Stripe, Chargebee, Paddle, and Braintree",{"id":3092,"title":3093,"titles":3094,"content":3095,"level":417},"\u002Fbilling-providers\u002Fpayment-provider-overview#feature-support-matrix","Feature Support Matrix",[214],"FeatureStripeChargebeePaddleBraintreeSubscription Discounts✅✅✅✅Subscription Pause✅✅✅✅Plan Change✅✅✅✅Trial Extension✅✅✅✅Contact Support✅✅✅✅Page Redirect✅✅✅✅Payment Recovery✅---Reactivation Campaigns✅---Sandbox Mode✅---",{"id":219,"title":218,"titles":3097,"content":3098,"level":368},[],"Integrate Churnkey with your Stripe account",{"id":3100,"title":3101,"titles":3102,"content":3103,"level":374},"\u002Fbilling-providers\u002Fstripe#how-to-connect-stripe-and-churnkey","How to Connect Stripe and Churnkey",[218],"After you register for a Churnkey account, you can connect it to your Stripe account on our Settings | Billing Provider page. Connect Stripe Once your account has been successfully connected, you should see something like this:",{"id":3105,"title":3106,"titles":3107,"content":422,"level":368},"\u002Fbilling-providers\u002Fstripe#how-is-the-stripe-connection-used","How is the Stripe connection used?",[],{"id":3109,"title":3110,"titles":3111,"content":3112,"level":374},"\u002Fbilling-providers\u002Fstripe#i-add-discounts-to-your-cancel-flow","I. Add discounts to your Cancel Flow",[3106],"Coupons in Test Mode\nIf you want to test coupons in your flow in test mode, please create coupons in your Stripe test mode with IDs that match the coupons that you've selected to be part of your Cancel Flow. For instance, if you have added a coupon with id 40_off_2_months in Stripe live mode, this coupon will also need to exist in your Stripe test account to use in Churnkey's test mode. After you connect Stripe (live mode), you'll be able to offer your customers temporary discounts as part of your Cancel Flow. Please note that you will only see your Stripe coupons after you've connected your live Stripe account. We only pull in coupons from the live account, not your Stripe test account. In the \"Offers\" tab of the flow builder, choose \"Apply Stripe Coupon\"Choose the coupon you'd like to offer customers in the dropdown labeled \"Stripe Coupon\"",{"id":3114,"title":3115,"titles":3116,"content":3117,"level":374},"\u002Fbilling-providers\u002Fstripe#ii-personalize-cancel-flows-with-customer-attributes-and-segmentation","II. Personalize Cancel Flows with customer attributes and segmentation",[3106],"Use customer segmentation to reduce voluntary churn with targeted Cancel Flows. With segmentation, you can target specific customers based on subscription attributes and serve up unique Cancel Flows for each of them. After all, someone who signed up yesterday should be spoken to differently from a customer who's been a paying subscriber for years. 🚀 Customer Segmentation Launch Details Available attributes for segmentation PlanPriceSubscription ageSubscription creation dateBilling interval (monthly, annual, etc)Trial vs Active subscription",{"id":3119,"title":3120,"titles":3121,"content":3122,"level":374},"\u002Fbilling-providers\u002Fstripe#iii-updating-customer-subscriptions-on-your-behalf","III. Updating customer subscriptions on your behalf",[3106],"When a customer goes through your Cancel Flow and (hopefully) accepts a pause or discount offer, Churnkey takes care of the billing updates for you. Churnkey is able to perform the following actions on your behalf Pause a subscriptionApply a discountChange the subscription planCancel a subscriptionCreate a new subscription via reactivation campaigns We'll look at each one of these in a little bit more detail.",{"id":3124,"title":132,"titles":3125,"content":3126,"level":417},"\u002Fbilling-providers\u002Fstripe#pause-subscription",[3106,3120],"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. You can specify the maximum pause length allowed by customers while configuring your offboarding flow. When a customer chooses to temporarily pause their account, if a subscription ID was provided when initializing the Churnkey embed, the subscription under that ID will be canceled. If just a customer ID is provided, each active subscription will be paused. If you would like to implement a custom function for handling pause events, you can do so using the handlePause callback (see Custom Callbacks).",{"id":3128,"title":3129,"titles":3130,"content":3131,"level":417},"\u002Fbilling-providers\u002Fstripe#apply-a-discount","Apply a Discount",[3106,3120],"In the Churnkey offer builder, you can offer specific discounts to customers. These discounts are populated based on the coupons that you have created in your Stripe account. When a customer accepts a discount, if a subscription ID was provided when initializing the Churnkey embed, the coupon will be applied to that subscription with stripe.subscriptions.update. If just a customer ID is provided, that coupon is applied directly to the Stripe customer account via stripe.customers.update. If you would like to implement a customer function for applying discounts, you can do so using the handleDiscount callback (see Custom Callbacks).",{"id":3133,"title":3134,"titles":3135,"content":3136,"level":417},"\u002Fbilling-providers\u002Fstripe#plan-change","Plan Change",[3106,3120],"The specific subscription is updated to the new plan via stripe.subscriptions.update . You can optionally set to not prorate changes.",{"id":3138,"title":3139,"titles":3140,"content":3141,"level":417},"\u002Fbilling-providers\u002Fstripe#cancel-subscription","Cancel Subscription",[3106,3120],"Churnkey can handle canceling your customer's subscription for you. By default, a customer's subscription will be set to cancel at the end of the current period, but this behavior can be modified to cancel a user's subscription immediately. When a customer chooses to cancel their account, if a subscription ID was provided when initializing the Churnkey embed, the subscription under that ID will be canceled. If just a customer ID is provided, each active, delinquent, and past due subscription will be canceled. Under the hood, Churnkey follows Stripe's best practices on canceling: I. If not using subscription schedules: For active and trialing subscriptions, they are set to cancel at the end of the term via stripe.subscriptions.update(subscriptionId, { cancel_at_period_end: true }) . If you have set subscriptions to cancel immediately in your Cancel Flow settings, subscriptions will cancel immediately via stripe.subscriptions.del .Inactive customer subscriptions are always canceled immediately via stripe.subscriptions.del. II. If using subscription schedules For active and trialing subscriptions, the subscription schedule is first released via stripe.subscriptionSchedules.release , and then is set to cancel at the end of the term via stripe.subscriptions.update(subscriptionId, { cancel_at_period_end: true }) . If you have set subscriptions to cancel immediately in your Cancel Flow settings, subscriptions will be released from their schedule and then canceled immediately via stripe.subscriptions.del .Inactive customer subscriptions are canceled immediately via stripe.subscriptionSchedules.cancel If you would like to implement a customer function for canceling subscriptions, you can do so using the handleCancel callback (see Custom Callbacks).",{"id":3143,"title":3144,"titles":3145,"content":3146,"level":417},"\u002Fbilling-providers\u002Fstripe#creating-new-subscriptions-via-reactivations","Creating New Subscriptions via Reactivations",[3106,3120],"If you are using Churnkey Reactivation campaigns, Churnkey will handle reactivating your customer's subscription. When a customer chooses to reactivate their account, Churnkey will create a subscription identical to the one that was previously canceled that triggered the Reactivation campaign. This is done with stripe.subscriptions.create. All subscription data, including metadata, will be copied over to the new subscription.",{"id":3148,"title":3149,"titles":3150,"content":3151,"level":374},"\u002Fbilling-providers\u002Fstripe#iv-calculating-boosted-revenue","IV. Calculating boosted revenue",[3106],"Once you've connected your Stripe account, you'll be able to track customer reactivations and boosted revenue - that's all the extra revenue you receive from customers who stayed on instead of cancelling. You can track boosted revenue on your Churnkey dashboard.",{"id":3153,"title":3154,"titles":3155,"content":3156,"level":374},"\u002Fbilling-providers\u002Fstripe#v-stripe-sandbox-mode","V. Stripe Sandbox Mode",[3106],"Churnkey supports a dedicated Stripe Sandbox environment in addition to the standard Live and Test modes. Sandbox mode connects to a completely separate Stripe sandbox account, giving you an isolated space to test Cancel Flows, Payment Recovery, Reactivation campaigns, and analytics without any risk to your live or test data. This is different from Churnkey's existing Test mode. Test mode shares your main Stripe account's test credentials. Sandbox mode connects to a separate Stripe sandbox account entirely, meaning the customers, subscriptions, coupons, and invoices inside the sandbox are fully independent from both your live and test environments.",{"id":3158,"title":3159,"titles":3160,"content":3161,"level":417},"\u002Fbilling-providers\u002Fstripe#why-use-sandbox-mode","Why use Sandbox mode?",[3106,3154],"Test mode works well for quick validations, but it still shares the same Stripe account as your live environment. That overlap can create noise: test customers appear alongside real ones, test coupons might conflict with production coupons, and accidental changes in one context can affect the other. Sandbox mode eliminates that problem. Because it connects to a separate Stripe sandbox account, everything inside it is isolated. You can freely create test customers, trigger failed payments, run dunning campaigns, and experiment with cancel flow configurations without worrying about unintended side effects. This makes Sandbox mode the ideal choice for staging environments, QA testing, onboarding new team members, or validating complex flows before going live.",{"id":3163,"title":3164,"titles":3165,"content":3166,"level":417},"\u002Fbilling-providers\u002Fstripe#connecting-your-stripe-sandbox","Connecting your Stripe Sandbox",[3106,3154],"Navigate to Settings | Billing Provider in the Churnkey dashboard. Below your existing Live and Test connections, you will see a Sandbox Mode section. Click \"Connect Stripe Sandbox\" to begin the connection process. This redirects you to Stripe's authorization page, where you select (or create) the Stripe sandbox account you want to connect. After you authorize access, Churnkey saves the sandbox credentials automatically and returns you to the settings page. If you prefer to connect manually, you can enter a sandbox API key directly. Stripe sandbox keys follow the same sk_test_* format as test keys, but they belong to your separate sandbox account. Paste the key into the sandbox API key field and click \"Check Connection\" to verify that Churnkey can communicate with the sandbox account successfully. Sandbox keys vs Test keys\nBoth sandbox and test Stripe keys use the sk_test_* prefix. The difference is which Stripe account they belong to. Your sandbox key comes from a separate Stripe sandbox account, while your test key comes from your main Stripe account's test mode. Churnkey stores and manages these credentials independently.",{"id":3168,"title":3169,"titles":3170,"content":3171,"level":417},"\u002Fbilling-providers\u002Fstripe#switching-to-sandbox-mode","Switching to Sandbox mode",[3106,3154],"Once your Stripe sandbox is connected, a mode selector appears in the top-right corner of your Churnkey dashboard pages. This dropdown shows three options: View Live Data, View Test Data, and View Sandbox Data. Select \"View Sandbox Data\" to switch your dashboard context to the sandbox environment. When Sandbox mode is active, the dashboard displays only data from your sandbox Stripe account. Analytics charts, session logs, customer profiles, and campaign metrics all reflect sandbox activity exclusively. Switching back to Live or Test mode restores the corresponding data view instantly.",{"id":3173,"title":3174,"titles":3175,"content":3176,"level":417},"\u002Fbilling-providers\u002Fstripe#sandbox-in-cancel-flows","Sandbox in Cancel Flows",[3106,3154],"Cancel Flows work the same way in Sandbox mode as they do in Live and Test modes. When you initialize the Churnkey embed with mode set to sandbox, the Cancel Flow pulls customer data, subscription details, and coupons from your sandbox Stripe account. This lets you walk through the entire cancellation experience, including discount offers, pause offers, and plan changes, against sandbox data. window.churnkey.init('show', {\n  customerId: 'cus_sandbox_xxx',\n  authHash: 'YOUR_HMAC_HASH',\n  appId: 'YOUR_APP_ID',\n  mode: 'sandbox', \u002F\u002F connects to your Stripe sandbox account\n  provider: 'stripe',\n}) When a Discount Offer step is reached in Sandbox mode, Churnkey displays a coupon selector dropdown populated with coupons from your sandbox Stripe account, just like it does in Test mode. If the coupon configured in your Cancel Flow exists in the sandbox account, it is selected automatically. Otherwise, you can pick any available sandbox coupon from the dropdown to test the discount experience end-to-end. Sessions triggered in Sandbox mode display a blue \"Sandbox\" badge in the activity stream, making it straightforward to distinguish sandbox sessions from live or test activity. Clicking on a sandbox session's customer link navigates to that customer's profile with the ?mode=sandbox parameter, keeping you in the sandbox context.",{"id":3178,"title":3179,"titles":3180,"content":3181,"level":417},"\u002Fbilling-providers\u002Fstripe#sandbox-in-payment-recovery-dunning","Sandbox in Payment Recovery (Dunning)",[3106,3154],"Payment Recovery campaigns support Sandbox mode through a mode selector that appears when a sandbox account is connected. Select Sandbox from the mode dropdown on the Payment Recovery page to configure and test dunning campaigns against your sandbox Stripe data. There is one important difference when testing Payment Recovery in Sandbox mode: you must provide a Stripe Invoice ID rather than a customer ID to initiate a test campaign. This reflects how Stripe sandbox invoice objects work and ensures the campaign targets the correct payment attempt within the sandbox environment.",{"id":3183,"title":3184,"titles":3185,"content":3186,"level":417},"\u002Fbilling-providers\u002Fstripe#sandbox-in-reactivation-campaigns","Sandbox in Reactivation campaigns",[3106,3154],"Reactivation campaigns also support Sandbox mode. When the sandbox environment is selected, reactivation workflows operate against sandbox customers and subscriptions. This allows you to simulate the full reactivation flow, from the initial email trigger through to the new subscription creation, without affecting real customer data.",{"id":3188,"title":3189,"titles":3190,"content":3191,"level":417},"\u002Fbilling-providers\u002Fstripe#webhook-behavior-in-sandbox-mode","Webhook behavior in Sandbox mode",[3106,3154],"Churnkey receives webhook events from your sandbox Stripe account. However, sandbox events (like all test-mode events) are filtered out in production environments by design. This means that dunning emails and payment recovery actions are not triggered by sandbox failed payments in production. This filtering prevents accidental emails from being sent to sandbox customers. If you need to verify the full webhook-driven dunning flow end-to-end, use the Campaign Preview tool on the Payment Recovery page with the Sandbox mode selector instead.",{"id":3193,"title":3194,"titles":3195,"content":3196,"level":417},"\u002Fbilling-providers\u002Fstripe#disconnecting-sandbox","Disconnecting Sandbox",[3106,3154],"If you no longer need the sandbox connection, navigate to Settings | Billing Provider and click \"Disconnect Sandbox\" in the Sandbox Mode section. This removes the sandbox credentials from Churnkey. Your live and test connections remain unchanged. You can reconnect a sandbox account at any time by following the connection steps above. Previous sandbox session data is retained in Churnkey's records even after disconnecting. html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":223,"title":222,"titles":3198,"content":3199,"level":368},[],"Integrate Churnkey with your Chargebee account",{"id":3201,"title":3202,"titles":3203,"content":3204,"level":374},"\u002Fbilling-providers\u002Fchargebee#how-to-connect-your-chargebee-account","How to Connect Your Chargebee Account",[222],"After you register for a Churnkey account, you can connect it to your Chargebee account on our Settings | Billing Provider page. You can enter both live and test credentials. The test credentials are optional, but great to add if you want to test your Cancel Flow in a staging environment before going live with Churnkey. Manage Chargebee Connection",{"id":3206,"title":3207,"titles":3208,"content":3209,"level":417},"\u002Fbilling-providers\u002Fchargebee#step-1-generate-an-api-key","Step 1. Generate an API Key",[222,3202],"Your API key is used to apply pauses, discounts, and cancellations to customer subscriptions on your behalf. Read all about how we use the API key below. Log-in to your Chargebee accountNavigate to Settings - Configure Chargebee - API KeysClick “+ Add API Key”Select Full-Access KeySelect Write KeyChoose a name for your new API Key",{"id":3211,"title":3212,"titles":3213,"content":3214,"level":417},"\u002Fbilling-providers\u002Fchargebee#step-2-add-webhook-notifications","Step 2. Add Webhook Notifications",[222,3202],"In order for Churnkey to track reactivation rates (after pauses and discounts), and keep track of total boosted revenue from your Cancel Flow, you need to add a webhook in your Chargebee account. Navigate to Settings - Configure Chargebee - WebhooksClick “+Add Webhook”Create a name for the webhookSet the webhook URL to https:\u002F\u002Fbilling.churnkey.co\u002Fe\u002Fsrc_JDSQXrbmQU2j?appId=YOUR_CHURNKEY_APP_ID (please note to replace YOUR_CHURNKEY_APP_ID with your App ID - your account specific URL can be found on your Churnkey settings page)",{"id":3216,"title":3217,"titles":3218,"content":3219,"level":417},"\u002Fbilling-providers\u002Fchargebee#step-3-add-api-credentials-to-churnkey","Step 3. Add API Credentials to Churnkey",[222,3202],"The API credentials (site name, API key) can be linked to your Churnkey account on the Billing Provider Settings page: Manage Chargebee Connection",{"id":3221,"title":3222,"titles":3223,"content":422,"level":374},"\u002Fbilling-providers\u002Fchargebee#how-is-the-chargebee-connection-used","How is the Chargebee connection used?",[222],{"id":3225,"title":3110,"titles":3226,"content":3227,"level":417},"\u002Fbilling-providers\u002Fchargebee#i-add-discounts-to-your-cancel-flow",[222,3222],"Coupons in Test Mode\nIf you want to test coupons in your flow in test mode, please create coupons in your Chargebee test mode with IDs that match the coupons that you've selected to be part of your Cancel Flow. For instance, if you have added a coupon with id 40_off_2_months in Chargebee live mode, this coupon will also need to exist in your Chargebee test account to use in Churnkey's test mode. After you connect Chargebee (live mode), you'll be able to offer your customers temporary discounts as part of your Cancel Flow. Please note that you will only see your Chargebee coupons after you've connected your live Chargebee account. We only pull in coupons from the live account, not your Chargebee test account. In the \"Offers\" tab of the flow builder, choose \"Apply Chargebee Coupon\"Choose the coupon you'd like to offer customers in the dropdown labeled \"Chargebee Coupon\"",{"id":3229,"title":3115,"titles":3230,"content":3231,"level":417},"\u002Fbilling-providers\u002Fchargebee#ii-personalize-cancel-flows-with-customer-attributes-and-segmentation",[222,3222],"Use customer segmentation to reduce voluntary churn with targeted Cancel Flows. With segmentation, you can target specific customers based on subscription attributes and serve up unique Cancel Flows for each of them. After all, someone who signed up yesterday should be spoken to differently from a customer who's been a paying subscriber for years. 🚀 Customer Segmentation Launch Details Available attributes for segmentation PlanPriceSubscription ageSubscription creation dateBilling interval (weekly, monthly, annual, etc)Trial vs Active subscription",{"id":3233,"title":3120,"titles":3234,"content":3235,"level":417},"\u002Fbilling-providers\u002Fchargebee#iii-updating-customer-subscriptions-on-your-behalf",[222,3222],"When a customer goes through your Cancel Flow and (hopefully) accepts a pause or discount offer, Churnkey takes care of the billing updates for you. Churnkey is able to perform the following actions on your behalf Pause a subscriptionApply a discountCancel a subscriptionExtend a trial periodSwitch plans We'll look at each one of these in a little bit more detail.",{"id":3237,"title":3238,"titles":3239,"content":3240,"level":958},"\u002Fbilling-providers\u002Fchargebee#pause-a-subscription","Pause a Subscription",[222,3222,3120],"Churnkey uses Chargebee'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. You can specify the maximum pause length allowed by customers while configuring your offboarding flow. When a customer chooses to temporarily pause their account, if a subscription ID was provided when initializing the Churnkey embed, the subscription under that ID will be canceled. If just a customer ID is provided, each active subscription will be paused. If you would like to implement a custom function for handling pause events, you can do so using the handlePause callback (see Custom Callbacks).",{"id":3242,"title":3129,"titles":3243,"content":3244,"level":958},"\u002Fbilling-providers\u002Fchargebee#apply-a-discount",[222,3222,3120],"In the Churnkey offer builder, you can offer specific discounts to customers. These discounts are populated based on the coupons that you have created in your Chargebee account. When a customer accepts a discount, if a subscription ID was provided when initializing the Churnkey embed, the coupon will be applied to that subscription. If just a customer ID is provided, that coupon is applied directly to the Chargebee customer account. If you would like to implement a customer function for applying discounts, you can do so using the handleDiscount callback (see Custom Callbacks).",{"id":3246,"title":3247,"titles":3248,"content":3249,"level":958},"\u002Fbilling-providers\u002Fchargebee#cancel-a-subscription","Cancel a Subscription",[222,3222,3120],"Churnkey can handle canceling your customer's subscription for you. By default, a customer's subscription will be set to cancel at the end of the current period, but this behavior can be modified to cancel a user's subscription immediately. When a customer chooses to cancel their account, if a subscription ID was provided when initializing the Churnkey embed, the subscription under that ID will be canceled. If just a customer ID is provided, each active, delinquent, and past due subscription will be canceled. Under the hood, Churnkey follows Chargebee's best practices on canceling. If you would like to implement a customer function for canceling subscriptions, you can do so using the handleCancel callback (see Custom Callbacks).",{"id":3251,"title":3252,"titles":3253,"content":3254,"level":958},"\u002Fbilling-providers\u002Fchargebee#extend-a-trial-period","Extend a Trial Period",[222,3222,3120],"More information coming soon!",{"id":3256,"title":3257,"titles":3258,"content":3254,"level":958},"\u002Fbilling-providers\u002Fchargebee#switch-plans","Switch plans",[222,3222,3120],{"id":3260,"title":3149,"titles":3261,"content":3262,"level":417},"\u002Fbilling-providers\u002Fchargebee#iv-calculating-boosted-revenue",[222,3222],"Once you've connected your Chargebee account, you'll be able to track customer reactivations and boosted revenue - that's all the extra revenue you receive from customers who stayed on instead of canceling. You can track boosted revenue on your Churnkey dashboard.",{"id":227,"title":226,"titles":3264,"content":3265,"level":368},[],"Integrate Churnkey with your Paddle Classic account",{"id":3267,"title":3268,"titles":3269,"content":3270,"level":374},"\u002Fbilling-providers\u002Fpaddle-classic#how-to-connect-your-paddle-account","How to Connect Your Paddle Account",[226],"After you register for a Churnkey account, you can connect it to your Paddle Classic account on our Settings | Billing Provider page. You can enter both live and sandbox credentials. The sandbox credentials are optional, but great to add if you want to test your Cancel Flow in a staging environment before going live with Churnkey. Manage Paddle Connection",{"id":3272,"title":3273,"titles":3274,"content":3275,"level":417},"\u002Fbilling-providers\u002Fpaddle-classic#step-1-select-paddle-version","Step 1. Select Paddle Version",[226,3268],"Paddle has two different versions: Paddle Classic and Paddle Billing. Unsure about the version you're using?\nIf your Paddle account was created after August 8th, 2023, then you're using Paddle Billing.If it was created before that date, you're likely using Paddle Classic, unless you actively migrated to the newer version.For more information, visit Paddle Billing. This documentation page is for Paddle Classic. If you're looking for integration instructions for Paddle Billing, click here. Start the connection process by selecting Paddle Classic in the Paddle Version section.",{"id":3277,"title":3278,"titles":3279,"content":3280,"level":417},"\u002Fbilling-providers\u002Fpaddle-classic#step-2-add-your-vendor-id-public-key-and-auth-code-to-churnkey","Step 2. Add your Vendor ID, Public Key, and Auth Code to Churnkey",[226,3268],"Your API key is used to apply discounts (via modifiers) to customer subscriptions on your behalf. Read all about how we use the API key below. Create a new Auth Code for Churnkey at Paddle | AuthenticationNote: make sure to use this auth code instead of the SDK API keyClick \"Reveal Auth Code\" under \"Active Integrations & Auth Codes\" to show the Churnkey-specific Auth Code. Your vendor_id can also be found at the top of the same page.Your Public Key is listed under Paddle | Public KeyEnter the above details under Churnkey | Billing Provider",{"id":3282,"title":3283,"titles":3284,"content":3285,"level":417},"\u002Fbilling-providers\u002Fpaddle-classic#step-3-add-webhook-notifications","Step 3. Add Webhook Notifications",[226,3268],"In order for Churnkey to track reactivation rates (after accepted offers), and keep track of total boosted revenue from your Cancel Flow, you need to add a webhook in your Paddle account. Navigate to Paddle | EventsClick “+Add a new endpoint” under “URLs for receiving webhooks”Set the webhook URL to https:\u002F\u002Fbilling.churnkey.co\u002Fe\u002Fsrc_SP8oUSlSZJab?appId=YOUR_CHURNKEY_APP_ID (please note to replace YOUR_CHURNKEY_APP_ID with your App ID - your account specific URL can be found on your Churnkey settings page)",{"id":3287,"title":3288,"titles":3289,"content":422,"level":374},"\u002Fbilling-providers\u002Fpaddle-classic#how-is-the-paddle-connection-used","How is the Paddle connection used?",[226],{"id":3291,"title":3110,"titles":3292,"content":3293,"level":417},"\u002Fbilling-providers\u002Fpaddle-classic#i-add-discounts-to-your-cancel-flow",[226,3288],"Discount Constraints\nPlease note that, due to limitations of Paddle’s API, discounts offered to customers will need to be one-time or recurring indefinitely. That is, you won’t be able to offer $5 off for 3 months (either for one-month or indefinitely).The good news: we’ve seen great results with one-month coupons so far! After you connect Paddle, you'll be able to offer your customers discounts as part of your Cancel Flow. In the “Offers” tab of the flow builder, choose “Apply Custom Discount”Choose the amount you’d like to offer customers and whether this is a one time or recurring discount.",{"id":3295,"title":3115,"titles":3296,"content":3297,"level":417},"\u002Fbilling-providers\u002Fpaddle-classic#ii-personalize-cancel-flows-with-customer-attributes-and-segmentation",[226,3288],"Use customer segmentation to reduce voluntary churn with targeted Cancel Flows. With segmentation, you can target specific customers based on subscription attributes and serve up unique Cancel Flows for each of them. After all, someone who signed up yesterday should be spoken to differently from a customer who's been a paying subscriber for years. You can use custom attributes to create different Cancel Flows for subscriptions in different currencies, tailoring the offers for each segment. 🚀 Customer Segmentation Launch Details Available attributes for segmentation PlanPriceSubscription ageSubscription creation dateBilling interval (weekly, monthly, annual, etc)Trial vs Active subscription",{"id":3299,"title":3120,"titles":3300,"content":3301,"level":417},"\u002Fbilling-providers\u002Fpaddle-classic#iii-updating-customer-subscriptions-on-your-behalf",[226,3288],"When a customer goes through your Cancel Flow and either chooses to cancel their subscription or (hopefully) accept a discount offer, Churnkey will take care of the billing and subscription updates for you. Churnkey is able to perform the following actions on your behalf: Apply a discountCancel a subscription We'll look at each one of these in a little bit more detail.",{"id":3303,"title":3129,"titles":3304,"content":3305,"level":958},"\u002Fbilling-providers\u002Fpaddle-classic#apply-a-discount",[226,3288,3120],"In the Churnkey offer builder, you can offer specific discounts to customers. While Paddle does allow you to create coupons within their dashboard, they do not allow these discounts to be applied through the API to existing subscriptions. Instead, Churnkey uses subscription modifiers to add a discount to existing subscriptions. When a customer accepts a discount, a modifier will be added to the subscription (for the amount you specified when building you Cancel Flow). If you would like to implement a customer function for applying discounts, you can do so using the handleDiscount callback (see Custom Callbacks).",{"id":3307,"title":3247,"titles":3308,"content":3309,"level":958},"\u002Fbilling-providers\u002Fpaddle-classic#cancel-a-subscription",[226,3288,3120],"Churnkey can handle canceling your customer's subscription for you. Churnkey uses Paddle’s subscription\u002Fusers_cancel endpoint. If you would like to implement a customer function for canceling subscriptions, you can do so using the handleCancel callback (see Custom Callbacks).",{"id":3311,"title":3312,"titles":3313,"content":3314,"level":958},"\u002Fbilling-providers\u002Fpaddle-classic#a-note-on-subscription-pauses","A Note on Subscription Pauses",[226,3288,3120],"Paddle does have a notion of subscription pauses. Currently, however, their API does not provide an option to pause a subscription for a limited period (e.g. pause a subscription for 2 months). Adding in this feature with custom workarounds is on the Churnkey roadmap, and we’ll keep all Paddle customers updated on this progress.",{"id":3316,"title":3149,"titles":3317,"content":3318,"level":417},"\u002Fbilling-providers\u002Fpaddle-classic#iv-calculating-boosted-revenue",[226,3288],"Once you’ve connected your Paddle account, you’ll be able to track customer reactivations and boosted revenue - that’s all the extra revenue you receive from customers who stayed on instead of cancelling. You can track boosted revenue on your Churnkey dashboard.",{"id":231,"title":230,"titles":3320,"content":3321,"level":368},[],"Integrate Churnkey with your Paddle Billing account",{"id":3323,"title":3268,"titles":3324,"content":3325,"level":374},"\u002Fbilling-providers\u002Fpaddle-billing#how-to-connect-your-paddle-account",[230],"After you register for a Churnkey account, you can connect it to your Paddle Billing account on our Settings | Billing Provider page. You can enter both live and sandbox credentials. The sandbox credentials are optional, but great to add if you want to test your Cancel Flow in a staging environment before going live with Churnkey. Manage Paddle Connection",{"id":3327,"title":3273,"titles":3328,"content":3329,"level":417},"\u002Fbilling-providers\u002Fpaddle-billing#step-1-select-paddle-version",[230,3268],"Paddle has two different versions: Paddle Classic and Paddle Billing. Unsure about the version you're using?\nIf your Paddle account was created after August 8th, 2023, then you're using Paddle Billing.If it was created before that date, you're likely using Paddle Classic, unless you actively migrated to the newer version.For more information, visit Paddle Billing. This documentation page is for Paddle Billing. If you're looking for integration instructions for Paddle Classic, click here. Start the connection process by selecting Paddle Billing in the Paddle Version section.",{"id":3331,"title":3332,"titles":3333,"content":3334,"level":417},"\u002Fbilling-providers\u002Fpaddle-billing#step-2-add-your-api-key-and-your-webhook-secret-key","Step 2. Add your API Key and your Webhook Secret Key",[230,3268],"Your API key is used to apply pauses, discounts, and cancellations to customer subscriptions on your behalf. Read all about how we use the API key below. Create a new API Key for Churnkey at Paddle | Authentication. If you're operating in Sandbox mode, use this link instead.Click the ellipsis button next to your newly-created API Key and copy it.Enter your API Key in Churnkey | Billing Provider",{"id":3336,"title":3283,"titles":3337,"content":3338,"level":417},"\u002Fbilling-providers\u002Fpaddle-billing#step-3-add-webhook-notifications",[230,3268],"In order for Churnkey to track reactivation rates (after accepted offers), and keep track of total boosted revenue from your Cancel Flow, you need to add a webhook in your Paddle account. Find your webhook URL on your Churnkey settings page and copy it. The URL changes based on the Paddle version. Since you are currently reading instructions for Paddle Billing, the URL should begin with:\nhttps:\u002F\u002Fbilling.churnkey.co\u002Fe\u002Fsrc_e0rghrqmqzovw2?appId= Navigate to Paddle | Notifications or use this link for Sandbox mode.Click \"+ New destination\" button on the top right corner.Set the webhook URL to the one you copied in step 1.Mark all checkboxes under the sections Transaction, Subscription, and Customer.Save the destination.After saving, click the ellipsis button next to it and select 'Edit destination'.You will see a screen similar to the one for adding the destination, but it now includes an additional, read-only field: \"Secret key\". Please copy it.Enter your Webhook Secret in Churnkey | Billing Provider",{"id":3340,"title":3288,"titles":3341,"content":422,"level":374},"\u002Fbilling-providers\u002Fpaddle-billing#how-is-the-paddle-connection-used",[230],{"id":3343,"title":3110,"titles":3344,"content":3345,"level":417},"\u002Fbilling-providers\u002Fpaddle-billing#i-add-discounts-to-your-cancel-flow",[230,3288],"After you connect Paddle (live mode), you'll be able to offer your customers temporary discounts as part of your Cancel Flow. Please note that you will only see your Paddle coupons after you've connected your live Paddle account. We only pull in coupons from the live account, not your Stripe test account. In the \"Offers\" tab of the flow builder, choose \"Apply Paddle Coupon\"Choose the coupon you'd like to offer customers in the dropdown labeled \"Paddle Coupon\"",{"id":3347,"title":3115,"titles":3348,"content":3297,"level":417},"\u002Fbilling-providers\u002Fpaddle-billing#ii-personalize-cancel-flows-with-customer-attributes-and-segmentation",[230,3288],{"id":3350,"title":3120,"titles":3351,"content":3352,"level":417},"\u002Fbilling-providers\u002Fpaddle-billing#iii-updating-customer-subscriptions-on-your-behalf",[230,3288],"When a customer goes through your Cancel Flow and either chooses to cancel their subscription or (hopefully) accept a discount offer, Churnkey will take care of the billing and subscription updates for you. Churnkey is able to perform the following actions on your behalf: Pause a subscriptionApply a discountCancel a subscriptionExtend a trial periodSwitch plans We'll look at each one of these in a little bit more detail.",{"id":3354,"title":3238,"titles":3355,"content":3356,"level":958},"\u002Fbilling-providers\u002Fpaddle-billing#pause-a-subscription",[230,3288,3120],"Churnkey uses Paddle’s built-in pause feature which will schedule a pause starting the next billing date, for the selected duration. 💡 We plan to add support for a configurable option soon, allowing you to apply pauses immediately, rather than scheduling them for the end of the billing period. If you need this feature, please feel free to reach out and inform us, so we can notify you once it's implemented. You can specify the maximum pause length allowed by customers while configuring your Cancel Flow. When a customer chooses to temporarily pause their account, if a subscription ID was provided when initializing the Churnkey embed, the subscription under that ID will be canceled. If just a customer ID is provided, each active subscription will be paused. If you would like to implement a custom function for handling pause events, you can do so using the handlePause callback (see Custom Callbacks).",{"id":3358,"title":3129,"titles":3359,"content":3360,"level":958},"\u002Fbilling-providers\u002Fpaddle-billing#apply-a-discount",[230,3288,3120],"In the Churnkey offer builder, you can offer specific discounts to customers. These discounts are populated based on the coupons that you have created in your Paddle account (in Paddle | Catalog | Discounts, or here for Sandbox mode). When a customer accepts a discount, if a subscription ID was provided when initializing the Churnkey embed, the coupon will be applied to that subscription. If just a customer ID is provided, that coupon is applied directly to the Paddle customer account. If you would like to implement a customer function for applying discounts, you can do so using the handleDiscount callback (see Custom Callbacks).",{"id":3362,"title":3247,"titles":3363,"content":3364,"level":958},"\u002Fbilling-providers\u002Fpaddle-billing#cancel-a-subscription",[230,3288,3120],"Churnkey can manage the cancellation of your customer's subscription. When a customer decides to cancel, we will schedule the subscription cancellation at the end of the current period. 💡 We plan to add support for a configurable option soon, allowing you to apply cancellations immediately, rather than scheduling them for the end of the billing period. If you need this feature, please feel free to reach out and inform us, so we can notify you once it's implemented. When a customer chooses to cancel their account, if a subscription ID was provided when initializing the Churnkey embed, the subscription under that ID will be canceled. If just a customer ID is provided, each active, delinquent, and past due subscription will be canceled. If you would like to implement a customer function for canceling subscriptions, you can do so using the handleCancel callback (see Custom Callbacks).",{"id":3366,"title":3252,"titles":3367,"content":3254,"level":958},"\u002Fbilling-providers\u002Fpaddle-billing#extend-a-trial-period",[230,3288,3120],{"id":3369,"title":3257,"titles":3370,"content":3254,"level":958},"\u002Fbilling-providers\u002Fpaddle-billing#switch-plans",[230,3288,3120],{"id":3372,"title":3149,"titles":3373,"content":3374,"level":417},"\u002Fbilling-providers\u002Fpaddle-billing#iv-calculating-boosted-revenue",[230,3288],"Once you've connected your Paddle account, you'll be able to track customer reactivations and boosted revenue - that's all the extra revenue you receive from customers who stayed on instead of canceling. You can track boosted revenue on your Churnkey dashboard.",{"id":235,"title":234,"titles":3376,"content":3377,"level":368},[],"Integrate Churnkey with your Braintree account",{"id":3379,"title":3380,"titles":3381,"content":3382,"level":374},"\u002Fbilling-providers\u002Fbraintree#how-to-connect-your-braintree-account","How to Connect Your Braintree Account",[234],"After you register for a Churnkey account, you can connect it to your Braintree account(s) on our Settings | Billing Provider page. You can enter both production and sandbox credentials. The sandbox credentials are optional, but great to add if you want to test your Cancel Flow in a staging environment before going live with Churnkey. Manage Braintree Connection",{"id":3384,"title":3385,"titles":3386,"content":3387,"level":417},"\u002Fbilling-providers\u002Fbraintree#step-1-braintree-user-with-sufficient-permissions","Step 1. Braintree User with sufficient permissions",[234,3380],"Braintree API credentials belong to a specific user, and each user has a role. You can view all users associated with your Braintree account on the Team page of the Braintree web app. Login to Braintree The API key that you associate with Churnkey must have sufficient user and role permissions. Option A: Use an Account AdminEnsure that you are logged in with a user which has Account Admin and API Access permissions, as pictured belowAccount Admin user example (you can use an existing user, or create a new one for this purpose) Option B: Create a new User and Role for ChurnkeyIf you would rather create a new role and associated user, we've listed the required permissions below:Required User\u002FRole Permissions",{"id":3389,"title":3390,"titles":3391,"content":3392,"level":417},"\u002Fbilling-providers\u002Fbraintree#step-2-generate-an-api-key","Step 2. Generate an API Key",[234,3380],"Head to the API settings - click the gear icon in the top right → API.Click \"+ Generate New API Key\"Click \"View\" on the newly generated API key to reveal the private key",{"id":3394,"title":3283,"titles":3395,"content":3396,"level":417},"\u002Fbilling-providers\u002Fbraintree#step-3-add-webhook-notifications",[234,3380],"In order for Churnkey to track reactivation rates (after pauses and discounts), and keep track of total boosted revenue from your Cancel Flow, you need to add a webhook in your Braintree account. Under the Webhooks tab of the API management settings page in Braintree, add a webhook at URL https:\u002F\u002Fbilling.churnkey.co\u002Fe\u002Fsrc_3owuSFmHG965?appId=YOUR_CHURNKEY_APP_ID (please note to replace YOUR_CHURNKEY_APP_ID with your App ID - your account specific URL can be found on your Churnkey settings page) You must enable notifications for all subscription events. Example Webhook",{"id":3398,"title":3399,"titles":3400,"content":3401,"level":417},"\u002Fbilling-providers\u002Fbraintree#step-4-add-api-credentials-to-churnkey","Step 4. Add API Credentials to Churnkey",[234,3380],"The API credentials (merchant ID, public key, private key) can be linked to your Churnkey account on the Billing Provider Settings page: Manage Braintree Connection",{"id":3403,"title":3404,"titles":3405,"content":422,"level":374},"\u002Fbilling-providers\u002Fbraintree#how-is-the-braintree-connection-used","How is the Braintree connection used?",[234],{"id":3407,"title":3110,"titles":3408,"content":3409,"level":417},"\u002Fbilling-providers\u002Fbraintree#i-add-discounts-to-your-cancel-flow",[234,3404],"Discounts in Test Mode\nIf you want to test discounts in your flow in test mode, please create discounts in your Braintree Sandbox account with IDs that match the discounts that you've selected to be part of your Cancel Flow. For instance, if you have added a discount with id 40_off_2_months in Braintree production mode, this discount will also need to exist in your Braintree sandbox account to use in Churnkey's test mode. After you connect Braintree (production mode), you'll be able to offer your customers temporary discounts as part of your Cancel Flow. Please note that you will only see your Braintree discounts after you've connected your production Braintree account. We only pull in discount from the production account, not your Braintree sandbox account. In the \"Offers\" tab of the flow builder, choose \"Apply Braintree Discount\"Choose the discount you'd like to offer customers in the dropdown labeled \"Braintree Discount\"",{"id":3411,"title":3115,"titles":3412,"content":3413,"level":417},"\u002Fbilling-providers\u002Fbraintree#ii-personalize-cancel-flows-with-customer-attributes-and-segmentation",[234,3404],"Use customer segmentation to reduce voluntary churn with targeted Cancel Flows. With segmentation, you can target specific customers based on subscription attributes and serve up unique Cancel Flows for each of them. After all, someone who signed up yesterday should be spoken to differently from a customer who's been a paying subscriber for years. 🚀 Customer Segmentation Launch Details Available attributes for segmentation PlanPriceSubscription ageSubscription creation dateBilling interval (monthly, annual, etc.)Trial vs Active subscription",{"id":3415,"title":3120,"titles":3416,"content":3417,"level":417},"\u002Fbilling-providers\u002Fbraintree#iii-updating-customer-subscriptions-on-your-behalf",[234,3404],"When a customer goes through your Cancel Flow and (hopefully) accepts a pause or discount offer, Churnkey takes care of the billing updates for you. Churnkey is able to perform the following actions on your behalf Pause a subscriptionApply a discountCancel a subscription We'll look at each one of these in a little bit more detail.",{"id":3419,"title":132,"titles":3420,"content":3421,"level":958},"\u002Fbilling-providers\u002Fbraintree#pause-subscription",[234,3404,3120],"Braintree does not offer a \"pause\" feature out-of-the-box. Instead, Churnkey can emulate a billing pause on your behalf by temporarily applying a 100% discount. In order to do this, you will need to create a discount with id CHURNKEY_PAUSE. This discount can be for any amount. If a customer selects to pause their account through your cancellation flow, Churnkey will match the discount amount to the price of the subscription, in effect creating a temporary 100% discount on the subscription for the selected duration. If you would like to implement a customer function for applying pauses, you can do so using the handlePause callback (see Custom Callbacks).",{"id":3423,"title":3129,"titles":3424,"content":3425,"level":958},"\u002Fbilling-providers\u002Fbraintree#apply-a-discount",[234,3404,3120],"In the Churnkey offer builder, you can offer specific discounts to customers. These discounts are populated based on the discounts that you have created in your Braintree account. When a customer accepts a discount, if a subscription ID was provided when initializing the Churnkey embed, the discount will be applied to that subscription. If just a customer ID is provided, that discount is applied directly to every active subscription under that customer's ID. If you would like to implement a customer function for applying discounts, you can do so using the handleDiscount callback (see Custom Callbacks).",{"id":3427,"title":3139,"titles":3428,"content":3429,"level":958},"\u002Fbilling-providers\u002Fbraintree#cancel-subscription",[234,3404,3120],"Churnkey can handle canceling your customer's subscription for you. By default, a customer's subscription will be set to cancel at the end of the current period. Under the hood, Churnkey uses Braintree's built-in subscription cancellation method. If you would like to implement a customer function for canceling subscriptions, you can do so using the handleCancel callback (see Custom Callbacks).",{"id":3431,"title":3149,"titles":3432,"content":3433,"level":417},"\u002Fbilling-providers\u002Fbraintree#iv-calculating-boosted-revenue",[234,3404],"Once you've connected your Braintree account, you'll be able to track customer reactivations and boosted revenue - that's all the extra revenue you receive from customers who stayed on instead of canceling. You can track boosted revenue on your Churnkey dashboard.",{"id":3435,"title":3436,"titles":3437,"content":422,"level":374},"\u002Fbilling-providers\u002Fbraintree#appendix","Appendix",[234],{"id":3439,"title":3440,"titles":3441,"content":3442,"level":417},"\u002Fbilling-providers\u002Fbraintree#required-userrole-permissions","Required User\u002FRole Permissions",[234,3436],"If you decide to create a new role and user for connecting Churnkey to your Braintree account, please ensure it has sufficient permissions, as outlined below.",{"id":3444,"title":3445,"titles":3446,"content":3447,"level":958},"\u002Fbilling-providers\u002Fbraintree#required-role-permissions","Required Role Permissions",[234,3436,3440],"The following Braintree API role credentials are necessary: TransactionsFor reactivation analytics (boosted revenue) that you'll see on your Churnkey dashboard; and dunningCustomer ManagementFor dunningRecurring BillingTo take care of subscription billing actions on your behalf (discounts, pauses, cancellations)WebhooksFor reactivation and subscription analytics that you'll see on your Churnkey dashboard Screenshot of example role with sufficient permissions",{"id":3449,"title":3450,"titles":3451,"content":3452,"level":958},"\u002Fbilling-providers\u002Fbraintree#required-user-permissions","Required User Permissions",[234,3436,3440],"If you are creating a new user, associate it with the Account Admin role, or with a newly created Churnkey role, as described above. ☑️ API Access☑️ Roles☑️ Account Admin; or☑️ Churnkey (as created above)☑️ Merchant Accounts☑️ All",{"id":239,"title":238,"titles":3454,"content":3455,"level":368},[],"Integrate Churnkey with your Maxio (formerly Chargify) account",{"id":3457,"title":3458,"titles":3459,"content":3460,"level":374},"\u002Fbilling-providers\u002Fmaxio#how-to-connect-your-maxio-account","How to Connect Your Maxio Account",[238],"After you register for a Churnkey account, you can connect it to your Maxio account on our Settings | Billing Provider page. You can enter both test site and live site credentials. The test credentials are optional, but recommended for testing your Cancel Flow in a staging environment before going live with Churnkey. Manage Maxio Connection in Churnkey",{"id":3462,"title":3463,"titles":3464,"content":3465,"level":417},"\u002Fbilling-providers\u002Fmaxio#step-1-creating-an-api-key","Step 1. Creating an API Key",[238,3458],"Your API key is used to apply pauses, discounts, and cancellations to customer subscriptions on your behalf. Here's how to generate one: Log in to your Maxio accountNavigate to Config - Integrations - API KeysClick \"New API Key\"Copy the API KeyAdd the API Key to your Churnkey account on the Billing Provider Settings pageRecommended: Once an API key is created, you can navigate back to the API Keys page and add a description to it to help you identify it as the Churnkey API key.",{"id":3467,"title":3468,"titles":3469,"content":422,"level":374},"\u002Fbilling-providers\u002Fmaxio#how-is-the-maxio-connection-used","How is the Maxio connection used?",[238],{"id":3471,"title":3110,"titles":3472,"content":3473,"level":417},"\u002Fbilling-providers\u002Fmaxio#i-add-discounts-to-your-cancel-flow",[238,3468],"Coupons in Test Mode\nWhen testing coupons in your flow in test mode, we recommend creating coupons in your Maxio test site with the same code as your production coupons. For example, if you have a coupon code \"40-OFF-2-MONTHS\" in production, create an identical coupon in your test site with the same code. After connecting Maxio, you will be able to offer discounts in your Cancel Flow. While building your flow, note that coupons are only pulled from your live Maxio account, not the test site that you connected. In the \"Offers\" tab of the flow builder, select \"Apply Maxio Coupon\"Choose your desired coupon from the \"Maxio Coupon\" dropdown",{"id":3475,"title":3115,"titles":3476,"content":3477,"level":417},"\u002Fbilling-providers\u002Fmaxio#ii-personalize-cancel-flows-with-customer-attributes-and-segmentation",[238,3468],"Create targeted Cancel Flows using customer segmentation based on subscription attributes. This allows you to provide different experiences for different customer segments. Available attributes for segmentation ProductPrice PointSubscription ageSubscription creation dateBilling interval (monthly, quarterly, annual, etc)Trial vs Active subscription",{"id":3479,"title":3480,"titles":3481,"content":3482,"level":417},"\u002Fbilling-providers\u002Fmaxio#iii-updating-customer-subscriptions","III. Updating customer subscriptions",[238,3468],"Churnkey manages subscription updates automatically when customers interact with your Cancel Flow. Here are the actions Churnkey can perform:",{"id":3484,"title":3238,"titles":3485,"content":3486,"level":958},"\u002Fbilling-providers\u002Fmaxio#pause-a-subscription",[238,3468,3480],"Churnkey implements subscription pausing through Maxio's hold functionality. When a customer pauses their subscription: The subscription status is changed to on_holdA resume date is set based on the selected pause durationBilling is suspended until the resume date You can configure maximum pause durations in your offboarding flow settings.",{"id":3488,"title":3129,"titles":3489,"content":3490,"level":958},"\u002Fbilling-providers\u002Fmaxio#apply-a-discount",[238,3468,3480],"Discounts are applied to customer subscriptions using Maxio's coupon system. An existing subscription can accommodate multiple discounts\u002Fcoupon codes. This is only applicable if each coupon is stackable. For more information on stackable coupons, we recommend reviewing Maxio's coupon documentation.",{"id":3492,"title":3247,"titles":3493,"content":3494,"level":958},"\u002Fbilling-providers\u002Fmaxio#cancel-a-subscription",[238,3468,3480],"By default, Churnkey sets subscriptions to cancel at period end using Maxio's initiateDelayedCancellation method. Immediate cancellation is also available, which uses the cancelSubscription method from Maxio's API.",{"id":3496,"title":3497,"titles":3498,"content":3499,"level":958},"\u002Fbilling-providers\u002Fmaxio#switch-plans","Switch Plans",[238,3468,3480],"Plan changes are handled through Maxio's migrateSubscriptionProduct method. Optionally, you can choose to preservePeriod by setting the proration setting to true in Churnkey Cancel Flow Settings.",{"id":248,"title":247,"titles":3501,"content":3502,"level":368},[],"Integrate Churnkey with any billing system using Direct mode Churnkey Direct lets you integrate retention flows with any billing system. Instead of connecting to Stripe or Chargebee, you pass customer and subscription data directly and handle billing operations through callbacks. Use Direct mode when: You use a custom or unsupported billing systemYou have strict data privacy requirementsYou're managing trial or freemium users without subscriptionsYou need to test flows before setting up a full integration",{"id":3504,"title":1205,"titles":3505,"content":3506,"level":374},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect#how-it-works",[247],"Pass customer data when initializing ChurnkeyCustomer interacts with your retention flowYour handlers execute billing operations (cancel, discount, pause, etc.)Churnkey tracks results in analytics automatically",{"id":3508,"title":3509,"titles":3510,"content":3511,"level":374},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect#quick-start","Quick Start",[247],"window.churnkey.init('show', {\n  appId: 'your_app_id',\n  mode: 'live',\n  provider: 'direct',\n  authHash: 'hmac_signature', \u002F\u002F See Authentication section\n\n  customer: {\n    id: 'cus_12345',\n    email: 'customer@example.com',\n  },\n\n  subscriptions: [\n    {\n      id: 'sub_67890',\n      start: new Date('2024-06-01'), \u002F\u002F When subscription started\n      status: {\n        name: 'active',\n        currentPeriod: {\n          start: new Date('2025-01-01'),\n          end: new Date('2025-02-01'),\n        },\n      },\n      items: [\n        {\n          price: {\n            id: 'price_pro',\n            amount: { value: 2999, currency: 'usd' },\n            interval: 'month',\n            intervalCount: 1,\n          },\n        },\n      ],\n    },\n  ],\n\n  async handleCancel(customer, surveyChoice, feedback, followupResponse) {\n    await yourAPI.cancelSubscription(customer.id);\n    return { message: 'Subscription canceled.' };\n  },\n}); For script installation and HMAC authentication, see the Quick Start Guide.",{"id":3513,"title":3514,"titles":3515,"content":3516,"level":374},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect#customer-object","Customer object",[247],"Identifies who is interacting with the retention flow. Only id is required. Usage: Additional fields like email, name, and metadata are used for segmentation, Slack notifications, merge fields, and analytics.",{"id":3518,"title":3519,"titles":3520,"content":3521,"level":374},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect#subscription-object","Subscription object",[247],"Describes the customer's billing state and determines which offers are shown.",{"id":3523,"title":3524,"titles":3525,"content":3526,"level":417},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect#subscription-status","Subscription status",[247,3519],"The status field determines which offers are available. For example, trial extensions only appear for trial subscriptions. status: {\n  name: 'active',\n  currentPeriod: {\n    start: new Date('2025-01-01'),\n    end: new Date('2025-02-01'),\n  },\n}\nstatus: {\n  name: 'trial',\n  trial: {\n    start: new Date('2025-01-01'),\n    end: new Date('2025-01-14'),\n  },\n  \u002F\u002F currentPeriod is optional - defaults to trial period\n}\nstatus: {\n  name: 'paused',\n  pause: {\n    start: new Date('2025-01-15'),\n    end: new Date('2025-02-15'), \u002F\u002F Omit for indefinite pause\n  },\n  currentPeriod: {\n    start: new Date('2025-01-01'),\n    end: new Date('2025-02-01'),\n  },\n}\nstatus: {\n  name: 'canceled',\n  canceledAt: new Date('2025-01-15'),\n}\nstatus: {\n  name: 'unpaid',\n  currentPeriod: {\n    start: new Date('2025-01-01'),\n    end: new Date('2025-02-01'),\n  },\n}",{"id":3528,"title":3529,"titles":3530,"content":3531,"level":417},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect#subscription-items","Subscription items",[247,3519],"Items represent the products and pricing on a subscription: items: [\n  {\n    id: 'si_basic',\n    price: {\n      id: 'price_basic_monthly',\n      amount: { value: 2999, currency: 'usd' }, \u002F\u002F $29.99\n      interval: 'month',\n      intervalCount: 1,\n    },\n    quantity: 1,\n    product: {\n      id: 'prod_basic',\n      name: 'Basic Plan',\n    },\n  },\n];\nitems: [\n  {\n    id: 'si_pro',\n    price: {\n      id: 'price_pro_annual',\n      amount: { value: 29900, currency: 'usd' }, \u002F\u002F $299\u002Fyear\n      interval: 'year',\n      intervalCount: 1,\n    },\n    quantity: 1,\n    product: {\n      id: 'prod_pro',\n      name: 'Pro Plan',\n    },\n  },\n];\nitems: [\n  {\n    id: 'si_team',\n    price: {\n      id: 'price_per_seat',\n      amount: { value: 1500, currency: 'usd' }, \u002F\u002F $15\u002Fseat\n      interval: 'month',\n      intervalCount: 1,\n    },\n    quantity: 10, \u002F\u002F 10 seats\n    product: {\n      id: 'prod_team',\n      name: 'Team Plan',\n    },\n  },\n]; See Direct mode examples for usage-based billing, paused subscriptions, and complex scenarios.",{"id":3533,"title":3534,"titles":3535,"content":3536,"level":374},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect#handlers","Handlers",[247],"Handlers execute billing operations when customers accept offers. Each offer requires its corresponding handler—if you configure a pause offer in the Flow Builder but don't provide handlePause, the offer won't appear. async handleCancel(customer, surveyChoice, feedback, followupResponse) {\n  \u002F\u002F surveyChoice: Selected cancellation reason (string)\n  \u002F\u002F feedback: Additional text feedback (string | null)\n  \u002F\u002F followupResponse: Follow-up question answers (object | null)\n\n  try {\n    await yourAPI.cancelSubscription(customer.id);\n    return { message: 'Your subscription has been canceled.' };\n  } catch (error) {\n    \u002F\u002F Error messages are shown to customers\n    throw new Error('Unable to cancel. Please contact support.');\n  }\n}\nasync handlePause(customer, pauseOptions) {\n  \u002F\u002F pauseOptions: {\n  \u002F\u002F   pauseEndDate: Date,\n  \u002F\u002F   pauseDurationDays: number\n  \u002F\u002F }\n\n  await yourAPI.pauseSubscription(\n    customer.id,\n    pauseOptions.pauseEndDate\n  );\n\n  return {\n    message: `Paused until ${pauseOptions.pauseEndDate.toLocaleDateString()}`,\n  };\n}\nasync handleDiscount(customer, coupon) {\n  \u002F\u002F coupon: {\n  \u002F\u002F   id: string,\n  \u002F\u002F   percentOff?: number,\n  \u002F\u002F   amountOff?: number,\n  \u002F\u002F   duration: 'once' | 'repeating' | 'forever',\n  \u002F\u002F   durationInMonths?: number\n  \u002F\u002F }\n\n  await yourAPI.applyDiscount(customer.id, {\n    percentOff: coupon.percentOff,\n    duration: coupon.duration,\n  });\n\n  return { message: `${coupon.percentOff}% discount applied!` };\n}\nasync handlePlanChange(customer, planOptions) {\n  \u002F\u002F planOptions: {\n  \u002F\u002F   newPriceId: string,\n  \u002F\u002F   oldPriceId: string\n  \u002F\u002F }\n\n  await yourAPI.updateSubscriptionPlan(\n    customer.id,\n    planOptions.newPriceId\n  );\n\n  return { message: 'Plan updated successfully' };\n}\nasync handleTrialExtension(customer, trialOptions) {\n  \u002F\u002F trialOptions: {\n  \u002F\u002F   trialExtensionDays: number,\n  \u002F\u002F   newTrialEndDate: Date\n  \u002F\u002F }\n\n  await yourAPI.extendTrial(\n    customer.id,\n    trialOptions.newTrialEndDate\n  );\n\n  return { message: `Trial extended ${trialOptions.trialExtensionDays} days` };\n}\nhandleSupportRequest(customer) {\n  \u002F\u002F Open your support widget or redirect\n  if (window.Intercom) {\n    window.Intercom('show');\n  } else {\n    window.location.href = '\u002Fsupport';\n  }\n}\nhandleRedirect(customer, url) {\n  window.location.href = url;\n}",{"id":3538,"title":3539,"titles":3540,"content":3541,"level":374},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect#configuring-offers","Configuring offers",[247],"Configure offers in the Flow Builder. Churnkey automatically shows or hides offers based on: Subscription status — Trial extensions only appear for trial subscriptionsBilling interval — Annual customers won't see pause offers by defaultHandler availability — Offers only appear if you've implemented the handler You configure offer values (discount percentages, pause durations, etc.) in the Flow Builder, then implement the billing changes in your handlers.",{"id":3543,"title":3544,"titles":3545,"content":3546,"level":374},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect#segmentation","Segmentation",[247],"Create targeted flows for different customer segments based on: Subscription attributes — Status, billing interval, subscription durationCustomer metadata — Custom attributes via customer.metadata and subscription.metadataBilling amounts — MRR, subscription value, pricing tier Configure segments in the Flow Builder under Audience Targeting. Learn more about segmentation → html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sIO_5, html code.shiki .sIO_5{--shiki-light:#F76D47;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sjIoE, html code.shiki .sjIoE{--shiki-light:#E53935;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .szk7O, html code.shiki .szk7O{--shiki-light:#90A4AE;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}",{"id":252,"title":251,"titles":3548,"content":3549,"level":368},[],"Real-world examples for common subscription scenarios Practical examples showing how to structure customer and subscription data for different billing scenarios. All examples show the subscriptions array, which should be combined with customer and handleCancel from the Churnkey Direct guide.",{"id":3551,"title":3552,"titles":3553,"content":422,"level":374},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect-examples#basic-subscriptions","Basic subscriptions",[251],{"id":3555,"title":3556,"titles":3557,"content":3558,"level":417},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect-examples#monthly-subscription","Monthly subscription",[251,3552],"subscriptions: [\n  {\n    start: new Date('2025-06-01'),\n    id: 'sub_monthly',\n    status: {\n      name: 'active',\n      currentPeriod: {\n        start: new Date('2025-01-01'),\n        end: new Date('2025-02-01'),\n      },\n    },\n    items: [\n      {\n        price: {\n          id: 'price_pro_monthly',\n          name: 'Pro Plan',\n          interval: 'month',\n          intervalCount: 1,\n          amount: { value: 2999, currency: 'usd' }, \u002F\u002F $29.99\n        },\n      },\n    ],\n  },\n];",{"id":3560,"title":3561,"titles":3562,"content":3563,"level":417},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect-examples#annual-subscription","Annual subscription",[251,3552],"subscriptions: [\n  {\n    start: new Date('2025-06-01'),\n    id: 'sub_annual',\n    status: {\n      name: 'active',\n      currentPeriod: {\n        start: new Date('2025-06-01'),\n        end: new Date('2026-06-01'),\n      },\n    },\n    items: [\n      {\n        price: {\n          id: 'price_pro_annual',\n          name: 'Pro Plan - Annual',\n          interval: 'year',\n          intervalCount: 1,\n          amount: { value: 29900 }, \u002F\u002F $299\u002Fyear\n        },\n      },\n    ],\n  },\n];",{"id":3565,"title":3566,"titles":3567,"content":3568,"level":417},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect-examples#quarterly-subscription","Quarterly subscription",[251,3552],"subscriptions: [\n  {\n    start: new Date('2025-06-01'),\n    id: 'sub_quarterly',\n    status: {\n      name: 'active',\n      currentPeriod: {\n        start: new Date('2025-09-01'),\n        end: new Date('2025-12-01'),\n      },\n    },\n    items: [\n      {\n        price: {\n          id: 'price_quarterly',\n          name: 'Quarterly Plan',\n          interval: 'month',\n          intervalCount: 3,\n          amount: { value: 7999 }, \u002F\u002F $79.99 every 3 months\n        },\n      },\n    ],\n  },\n];",{"id":3570,"title":3571,"titles":3572,"content":422,"level":374},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect-examples#subscription-statuses","Subscription statuses",[251],{"id":3574,"title":3575,"titles":3576,"content":3577,"level":417},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect-examples#trial-subscription","Trial subscription",[251,3571],"subscriptions: [\n  {\n    start: new Date('2025-06-01'),\n    id: 'sub_trial',\n    status: {\n      name: 'trial',\n      trial: {\n        start: new Date('2026-01-01'),\n        end: new Date('2026-01-14'),\n      },\n      \u002F\u002F currentPeriod optional - defaults to trial period\n    },\n    items: [\n      {\n        price: {\n          id: 'price_pro',\n          name: 'Pro Plan',\n          amount: { value: 2999 },\n        },\n      },\n    ],\n  },\n];",{"id":3579,"title":3580,"titles":3581,"content":3582,"level":417},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect-examples#paused-subscription","Paused subscription",[251,3571],"subscriptions: [\n  {\n    start: new Date('2025-06-01'),\n    id: 'sub_paused',\n    status: {\n      name: 'paused',\n      pause: {\n        start: new Date('2026-01-15'),\n        end: new Date('2026-02-15'), \u002F\u002F Omit for indefinite pause\n      },\n      currentPeriod: {\n        start: new Date('2026-01-01'),\n        end: new Date('2026-02-01'),\n      },\n    },\n    items: [\n      {\n        price: {\n          id: 'price_pro',\n          name: 'Pro Plan',\n          amount: { value: 2999 },\n        },\n      },\n    ],\n  },\n];",{"id":3584,"title":3585,"titles":3586,"content":422,"level":374},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect-examples#advanced-pricing","Advanced pricing",[251],{"id":3588,"title":3589,"titles":3590,"content":3591,"level":417},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect-examples#per-seat-pricing","Per-seat pricing",[251,3585],"Subscription with quantity-based pricing: subscriptions: [\n  {\n    start: new Date('2025-06-01'),\n    id: 'sub_team',\n    status: {\n      name: 'active',\n      currentPeriod: {\n        start: new Date('2025-11-01'),\n        end: new Date('2025-12-01'),\n      },\n    },\n    items: [\n      {\n        price: {\n          id: 'price_per_seat',\n          name: 'Team Plan',\n          interval: 'month',\n          intervalCount: 1,\n          amount: { value: 1500 }, \u002F\u002F $15 per seat\n        },\n        quantity: 10, \u002F\u002F 10 seats = $150\u002Fmonth total\n      },\n    ],\n  },\n];",{"id":3593,"title":3594,"titles":3595,"content":3596,"level":417},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect-examples#multi-item-subscription","Multi-item subscription",[251,3585],"Customer with base plan plus add-ons: subscriptions: [\n  {\n    start: new Date('2025-06-01'),\n    id: 'sub_bundle',\n    status: {\n      name: 'active',\n      currentPeriod: {\n        start: new Date('2025-10-01'),\n        end: new Date('2025-11-01'),\n      },\n    },\n    items: [\n      {\n        price: {\n          id: 'price_base',\n          name: 'Base Plan',\n          amount: { value: 2999 },\n        },\n      },\n      {\n        price: {\n          id: 'price_analytics_addon',\n          name: 'Analytics Add-on',\n          amount: { value: 500 },\n        },\n        quantity: 2, \u002F\u002F 2 analytics add-ons\n      },\n    ],\n  },\n];",{"id":3598,"title":3599,"titles":3600,"content":3601,"level":417},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect-examples#non-usd-currency","Non-USD currency",[251,3585],"Customer in different currency: customer: {\n  id: 'cus_uk',\n  email: 'user@example.co.uk',\n  currency: 'gbp',\n},\n\nsubscriptions: [{\n  id: 'sub_uk',\n  start: new Date('2025-06-01'),\n  status: {\n    name: 'active',\n    currentPeriod: {\n      start: new Date('2025-01-01'),\n      end: new Date('2025-02-01'),\n    },\n  },\n  items: [{\n    price: {\n      id: 'price_pro_gbp',\n      name: 'Pro Plan',\n      amount: { value: 2499, currency: 'gbp' }, \u002F\u002F £24.99\n    },\n  }],\n}]",{"id":3603,"title":3604,"titles":3605,"content":3606,"level":374},"\u002Fbilling-providers\u002Fdirect-connect\u002Fdirect-examples#custom-metadata","Custom metadata",[251],"Pass custom attributes for segmentation and analytics: customer: {\n  id: 'cus_enterprise',\n  email: 'admin@bigcorp.com',\n  name: 'Enterprise Admin',\n  metadata: {\n    account_type: 'enterprise',\n    team_size: 35,\n    industry: 'saas',\n  },\n},\n\nsubscriptions: [{\n  id: 'sub_enterprise',\n  start: new Date('2025-01-01'),\n  status: {\n    name: 'active',\n    currentPeriod: {\n      start: new Date('2025-01-01'),\n      end: new Date('2026-01-01'),\n    },\n  },\n  items: [{\n    price: {\n      id: 'price_enterprise',\n      name: 'Enterprise Plan',\n      interval: 'year',\n      intervalCount: 1,\n      amount: { value: 120000 }, \u002F\u002F $1,200\u002Fyear\n    },\n  }],\n  metadata: {\n    contract_id: 'contract_2025_001',\n    renewal_date: '2026-01-01',\n  },\n}] Use these attributes to create targeted flows in the Flow Builder's Audience Targeting section. html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sIO_5, html code.shiki .sIO_5{--shiki-light:#F76D47;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"id":261,"title":260,"titles":3608,"content":3609,"level":368},[],"Use one email address to access multiple Churnkey workspaces and switch between them instantly. Many teams manage more than one product, business unit, or client account through Churnkey. Multi-Workspace Support lets a single email address belong to multiple workspaces, so you can access all of them without juggling separate logins or browser profiles.",{"id":3611,"title":3612,"titles":3613,"content":3614,"level":374},"\u002Faccount\u002Fmulti-workspace-support#what-is-a-workspace","What is a Workspace?",[260],"A workspace in Churnkey is an isolated environment where your organization configures Cancel Flows, payment recovery campaigns, analytics, and integrations. Each workspace connects to its own billing provider account and maintains its own data, team members, and settings. Before Multi-Workspace Support, every email address could belong to exactly one workspace. If you needed access to a second workspace — say, for a different product line or a client you manage — you had to create a separate account with a different email. That meant separate passwords, separate sessions, and constant switching between browser windows. Multi-Workspace Support removes that limitation. One email, many workspaces. You log in once and move between workspaces in seconds.",{"id":3616,"title":3617,"titles":3618,"content":3619,"level":374},"\u002Faccount\u002Fmulti-workspace-support#who-benefits-from-this","Who Benefits From This",[260],"Agencies and consultants that manage Churnkey on behalf of several clients can now operate from a single account. No more password managers full of client-specific credentials. Companies with multiple products that run separate Churnkey workspaces for each product line can give team members unified access across all of them. Operations and finance teams that need visibility into several workspaces for reporting or auditing purposes can do so without requesting separate invitations under throwaway email addresses.",{"id":3621,"title":3622,"titles":3623,"content":3624,"level":374},"\u002Faccount\u002Fmulti-workspace-support#getting-invited-to-multiple-workspaces","Getting Invited to Multiple Workspaces",[260],"There is nothing special you need to do. The process works the same way it always has — a workspace admin invites your email address through the Team page. If that email already belongs to another workspace, Churnkey links both workspaces to your account automatically. The Team page is where workspace admins manage members. Click Invite via Email to send an invitation. The page lists every current member along with their role (Admin, Member, Viewer) and notification preferences. You receive an invitation email for each workspace. Accept the invitation and you are in. The next time you log in, Churnkey detects that your account has access to more than one workspace and presents you with a workspace selector. There is no limit to the number of workspaces a single email can join. Your role (Owner, Admin, Member) is set per workspace, so you might be an Owner on your company's primary workspace and a Member on a secondary one.",{"id":3626,"title":3627,"titles":3628,"content":3629,"level":374},"\u002Faccount\u002Fmulti-workspace-support#how-login-works-with-multiple-workspaces","How Login Works With Multiple Workspaces",[260],"When your email belongs to a single workspace, login works exactly as it did before — you enter your credentials and land directly on your dashboard. When your email belongs to two or more workspaces, login adds one extra step: a workspace selection screen.",{"id":3631,"title":3632,"titles":3633,"content":3634,"level":417},"\u002Faccount\u002Fmulti-workspace-support#the-workspace-selection-screen","The Workspace Selection Screen",[260,3627],"After entering your credentials, you see a page titled \"Select workspace.\" with the subtitle \"Your account has access to multiple workspaces.\" This page lists every workspace your account can access. Each entry shows the workspace name and your role within that workspace. Click on the workspace you want to enter. Churnkey loads that workspace's dashboard immediately. There is no delay and no second authentication step — you are already logged in.",{"id":3636,"title":3637,"titles":3638,"content":3639,"level":417},"\u002Faccount\u002Fmulti-workspace-support#what-if-i-only-have-one-workspace","What if I Only Have One Workspace?",[260,3627],"Nothing changes. If your email belongs to a single workspace, you skip the selection screen entirely and land on your dashboard as usual. The workspace selection screen only appears when there are multiple workspaces to choose from.",{"id":3641,"title":3642,"titles":3643,"content":3644,"level":374},"\u002Faccount\u002Fmulti-workspace-support#switching-between-workspaces","Switching Between Workspaces",[260],"Once inside a workspace, you do not need to log out and back in to reach a different one. The workspace switcher in the sidebar lets you move between workspaces without leaving the application.",{"id":3646,"title":3647,"titles":3648,"content":3649,"level":417},"\u002Faccount\u002Fmulti-workspace-support#using-the-workspace-switcher","Using the Workspace Switcher",[260,3642],"Look at the top-left corner of the sidebar. If your account has access to multiple workspaces, you see a section displaying your current workspace name, its avatar (a colored circle with the first letter of the workspace name), and your role. A small arrow icon next to the name indicates that clicking will open the switcher. Click on this section to open a popover that lists all your available workspaces. The currently active workspace is marked with a filled radio indicator. Each workspace in the list shows its name, colored avatar, and your role. Click on a different workspace to switch to it. The transition is immediate — the dashboard reloads with the selected workspace's data, settings, and configurations. Notice how the sidebar updates to reflect the new workspace's name and available navigation items. Your previous workspace's state is preserved and will be exactly where you left it when you switch back.",{"id":3651,"title":3652,"titles":3653,"content":3654,"level":417},"\u002Faccount\u002Fmulti-workspace-support#what-stays-the-same-when-switching","What Stays the Same When Switching",[260,3642],"Your login session persists across switches. You do not need to re-enter your password or go through any additional verification. Switching workspaces changes the context of what you see in the dashboard, not your authentication state.",{"id":3656,"title":3657,"titles":3658,"content":3659,"level":417},"\u002Faccount\u002Fmulti-workspace-support#what-changes-when-switching","What Changes When Switching",[260,3642],"Everything tied to the workspace changes when you switch: Cancel Flow configurations, analytics data, billing provider connections, team members, and settings. Think of it as opening a completely different Churnkey account — but without the friction of logging out and back in. Your role may also change. You could be an Admin in one workspace and a Member in another, which means the settings and actions available to you will differ based on the workspace you are currently viewing.",{"id":3661,"title":3662,"titles":3663,"content":422,"level":374},"\u002Faccount\u002Fmulti-workspace-support#setting-up-your-team-across-workspaces","Setting Up Your Team Across Workspaces",[260],{"id":3665,"title":3666,"titles":3667,"content":3668,"level":417},"\u002Faccount\u002Fmulti-workspace-support#inviting-someone-to-multiple-workspaces","Inviting Someone to Multiple Workspaces",[260,3662],"If you manage two or more workspaces and want a colleague to have access to all of them, invite their email address from each workspace individually. There is no bulk-invite mechanism across workspaces because each workspace maintains its own team roster and role assignments. Go to the Team page in Workspace A and click Invite via Email. Enter the person's email address (for example, user@company.com) and select the role you want to assign. Then switch to Workspace B and repeat the process with the same email. They receive separate invitations for each workspace and can accept them independently.",{"id":3670,"title":3671,"titles":3672,"content":3673,"level":417},"\u002Faccount\u002Fmulti-workspace-support#managing-roles","Managing Roles",[260,3662],"Roles are assigned per workspace. When you invite someone, you choose what role they have in that specific workspace. Changing someone's role in Workspace A has no effect on their role in Workspace B. This design is intentional. It means a consultant can have full Admin access to their own company's workspace while having restricted Member access to a client's workspace — all from the same email address.",{"id":3675,"title":3676,"titles":3677,"content":3678,"level":374},"\u002Faccount\u002Fmulti-workspace-support#common-questions","Common Questions",[260],"Do I need to create a new account to join another workspace?\nNo. If you already have a Churnkey account, the workspace admin invites your existing email. Churnkey links the new workspace to your existing account automatically. Can I remove myself from a workspace?\nYes. Contact the workspace admin and ask them to remove your membership. This only affects your access to that specific workspace. All your other workspace memberships remain untouched. What happens if I am removed from all workspaces except one?\nYour login experience returns to normal — you skip the workspace selection screen and land directly on your remaining workspace's dashboard. The workspace switcher in the sidebar also disappears since there is nothing to switch to. Is my data shared between workspaces?\nNo. Each workspace is fully isolated. Customer data, Cancel Flow configurations, analytics, billing provider connections, and team settings are completely separate. Belonging to multiple workspaces gives you access to each, but does not merge or share any data between them. Does switching workspaces affect active Cancel Flows or campaigns?\nNo. Switching workspaces is purely a navigation action on your end. It changes what you see in the dashboard. It does not start, stop, or modify any active flows, campaigns, or experiments in any workspace. Can two workspaces connect to the same billing provider account?\nThis depends on your billing provider's setup and is unrelated to Multi-Workspace Support. Each workspace maintains its own billing provider connection configured independently in its settings. Will I be charged for having more than one organization?\nIf the workspaces belong to different companies, yes. However, Churnkey offers discounts for multiple businesses managed under the same account. Reach out to us via the in-app chat to discuss your specific situation and get the best pricing for your setup.",{"id":270,"title":269,"titles":3680,"content":3681,"level":368},[],"We want setting up Churnkey to be a breeze.",{"id":3683,"title":3684,"titles":3685,"content":3686,"level":417},"\u002Fsupport\u002Ffaqs#can-i-use-churnkey-with-stripe-portal","Can I use Churnkey with Stripe Portal?",[269],"One of the most common use cases of Churnkey is right along side Stripe Portal. While we cannot integrate directly into Stripe Portal, we can live next to each other under a \"Manage Subscription\" button to redirect to Stripe Portal, and a \"Cancel Subscription\" button for launching the Churnkey Cancel Flow. When using Churnkey's Cancel Flows alongside Stripe Portal, you should disable cancellation from the Stripe Portal. This can be done on the Stripe Portal settings page as shown below Turn off cancellation on Stripe Portal settings",{"id":3688,"title":3689,"titles":3690,"content":3691,"level":417},"\u002Fsupport\u002Ffaqs#how-should-i-set-up-my-stripe-settings-to-work-well-with-churnkey-precision-retries","How should I set up my Stripe settings to work well with Churnkey Precision Retries?",[269],"Churnkey's Precision Retries are designed to occur within a 30 day window after a failed payment. They will automatically stop if a payment is recovered, or if the underlying invoice is marked void or uncollectible. Below are the recommended Stripe settings for use with Precision Retries. Importantly, Churnkey will not automatically change the subscription status to uncollectible or unpaid. If you are relying on Stripe to do this for tax or other purposes, please consult with your technical team for making sure these continue to work as expected. For most companies, the best way to achieve this is to leave on Stripe's Smart Retries 4x within 1 month, and then marking the invoice and subscription status as desired. View recommended settings",{"id":3693,"title":3694,"titles":3695,"content":3696,"level":417},"\u002Fsupport\u002Ffaqs#what-are-some-best-practices-for-setting-up-cancel-flows","What are some best practices for setting up Cancel Flows?",[269],"Check out our guide for best practices for customer retention.",{"id":3698,"title":3699,"titles":3700,"content":3701,"level":417},"\u002Fsupport\u002Ffaqs#what-kind-of-retention-numbers-can-i-expect-to-see","What kind of retention numbers can I expect to see?",[269],"We've seen retention boosts from 10% all the way up to 42%. Some of the most important factors are price point, B2C vs. B2B, percentage of customers on annual plans, and your current churn rate. We'd be happy to hop on a call and give you an estimate of what improvement you're likely to see based on our in-house dataset.",{"id":3703,"title":3704,"titles":3705,"content":3706,"level":417},"\u002Fsupport\u002Ffaqs#how-does-pause-work-for-annual-customers","How does pause work for annual customers?",[269],"Pause offers won't be shown to your annual customers because of how billing providers handle this. Essentially, if an annual customer pauses their subscription for 2 months, billing providers (Stripe, Chargebee, Braintree) will stop collecting invoices for 2 months. If the customer's renewal date falls within that 2 month time frame, they'll essentially get the entire next year for free. If it does not fall within that time frame, the pause will have no effect. What we've seen work really well for annual customers is creating a lower percentage 12-month coupon (10 or 20%) and then tailoring the copy on the discount offer step to highlight that this is on top of the already discounted annual plan.",{"id":3708,"title":3709,"titles":3710,"content":3711,"level":417},"\u002Fsupport\u002Ffaqs#why-are-my-coupons-and-plans-from-my-payment-provider-not-showing-up","Why are my coupons and plans from my payment provider not showing up?",[269],"You will have to connect your live mode credentials for coupons and plans from Stripe, Chargebee, Paddle, and Braintree for them to show in the Cancel Flow builder on Churnkey. You can connect you live mode credentials at Churnkey | Settings | Billing Provider.",{"id":3713,"title":3714,"titles":3715,"content":3716,"level":417},"\u002Fsupport\u002Ffaqs#how-can-i-check-if-a-stripe-subscription-is-paused","How can I check if a Stripe subscription is paused?",[269],"You can detect a pause on the Stripe subscription object. We use Stripe's recommended \"Pause Collection\" method for handling pause, so the subscription will be updated with the following property (with an appropriate resumes_at date). {\n  pause_collection: {\n    behavior: \"mark_uncollectible\",\n    resumes_at: 1673536945\n  }\n}",{"id":3718,"title":3719,"titles":3720,"content":3721,"level":417},"\u002Fsupport\u002Ffaqs#what-if-i-need-to-use-custom-billing-logic","What if I need to use custom billing logic?",[269],"By default, Churnkey takes care of billing updates for you; this includes subscription pauses, discounts, plan changes, and cancellations. If you'd like to implement custom billing logic, you can use 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. {\n    handlePause: \u003CPromise>,\n    handleCancel: \u003CPromise>,\n    handleDiscount: \u003CPromise>,\n    handleTrialExtension: \u003CPromise>,\n    handleSupportRequest: \u003Cfunction>\n    handlePlanChange: \u003CPromise\u003C(customer, { plan })>,\n} In addition to handler callbacks, we also provide listener callbacks. More details on those here →",{"id":3723,"title":3724,"titles":3725,"content":3726,"level":417},"\u002Fsupport\u002Ffaqs#can-i-make-the-contact-us-button-open-an-email-to-our-support-team","Can I make the \"Contact Us\" button open an email to our support team?",[269],"Yes. You can achieve this by using the handleSupportRequest callback with something like the following, which will open up a new email to your email of choice and optionally a pre-filled email subject and body. window.churnkey.init('show', {\n  appId: 'YOUR_APP_ID',\n  customerId: 'STRIPE_CUSTOMER_ID',\n  authHash: 'HMAC_HASH,\n  ...\n  handleSupportRequest: (customer) => {\n    window.open(\n      `mailto:${customer.email}?subject=${emailSub}&body=${emailBody}`,\n      '_self'\n    );\n  },\n})",{"id":3728,"title":3729,"titles":3730,"content":3731,"level":417},"\u002Fsupport\u002Ffaqs#can-i-apply-prorations-when-a-customer-accepts-a-plan-change-offer-upgrades-and-downgrades","Can I apply prorations when a customer accepts a plan change offer (upgrades and downgrades)?",[269],"By default, subscription plan changes will create proration adjustments (either charges or credits). You can optionally switch off proration charges under Cancel Flows | Settings. More details on prorationFrom a subscription perspective, the subscription plan is always changed immediately. Stripe does offer two proration options,\n(a)  Create Proration Charges\nFor example, if a customer upgrades from a 10 USD per month subscription to a 20 USD option, they’re charged prorated amounts for the time spent on each option. Assuming the change occurred halfway through the billing period, the customer is billed an additional 5 USD: -5 USD for unused time on the initial price, and 10 USD for the remaining time on the new price.\nThe prorated amount is calculated as soon as the API updates the subscription. The current billing period’s start and end times are used to calculate the cost of the subscription before and after the change.\n(b) Proration behavior of \"none\"\nThe plan is still switched immediately, but the customer isn't billed (or credited) the difference in charges.\nThe nitty gritty of the Stripe options is detailed here:\nhttps:\u002F\u002Fstripe.com\u002Fdocs\u002Fbilling\u002Fsubscriptions\u002Fprorations Changing plans at the end of the customer billing cycleUnfortunately, this isn't possible with out-of-the-box Churnkey yet, just the two options to either prorate or not, as described above.\nStripe has recently released a Subscription Schedule feature, which should allow for changing plans at the end of the billing cycle. For the interested, you can use this in combination with Churnkey's handlePlanChange callback, which allows you to override the default billing action with custom logic. Otherwise, stay tuned for updates on this front.",{"id":3733,"title":3734,"titles":3735,"content":3736,"level":417},"\u002Fsupport\u002Ffaqs#how-can-i-change-the-fallback-behavior-in-case-of-error","How can I change the fallback behavior in case of error?",[269],"Head to https:\u002F\u002Fapp.churnkey.co\u002Fflows\u002FsettingsUpdate genericErrorDescription from its default “Please contact us” to a more specific message e.g. “Please contact us at support@acme.inc”",{"id":3738,"title":3739,"titles":3740,"content":3741,"level":417},"\u002Fsupport\u002Ffaqs#how-can-i-delete-test-data","How can I delete test data?",[269],"From the Cancel Flow Dashboard, scroll to the Activity Overview and click on the customer whose data you would like to clear. This will take you to that customer’s timeline. On that page, you should see an overflow menu which can be accessed through the three dots in the top right. Under that menu is an option to delete customer data, which will clear the Churnkey Cancel Flow sessions associated with that customer. Show GIF of deleting customer data",{"id":3743,"title":3744,"titles":3745,"content":3746,"level":417},"\u002Fsupport\u002Ffaqs#what-is-a-bounced-session","What is a \"bounced\" session?",[269],"A Cancel Flow session will get marked as \"bounced\" if another more relevant session takes place within 24 hours of that initial session. For instance, if a customer pauses and then decides to cancel within 24 hours, that first session with the pause offer accepted will be marked as bounced. Bounced sessions are not included in dashboard analytics.",{"id":3748,"title":3749,"titles":3750,"content":3751,"level":417},"\u002Fsupport\u002Ffaqs#when-can-we-expect-dunning-payment-recovery-for-chargebee","When can we expect dunning (Payment Recovery) for Chargebee?",[269],"Regarding Payment Recovery, Chargebee has a decent solution available and limited API access, but we can offer some advice on how to better set up their default campaign. We recommend you: Send emails in the sequence from different sending addresses and sender names. Email clients like Gmail will thread emails if they're from the same address + it increases your chances of catching someone's eye with different namesDay 0, day 3, day 5, day 7, day 14 for sending emails performed the best during our experimentation. For more details, we offer consultation services on how to best optimize retention campaigns based on our in-house dunning dataset.",{"id":3753,"title":3754,"titles":3755,"content":3756,"level":417},"\u002Fsupport\u002Ffaqs#can-i-apply-subscription-pauses-at-the-end-of-the-billing-cycle","Can I apply subscription pauses at the end of the billing cycle?",[269],"For companies using Stripe Unfortunately the Stripe API doesn't allow for scheduling pauses. Instead, we now have an option on this page where you can set \"Pause Wall to show at the end of the billing cycle\" on Churnkey | Flows | Settings. This setting switched to end of term will do a few things: It will set the pause end date to be at the end of the billing term of the nth month after pause. For example, if someone pauses on May 15th and their billing period renews on the 30th, a 2-month pause will pause until July 30th instead of July 15th. Note that the pause will still be applied immediately, but the end date will be moved further until the end of that future billing cycle.Within the Stripe subscription metadata, there will be a pauseWallStartDate set to subscription.current_period_end when the customer pauses. We use this internally for the Pause Wall, but if you want to use it for your own purposes it will be thereThe Pause Wall will only render after the current date is greater than pauseWallStartDate (and until the subscription is resumed at the end of the pause date).",{"id":3758,"title":3759,"titles":3760,"content":3761,"level":417},"\u002Fsupport\u002Ffaqs#how-can-i-filter-a-segmented-audience-using-the-number-of-months-since-a-custom-attribute","How can I filter a segmented audience using the number of months since a custom attribute?",[269],"If you have the customFieldDate handy, you can calculate a monthsSinceCustomDate from that, and use this as that as an audience filter using with monthsSinceCustomDate as a custom attribute of type number. When initializing Churnkey, pass monthsSinceCustomDate as a customerAttribute. function getMonthsSince(customDate) {\n  const now = new Date()\n  return (\n    now.getMonth() -\n    customDate.getMonth() +\n    12 * (now.getFullYear() - customDate.getFullYear())\n  );\n}\n\nwindow.churnkey.init('show', {\n  ...,\n  customerAttributes: {\n    monthsSinceCustomDate: getMonthsSince(customFieldDate)\n  }\n})",{"id":3763,"title":3764,"titles":3765,"content":3766,"level":417},"\u002Fsupport\u002Ffaqs#churnkey-cannot-edit-subscriptions-created-by-another-3rd-party-stripe-connection","Churnkey cannot edit subscriptions created by another 3rd party Stripe connection",[269],"In some instances, you may be using another third party other than Churnkey to create subscriptions - for instance, ProfitWell reactivations or Chargedesk. This is perfectly fine, but Stripe does have a permissions restriction that doesn't allow third parties (i.e. Churnkey in this case) from updating subscriptions created by other third parties. We put in place a 3 step fix for this (5 minutes) where we'll use a restricted API key instead of the Stripe connect OAuth permissions for those requests: Go to Stripe Dashboard | API KeysCreate a new API key (call it something like \"Churnkey\") and copy the secret key to clipboardAdd this API key to Churnkey on the Churnkey | Settings | Billing Provider page. The option to add an API key is listed on the hamburger overflow menu in the top right corner If you would prefer to use a restricted key which is recommended, make sure to include the following permissions: Core | Customers | WriteBilling | Subscriptions | WriteBilling | Invoices | WriteBilling | Coupons | WriteBilling | Credit Notes | Write You can create a restricted API key here: https:\u002F\u002Fdashboard.stripe.com\u002Fapikeys\u002Fcreate",{"id":3768,"title":3769,"titles":3770,"content":3771,"level":417},"\u002Fsupport\u002Ffaqs#how-can-i-update-the-churnkey-stripe-app","How can I update the Churnkey Stripe App",[269],"Head to https:\u002F\u002Fdashboard.stripe.com\u002Fsettings\u002Fapps\u002Fcom.churnkey.appUpdate the app version and new permissions as shown below html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sIO_5, html code.shiki .sIO_5{--shiki-light:#F76D47;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .st6vK, html code.shiki .st6vK{--shiki-light:#E2931D;--shiki-default:#116329;--shiki-dark:#7EE787}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .smKOL, html code.shiki .smKOL{--shiki-light:#E53935;--shiki-default:#116329;--shiki-dark:#7EE787}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .sC3g9, html code.shiki .sC3g9{--shiki-light:#39ADB5;--shiki-light-font-style:inherit;--shiki-default:#82071E;--shiki-default-font-style:italic;--shiki-dark:#FFA198;--shiki-dark-font-style:italic}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sv1Xi, html code.shiki .sv1Xi{--shiki-light:#91B859;--shiki-light-font-style:inherit;--shiki-default:#82071E;--shiki-default-font-style:italic;--shiki-dark:#FFA198;--shiki-dark-font-style:italic}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}",{"id":3773,"title":3774,"titles":3775,"content":3776,"level":368},"\u002Fintegrations\u002Fgeneral","Integration Basics",[],"Learn the core concepts and requirements for building a Churnkey integration.",{"id":3778,"title":3779,"titles":3780,"content":3781,"level":374},"\u002Fintegrations\u002Fgeneral#project-structure","Project Structure",[3774],"We recommend organizing your integration code in the following structure: churnkey\u002F\n├── controllers\u002F      # Data access endpoints\n│   ├── Customers.ts\n│   ├── Prices.ts\n│   ├── Subscriptions.ts\n│   ├── Coupons.ts   # optional\n│   ├── Families.ts  # optional\n|   └── Products.ts  # optional\n├── models\u002F          # Data models\n│   ├── Customer.ts\n│   ├── Price.ts\n│   ├── Subscription.ts\n│   ├── Coupon.ts    # optional\n│   ├── Family.ts    # optional\n│   └── Product.ts   # optional\n├── actions\u002F         # Subscription modifications\n│   ├── Cancel.ts    # optional\n│   ├── ApplyCoupon.ts  # optional\n│   ├── ExtendTrial.ts  # optional\n│   ├── ChangePrice.ts  # optional\n│   └── Pause.ts     # optional\n└── index.ts        # Integration entry point",{"id":3783,"title":3784,"titles":3785,"content":3786,"level":374},"\u002Fintegrations\u002Fgeneral#pagination","Pagination",[3774],"All list endpoints should support pagination to handle large datasets efficiently. Each list() method accepts pagination parameters and returns a paginated response. You can skip implementation of pagination if you are sure that the number of items will be small. However, it is strongly recommended to implement pagination just in case.",{"id":3788,"title":3789,"titles":3790,"content":3791,"level":417},"\u002Fintegrations\u002Fgeneral#request-parameters","Request Parameters",[3774,3784],"Pagination is controlled through query parameters: GET \u002Fchurnkey\u002Fcustomers?limit=10&cursor=abc123",{"id":3793,"title":3794,"titles":3795,"content":3796,"level":417},"\u002Fintegrations\u002Fgeneral#response-format","Response Format",[3774,3784],"List endpoints return paginated responses in this format:",{"id":3798,"title":3799,"titles":3800,"content":3801,"level":374},"\u002Fintegrations\u002Fgeneral#error-handling","Error Handling",[3774],"When an error occurs, return an appropriate HTTP status code along with a structured error response: {\n  \"code\": 500, \u002F\u002F HTTP status code\n  \"message\": \"A human-readable error message\"\n} Common status codes: 400 - Bad Request (invalid parameters)401 - Unauthorized (invalid or missing token)404 - Not Found (resource doesn't exist)500 - Internal Server Error",{"id":3803,"title":3804,"titles":3805,"content":3806,"level":374},"\u002Fintegrations\u002Fgeneral#test-mode","Test Mode",[3774],"Every request includes an X-Churnkey-Mode header indicating whether it's a test or live request: X-Churnkey-Mode: test   # Test mode\nX-Churnkey-Mode: live   # Live mode (default) Use this header to determine which environment (test or production) to use for your billing system operations. Test mode must be explicitly enabled in the Churnkey dashboard.",{"id":3808,"title":3809,"titles":3810,"content":3811,"level":374},"\u002Fintegrations\u002Fgeneral#authentication","Authentication",[3774],"Every request to your integration endpoints will include an Authorization header with your integration token: Authorization: Bearer your_integration_token Verify this token matches the one from your Churnkey dashboard before processing any requests.",{"id":3813,"title":3814,"titles":3815,"content":3816,"level":374},"\u002Fintegrations\u002Fgeneral#features-manifest","Features Manifest",[3774],"Your integration must expose a \u002Fchurnkey\u002Ffeatures endpoint that describes which functionality is supported. This helps Churnkey understand which Cancel Flow options to enable. The manifest includes: Supported controllers (required and optional)Available actions and their configurationsSupported features for each action (e.g., immediate vs end-of-period cancellation)",{"id":3818,"title":3819,"titles":3820,"content":3821,"level":374},"\u002Fintegrations\u002Fgeneral#implementing-context","Implementing Context",[3774],"Your custom context should extend the SDK Integrator.Context class. You can add any properties\u002Fmethods you need to the context object. import { Integrator } from '@churnkey\u002Fsdk'\nexport class Context extends Integrator.Context {\n    db: DbConnection\n    constructor(mode: Integrator.Mode, db: DbConnection) {\n        super(mode)\n        this.db = db\n    }\n}",{"id":3823,"title":3824,"titles":3825,"content":3826,"level":374},"\u002Fintegrations\u002Fgeneral#instantiating-context","Instantiating Context",[3774],"When you expose your integration to the internet, you should provide a function that returns a new instance of your context. This function will be called for every request. import { Integrator } from '.\u002Fchurnkey\u002FIntegration'\n\nconst app = express()\nIntegration.expose({\n    app: app, \u002F\u002F express app instance\n    token: process.env.CK_INTEGRATION_TOKEN, \u002F\u002F your integration token\n    ctx(req, req) {\n        return new Context(\n            req.headers['X-Churnkey-Mode'],\n            req.db\n        )\n    } \n}) html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sIO_5, html code.shiki .sIO_5{--shiki-light:#F76D47;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sK3rv, html code.shiki .sK3rv{--shiki-light:#39ADB5;--shiki-default:#116329;--shiki-dark:#7EE787}html pre.shiki code .s10kg, html code.shiki .s10kg{--shiki-light:#9C3EDA;--shiki-default:#116329;--shiki-dark:#7EE787}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sHdwX, html code.shiki .sHdwX{--shiki-light:#E2931D;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sXZA8, html code.shiki .sXZA8{--shiki-light:#E53935;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sIfGp, html code.shiki .sIfGp{--shiki-light:#39ADB5;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sjIoE, html code.shiki .sjIoE{--shiki-light:#E53935;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}",{"id":274,"title":3828,"titles":3829,"content":3830,"level":368},"Custom Provider Integration",[],"Build a custom integration to connect your billing system with Churnkey's retention tools.",{"id":3832,"title":5,"titles":3833,"content":3834,"level":374},"\u002Fintegrations#overview",[3828],"Churnkey helps reduce churn by offering targeted retention experiences when customers try to cancel. This guide walks you through building a custom integration that connects your billing system to Churnkey, enabling features like pause offers, targeted discounts, and plan changes during cancellation flows.",{"id":3836,"title":3837,"titles":3838,"content":3839,"level":374},"\u002Fintegrations#core-models","Core Models",[3828],"Models map your billing system's data to Churnkey's format. Each model represents a key billing concept that Churnkey needs to understand your customers and their subscriptions. A user who owns subscriptions.Billing cycle and price of a subscription (a.k.a. Plan)Subscription to a product or service.A discount code for a subscription.A product or service.A group of products or services.An invoice for a subscription.",{"id":3841,"title":3842,"titles":3843,"content":3844,"level":374},"\u002Fintegrations#data-access-controllers","Data Access Controllers",[3828],"Controllers provide secure, standardized access to your billing data. Each controller exposes two essential methods: retrieve(): Fetch a single record by IDlist(): Fetch multiple records with pagination support List, retrieve and find Customer models.List and retrieve Price models.List and retrieve Subscription models.List and retrieve Coupon models.List and retrieve Product models.List and retrieve Family models.List and retrieve Invoice models for advanced analytics.",{"id":3846,"title":3847,"titles":3848,"content":3849,"level":374},"\u002Fintegrations#subscription-actions","Subscription Actions",[3828],"Actions enable Churnkey to modify subscriptions based on customer choices during the cancellation flow. Each action handles a specific type of subscription change, like applying a discount or pausing service. Cancels a subscription.Pauses a subscription.Extends a trial period for a subscription.Applies a coupon to a subscription.Changes the price and\u002For product of a subscription.",{"id":3851,"title":3852,"titles":3853,"content":3854,"level":374},"\u002Fintegrations#deploy-your-integration","Deploy Your Integration",[3828],"Finally, you need to make sure that all your Controllers and Actions are accessible from an Internet, have proper authentication and provide Features manifest. Follow the publishing guide for detailed deployment instructions and troubleshooting tips.",{"id":284,"title":283,"titles":3856,"content":3857,"level":368},[],"A customer is a user who is being billed for a subscription. This model includes information about the customer, such as their name, email, address, etc.",{"id":3859,"title":3860,"titles":3861,"content":422,"level":374},"\u002Fintegrations\u002Fmodels\u002Fcustomer#properties","Properties",[283],{"id":3863,"title":3864,"titles":3865,"content":3866,"level":374},"\u002Fintegrations\u002Fmodels\u002Fcustomer#code-example","Code Example",[283],"import { Integrator } from '@churnkey\u002Fsdk'\nexport class Customer extends Integrator.Customer {\n    constructor(customer: YourCustomer) {\n        super({\n            id: customer.id,\n            ... \u002F\u002F map other properties\n        })\n    }\n}\nimport { Coupon } from '.\u002FCoupon'\n\ninterface Customer {\n    id: string\n    name?: string\n    lastName?: string\n    email?: string\n    phone?: string\n    addresses?: Address[]\n    discounts?: Discount[]\n    currency?: string\n    metadata?: Record\u003Cstring, string>\n}\n\ninterface Address {\n    country?: string\n    state?: string\n    city?: string\n    postalCode?: string\n    line1?: string\n    line2?: string\n}\n\ninterface Discount {\n    coupon: Coupon\n    start?: Date\n    end?: Date\n}\n\nexport function Customer(customer: YourCustomer): Customer {\n    return {\n        id: customer.id,\n        ... \u002F\u002F map other properties\n    }\n}\npackage models\n\nimport (\n    \"time\"\n)\n\ntype Customer struct {\n    ID        string            `json:\"id\"`\n    Name      *string           `json:\"name\"`\n    LastName  *string           `json:\"lastName\"`\n    Email     *string           `json:\"email\"`\n    Phone     *string           `json:\"phone\"`\n    Addresses []Address         `json:\"addresses\"`\n    Discounts []Discount        `json:\"discounts\"`\n    Currency  *string           `json:\"currency\"`\n    Metadata  map[string]string `json:\"metadata\"`\n}\n\ntype Address struct {\n    Country    *string `json:\"country\"`\n    State      *string `json:\"state\"`\n    City       *string `json:\"city\"`\n    PostalCode *string `json:\"postalCode\"`\n    Line1      *string `json:\"line1\"`\n    Line2      *string `json:\"line2\"`\n}\n\ntype Discount struct {\n    Coupon Coupon     `json:\"coupon\"`\n    Start  *time.Time `json:\"start\"`\n    End    *time.Time `json:\"end\"`\n}\n\nfunc Customer(customer YourCustomer) Customer {\n    return Customer{\n        ID:        customer.ID,\n        \u002F\u002F map other properties\n    }\n} html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sHdwX, html code.shiki .sHdwX{--shiki-light:#E2931D;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sXZA8, html code.shiki .sXZA8{--shiki-light:#E53935;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}",{"id":288,"title":287,"titles":3868,"content":3869,"level":368},[],"A price (also known as plan) defines the cost, currency and billing cycle of product or service.",{"id":3871,"title":3872,"titles":3873,"content":3874,"level":374},"\u002Fintegrations\u002Fmodels\u002Fprice#type","Type",[287],"Depending on your app's architecture, prices can belong to a Product or be standalone entities.\nIn both cases, you can start by implementing a standalone Price model, and later refactor it to add products support. If you implement a product-based price, you will need to create a Products controller and set the productId property on the price.",{"id":3876,"title":3860,"titles":3877,"content":422,"level":374},"\u002Fintegrations\u002Fmodels\u002Fprice#properties",[287],{"id":3879,"title":3864,"titles":3880,"content":3881,"level":374},"\u002Fintegrations\u002Fmodels\u002Fprice#code-example",[287],"import { Integrator } from '@churnkey\u002Fsdk'\n\u002F\u002F export class Price extends Integrator.Price.Product {\nexport class Price extends Integrator.Price.Standalone {\n    constructor(price: YourPrice) {\n        super(\n            {\n                id: price.id,\n                ... \u002F\u002F map other properties\n            }\n        )\n    }\n}\ninterface Price {\n    id: string\n    type: Type\n    duration: Duration\n    amount: Amount\n    productId?: string\n    name?: string\n    description?: string\n}\n\nexport function Price(price: YourPrice): Price {\n    return {\n        id: price.id,\n        ... \u002F\u002F map other properties\n    }\n}\n\nenum Type {\n    Standalone = \"standalone\",\n    Product = \"product\"\n}\n\ninterface Duration {\n    amount: number\n    unit: Unit\n}\n\nenum Unit {\n    Month = \"month\",\n    Year = \"year\"\n}\n\ntype Amount = FixedAmount | TieredAmount\n\ninterface FixedAmount {\n    model: Model.Fixed\n    currency: string\n    unit?: number\n    flat?: number\n}\n\ninterface TieredAmount {\n    model: Model.Tiered\n    currency: string\n    mode: Mode\n    tiers: Tier[]\n}\n\n\nenum Model {\n    Fixed = \"fixed\",\n    Tiered = \"tiered\"\n}\n\nenum Mode {\n    Graduated = \"graduated\",\n    Total = \"total\"\n}\n\ninterface Tier {\n    unit?: number\n    flat?: number\n    upTo?: number\n}\npackage models\n\ntype Price struct {\n    ID        string            `json:\"id\"`\n    Type      Type              `json:\"type\"`\n    Duration  Duration          `json:\"duration\"`\n    Amount    Amount            `json:\"amount\"`\n    ProductID *string           `json:\"productId\"`\n    Name      *string           `json:\"name\"`\n    Description *string         `json:\"description\"`\n}\n\ntype Type string\nconst (\n    Standalone Type = \"standalone\"\n    Product Type = \"product\"\n)\n\ntype Duration struct {\n    Amount int `json:\"amount\"`\n    Unit   Unit `json:\"unit\"`\n}\n\ntype Unit string\nconst (\n    Month Unit = \"month\"\n    Year Unit = \"year\"\n)\n\ntype Amount interface {\n    PricingModel() Model\n}\n\ntype FixedAmount struct {\n    Model    Model  `json:\"model\"`\n    Currency string `json:\"currency\"`\n    Unit     *int   `json:\"unit\"`\n    Flat     *int   `json:\"flat\"`\n}\n\nfunc (FixedAmount) PricingModel() Model {\n    return Fixed\n}\n\ntype TieredAmount struct {\n    Model Model `json:\"model\"`\n    Currency string `json:\"currency\"`\n    Mode Mode `json:\"mode\"`\n    Tiers []Tier `json:\"tiers\"`\n}\n\nfunc (TieredAmount) PricingModel() Model {\n    return Tiered\n}\n\ntype Model string\nconst (\n    Fixed Model = \"fixed\"\n    Tiered Model = \"tiered\"\n)\n\ntype Mode string\nconst (\n    Graduated Mode = \"graduated\"\n    Total Mode = \"total\"\n)\n\ntype Tier struct {\n    Unit  *int `json:\"unit\"`\n    Flat  *int `json:\"flat\"`\n    UpTo  *int `json:\"upTo\"`\n}\n\nfunc Price(price YourPrice) Price {\n    return Price{\n        ID: price.ID,\n        ... \u002F\u002F map other properties\n    }\n} html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sHdwX, html code.shiki .sHdwX{--shiki-light:#E2931D;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sXZA8, html code.shiki .sXZA8{--shiki-light:#E53935;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}",{"id":292,"title":291,"titles":3883,"content":3884,"level":368},[],"A subscription is a recurring payment for a product or service. This model includes information about the subscription, including its status, billing cycle, items, and discounts.",{"id":3886,"title":3860,"titles":3887,"content":422,"level":374},"\u002Fintegrations\u002Fmodels\u002Fsubscription#properties",[291],{"id":3889,"title":3864,"titles":3890,"content":3891,"level":374},"\u002Fintegrations\u002Fmodels\u002Fsubscription#code-example",[291],"import { Integrator } from '@churnkey\u002Fsdk'\nimport { Price } from '.\u002FPrice' \u002F\u002F you should implement Price model\nimport { Coupon } from '.\u002FCoupon' \u002F\u002F optional, you should implement Coupon model\n\nexport class Subscription extends Integrator.Subscription {\n    constructor(subscription: YourSubscription) {\n        super({\n            id: subscription.id,\n            items: subscription.items.map(i => {\n                return {\n                    id: i.id,\n                    price: new Price(i.price),\n                    quantity: i.quantity\n                }\n            }),\n            discounts: subscription.discounts?.map(d => { \u002F\u002F optional\n                return {\n                    coupon: new Coupon(d.coupon),\n                    start: d.start,\n                    end: d.end\n                }\n            }),\n            ... \u002F\u002F map other properties\n        })\n    }\n}\nimport { Price } from '.\u002FPrice' \u002F\u002F you should implement Price model\nimport { Coupon } from '.\u002FCoupon' \u002F\u002F optional, you should implement Coupon model\n\ninterface Subscription {\n    id: string\n    status: Status\n    duration: Duration\n    items: Item[]\n    discounts?: Discount[]\n    metadata?: Record\u003Cstring, string>\n}\n\nexport function Subscription(subscription: YourSubscription): Subscription {\n    return {\n        id: subscription.id,\n        ... \u002F\u002F map other properties\n    }\n}\n\nexport type Status = Status.Active | Status.Canceled | Status.Paused | Status.Unpaid | Status.Trial\n\nexport namespace Status {\n    interface Base {\n        name: Name\n        currentPeriod?: Period\n        trial?: Period\n    }\n\n    export enum Name {\n        Active = \"active\",\n        Canceled = \"canceled\",\n        Paused = \"paused\",\n        Unpaid = \"unpaid\",\n        Trial = \"trial\",\n    }\n\n    export interface Active extends Base {\n        name: Name.Active\n        currentPeriod: Period\n    }\n\n    export interface Canceled extends Base {\n        name: Name.Canceled\n        canceledAt: Date\n    }\n\n    export interface Paused extends Base {\n        name: Name.Paused\n        start: Date\n        end: Date\n    }\n\n    export interface Unpaid extends Base {\n        name: Name.Unpaid\n    }\n\n    export interface Trial extends Base {\n        name: Name.Trial\n        trial: Period\n    }\n}\n\nexport interface Duration {\n    amount: number\n    unit: Duration.Unit\n}\n\nexport namespace Duration {\n    export enum Unit {\n        Month = \"month\",\n        Year = \"year\"\n    }\n}\n\nexport interface Period {\n    start: Date\n    end: Date\n}\n\nexport interface Item {\n    id?: string\n    price: Price\n    quantity: number\n}\n\nexport interface Discount {\n    coupon: Coupon\n    start: Date\n    end?: Date\n} html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sHdwX, html code.shiki .sHdwX{--shiki-light:#E2931D;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sXZA8, html code.shiki .sXZA8{--shiki-light:#E53935;--shiki-default:#953800;--shiki-dark:#FFA657}",{"id":296,"title":295,"titles":3893,"content":3894,"level":368},[],"Coupon represents a code that can be redeemed for a discount.",{"id":3896,"title":3860,"titles":3897,"content":422,"level":374},"\u002Fintegrations\u002Fmodels\u002Fcoupon#properties",[295],{"id":3899,"title":3864,"titles":3900,"content":3901,"level":374},"\u002Fintegrations\u002Fmodels\u002Fcoupon#code-example",[295],"import { Integrator } from '@churnkey\u002Fsdk'\n\nexport class Coupon extends Integrator.Coupon {\n    constructor(coupon: YourCoupon) {\n        super({\n            id: coupon.id,\n            ... \u002F\u002F map other properties\n        })\n    }\n}\ninterface Coupon {\n    id: string\n    duration: Duration\n    value: Value\n    name?: string\n    code?: string\n    redemptions?: Redemptions\n    expiresAt?: Date\n}\n\nexport function Coupon(coupon: YourCoupon): Coupon {\n    return {\n        id: coupon.id,\n        ... \u002F\u002F map other properties\n    }\n}\n\nexport type Duration = Duration.Once | Duration.Recurring | Duration.Forever | Duration.CycleAmount\n\nnamespace Duration {\n    export enum Type {\n        Once = \"once\",\n        Recurring = \"recurring\"\n        Forever = \"forever\"\n        CycleAmount = \"cycle-amount\"\n    }\n\n    interface Base {\n        type: Type\n    }\n\n    export interface Once extends Base {\n        type: Type.Once\n    }\n\n    export interface Recurring extends Base {\n        type: Type.Recurring\n        amount: number\n        unit: Duration.Unit\n    }\n\n    export interface Forever extends Base {\n        type: Type.Forever\n    }\n\n    export interface CycleAmount extends Base {\n        type: Type.CycleAmount\n        cycles: number\n    }\n\n    export enum Unit {\n        Month = \"month\",\n        Year = \"year\"\n    }\n}\n\n\nexport type Value = Value.Fixed | Value.Percentage\n\nexport namespace Value {\n    export enum Type {\n        Fixed = \"fixed\",\n        Percentage = \"percentage\"\n    }\n\n    interface Base {\n        type: Type\n    }\n\n    export interface Fixed extends Base {\n        type: Type.Fixed\n        currency: string\n        unit?: number\n        flat?: number\n    }\n\n    export interface Percentage extends Base {\n        type: Type.Percentage\n        percent: number\n        currency?: string\n    }\n}\n\nexport interface Redemptions {\n    max?: number\n    current?: number\n} html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sHdwX, html code.shiki .sHdwX{--shiki-light:#E2931D;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sXZA8, html code.shiki .sXZA8{--shiki-light:#E53935;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}",{"id":300,"title":299,"titles":3903,"content":3904,"level":368},[],"Product represents a good or service that can be sold. If you are implementing a Product model, make sure you pass productId to the Price model. This way, you can associate a price with a product.",{"id":3906,"title":3872,"titles":3907,"content":3908,"level":374},"\u002Fintegrations\u002Fmodels\u002Fproduct#type",[299],"Depending on your app's architecture, products can belong to a Family or be standalone entities.\nIn both cases, you can start by implementing a standalone Product model, and later refactor it to add families support. If you implement a family-based product, you will need to create a Families controller and set the familyId property on the product.",{"id":3910,"title":3860,"titles":3911,"content":422,"level":374},"\u002Fintegrations\u002Fmodels\u002Fproduct#properties",[299],{"id":3913,"title":3864,"titles":3914,"content":3915,"level":374},"\u002Fintegrations\u002Fmodels\u002Fproduct#code-example",[299],"import { Integrator } from '@churnkey\u002Fsdk'\n\n\u002F\u002F export class Product extends Integrator.Product.Family {\nexport class Product extends Integrator.Product.Standalone {\n    constructor(product: YourProduct) {\n        super({\n            id: product.id,\n            ... \u002F\u002F map other properties\n        })\n    }\n}\ninterface Product {\n    id: string\n    type: Type\n    name: string\n    description?: string\n    unitLabel?: string\n    familyId?: string\n}\n\nexport function Product(product: YourProduct): Product {\n    return {\n        id: product.id,\n        ... \u002F\u002F map other properties\n    }\n}\n\nenum Type {\n    Standalone = \"standalone\",\n    Family = \"family\"\n} html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sHdwX, html code.shiki .sHdwX{--shiki-light:#E2931D;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sXZA8, html code.shiki .sXZA8{--shiki-light:#E53935;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}",{"id":304,"title":303,"titles":3917,"content":3918,"level":368},[],"A group of products or services. If you are implementing a Family model, make sure you pass familyId to the Product model. This way, you can associate a family with a product.",{"id":3920,"title":3860,"titles":3921,"content":422,"level":374},"\u002Fintegrations\u002Fmodels\u002Ffamily#properties",[303],{"id":3923,"title":3864,"titles":3924,"content":3925,"level":374},"\u002Fintegrations\u002Fmodels\u002Ffamily#code-example",[303],"import { Integrator } from '@churnkey\u002Fts-sdk'\n\nexport class Family extends Integrator.Family {\n    constructor(family: YourFamily) {\n        super(\n            {\n                id: family.id,\n                ... \u002F\u002F map other properties\n            }\n        )\n    }\n}\ninterface Family {\n    id: string\n    name: string\n    description?: string\n}\n\nexport function Family(family: YourFamily) {\n    return {\n        id: family.id,\n        ... \u002F\u002F map other properties\n    }\n}\npackage models\n\ntype Family struct {\n    ID          string  `json:\"id\"`\n    Name        string  `json:\"name\"`\n    Description *string `json:\"description\"`\n}\n\nfunc Family(family YourFamily) Family {\n    return Family{\n        ID:          family.ID,\n        ... \u002F\u002F map other properties\n    }\n} html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sHdwX, html code.shiki .sHdwX{--shiki-light:#E2931D;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sXZA8, html code.shiki .sXZA8{--shiki-light:#E53935;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}",{"id":313,"title":312,"titles":3927,"content":3928,"level":368},[],"List, retrieve and find `Customer` models. To implement the Customers controller, you need to implement at least 2 API endpoints, we will use these endpoints to fetch customers from your system.",{"id":3930,"title":3931,"titles":3932,"content":3933,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fcustomers#prerequisites","Prerequisites",[312],"A user who owns subscriptions.If you have customer-level discounts, you may need to implement a Coupons controller first.",{"id":3935,"title":3936,"titles":3937,"content":3938,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fcustomers#sdk","SDK",[312],"If you are using the SDK, you can implement the Customers controller by following the code example below. You don't need to get into the details of the API endpoints, the SDK will take care of that for you. import { Integrator } from '@churnkey\u002Fsdk'\nimport { Context } from '..\u002FContext'\nimport { Customer } from '..\u002Fmodels\u002FCustomer'\n\nexport const Customers = Integrator.Customers.config({\n    ctx: Context,\n    async retrieve(ctx, options) {\n        const yourCustomer = await ctx.db.findCustomerById(options.id)\n        return new Customer(yourCustomer) \n    },\n    async list(ctx, options) {\n        const yourCustomers = await ctx.db.findCustomers({\n            limit: options.limit,\n            offset: options.cursor \u002F\u002F the value you pass as `next` below\n        })\n        return {\n            data: yourCustomers.map(c => new Customer(c)),\n            \u002F\u002F pass the next cursor if there are more items\n            next: yourCustomers.length === options.limit ? offset + limit : undefined\n        }\n    },\n    async findByEmail(ctx, email) { \u002F\u002F optional\n        const yourCustomer = await ctx.db.findCustomerByEmail(email)\n        return new Customer(yourCustomer)\n    },\n    async findByPhone(ctx, phone) { \u002F\u002F optional\n        const yourCustomer = await ctx.db.findCustomerByPhone(phone)\n        return new Customer(yourCustomer)\n    }\n})",{"id":3940,"title":3941,"titles":3942,"content":422,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fcustomers#endpoints","Endpoints",[312],{"id":3944,"title":3945,"titles":3946,"content":3947,"level":417},"\u002Fintegrations\u002Fcontrollers\u002Fcustomers#retrieve","Retrieve",[312,3941],"GET \u002Fchurnkey\u002Fcustomers\u002F:id This endpoint fetches Customer by its id. Usually, implementation will include finding a customer in your database and mapping it to the Customer model. Must return Customer model. See Customer model documentation.See Error Responses. import { Customer } from '..\u002Fmodels\u002FCustomer'\n\napp.get('\u002Fchurnkey\u002Fcustomers\u002F:id', async (req, res) => {\n    const customer = await db.findCustomerById(req.params.id)\n    if (!customer) {\n        return res.status(404).send({ code: 404, message: 'Customer not found' })\n    }\n    res.send(new Customer(customer))\n})",{"id":3949,"title":3950,"titles":3951,"content":3952,"level":417},"\u002Fintegrations\u002Fcontrollers\u002Fcustomers#list","List",[312,3941],"GET \u002Fchurnkey\u002Fcustomers This endpoint fetches a list of customers from your database. You should find customers in your database (with pagination), map them to the Customer model and return a paginated list. Learn more about pagination. import { Customer } from '..\u002Fmodels\u002FCustomer'\n\napp.get('\u002Fchurnkey\u002Fcustomers', async (req, res) => {\n    const limit = Number.parseInt(req.query.limit)\n    const offset = Number.parseInt(req.query.cursor) \n    const customers = await db.findCustomers({ limit, offset })\n    res.send({\n        data: customers.map(c => new Customer(c)),\n        next: customers.length === limit ? offset + limit : undefined\n    })\n})",{"id":3954,"title":3955,"titles":3956,"content":3957,"level":417},"\u002Fintegrations\u002Fcontrollers\u002Fcustomers#find-by-email","Find by Email",[312,3941],"GET \u002Fchurnkey\u002Fcustomers\u002Femail\u002F:email This endpoint fetches Customer by its email. Usually, implementation will include finding a customer in your database and mapping it to the Customer model. Make sure that each customer has a unique email address if you are going to implement this endpoint. Must return Customer model. See Customer model documentation.See Error Responses. import { Customer } from '..\u002Fmodels\u002FCustomer'\n\napp.get('\u002Fchurnkey\u002Fcustomers\u002Femail\u002F:email', async (req, res) => {\n    const customer = await db.findCustomerByEmail(req.params.email)\n    if (!customer) {\n        return res.status(404).send({ code: 404, message: 'Customer not found' })\n    }\n    res.send(new Customer(customer))\n})",{"id":3959,"title":3960,"titles":3961,"content":3962,"level":417},"\u002Fintegrations\u002Fcontrollers\u002Fcustomers#find-by-phone","Find by Phone",[312,3941],"GET \u002Fchurnkey\u002Fcustomers\u002Fphone\u002F:phone This endpoint fetches Customer by its phone. Usually, implementation will include finding a customer in your database and mapping it to the Customer model. Make sure that each customer has a unique phone number if you are going to implement this endpoint. Must return Customer model. See Customer model documentation.See Error Responses. import { Customer } from '..\u002Fmodels\u002FCustomer'\n\napp.get('\u002Fchurnkey\u002Fcustomers\u002Femail\u002F:email', async (req, res) => {\n    const customer = await db.findCustomerByEmail(req.params.email)\n    if (!customer) {\n        return res.status(404).send({ code: 404, message: 'Customer not found' })\n    }\n    res.send(new Customer(customer))\n})",{"id":3964,"title":201,"titles":3965,"content":3966,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fcustomers#webhooks",[312],"Coming soon. html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sjIoE, html code.shiki .sjIoE{--shiki-light:#E53935;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html pre.shiki code .sIfGp, html code.shiki .sIfGp{--shiki-light:#39ADB5;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sIO_5, html code.shiki .sIO_5{--shiki-light:#F76D47;--shiki-default:#0550AE;--shiki-dark:#79C0FF}",{"id":317,"title":316,"titles":3968,"content":3969,"level":368},[],"List and retrieve `Price` models. To implement the Prices controller, you need to implement 2 API endpoints, we will use these endpoints to fetch prices from your system.",{"id":3971,"title":3931,"titles":3972,"content":3973,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fprices#prerequisites",[316],"A price for a product or service.\n If you chose to implement prices with products, you must implement a Products controller first.",{"id":3975,"title":3936,"titles":3976,"content":3977,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fprices#sdk",[316],"If you are using the SDK, you can implement the Prices controller by following the code example below. You don't need to get into the details of the API endpoints, the SDK will take care of that for you. import { Integrator } from '@churnkey\u002Fsdk'\nimport { Context } from '..\u002FContext'\nimport { Price } from '..\u002Fmodels\u002FPrice'\n\u002F\u002F import { Products } from '.\u002FProducts'\n\nexport const Prices = Integrator.Prices.config({\n    ctx: Context,\n    type: Integrator.Price.Type.Standalone,\n    \u002F\u002F type: Integrator.Price.Type.Product,\n    \u002F\u002F Products: Products, \n    async retrieve(ctx, options) {\n        const yourPrice = await ctx.db.findPrice(options.id)\n        return new Price(yourPrice)\n    },\n    async list(ctx, options) {\n        const yourPrices = await ctx.db.listPrices({\n            limit: options.limit,\n            offset: options.cursor \u002F\u002F the value you pass as `next` below\n        })\n        return {\n            data: yourPrices.map(price => new Price(price)),\n            \u002F\u002F pass the next cursor if there are more items\n            next: yourPrices.length === options.limit ? offset + limit : undefined\n        }\n    }\n})",{"id":3979,"title":3941,"titles":3980,"content":422,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fprices#endpoints",[316],{"id":3982,"title":3945,"titles":3983,"content":3984,"level":417},"\u002Fintegrations\u002Fcontrollers\u002Fprices#retrieve",[316,3941],"GET \u002Fchurnkey\u002Fprices\u002F:id This endpoint fetches Price by its id. Usually, implementation will include finding a price in your database and mapping it to the Price model. Must return Price model. See Price model documentation.See Error Responses. import { Price } from '..\u002Fmodels\u002FPrice'\n\napp.get('\u002Fchurnkey\u002Fprices\u002F:id', async (req, res) => {\n    const price = await db.findPriceById(req.params.id)\n    if (!price) {\n        return res.status(404).send({ code: 404, message: 'Price not found' })\n    }\n    res.send(new Price(price))\n})",{"id":3986,"title":3950,"titles":3987,"content":3988,"level":417},"\u002Fintegrations\u002Fcontrollers\u002Fprices#list",[316,3941],"GET \u002Fchurnkey\u002Fprices This endpoint fetches a list of prices from your database. You should find prices in your database (with pagination), map them to the Price model and return a paginated list. Learn more about pagination. import { Price } from '..\u002Fmodels\u002FPrice'\n\napp.get('\u002Fchurnkey\u002Fprices', async (req, res) => {\n    const limit = Number.parseInt(req.query.limit)\n    const offset = Number.parseInt(req.query.cursor) \n    const prices = await db.findPrices({ limit, offset })\n    res.send({\n        data: prices.map(c => new Price(c)),\n        next: prices.length === limit ? offset + limit : undefined\n    })\n}) html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sjIoE, html code.shiki .sjIoE{--shiki-light:#E53935;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .sIfGp, html code.shiki .sIfGp{--shiki-light:#39ADB5;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sIO_5, html code.shiki .sIO_5{--shiki-light:#F76D47;--shiki-default:#0550AE;--shiki-dark:#79C0FF}",{"id":321,"title":320,"titles":3990,"content":3991,"level":368},[],"List and retrieve `Subscription` models. To implement the Subscriptions controller, you need to implement 2 API endpoints, we will use these endpoints to fetch subscriptions from your system.",{"id":3993,"title":3931,"titles":3994,"content":3995,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fsubscriptions#prerequisites",[320],"Subscription to a product or service. You must implement a Prices controller first. If you have subscription-level discounts, you may need to implement a Coupons controller first. ::",{"id":3997,"title":3936,"titles":3998,"content":3999,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fsubscriptions#sdk",[320],"If you are using the SDK, you can implement the Subscriptions controller by following the code example below. You don't need to get into the details of the API endpoints, the SDK will take care of that for you. import { Integrator } from '@churnkey\u002Fsdk'\nimport { Context } from '..\u002FContext'\nimport { Subscription } from '..\u002Fmodels\u002FSubscription'\nimport { Prices } from '.\u002FPrices'\n\nexport const Subscriptions = Integrator.Subscriptions.config({\n    ctx: Context,\n    Prices: Prices,\n    async retrieve(ctx, options) {\n        const yourSubscription = await ctx.db.findSubscription(options.customerId, options.id)\n        return new Subscription(yourSubscription)\n    },\n    async list(ctx, options) {\n        const yourSubscriptions = await ctx.db.listSubscriptions({\n            customerId: options.customerId,\n            limit: options.limit,\n            offset: options.cursor \u002F\u002F the value you passed as `next` below\n        })\n        return {\n            data: yourSubscriptions.map(subscription => new Subscription(subscription)),\n            \u002F\u002F pass the next cursor if there are more items\n            next: yourSubscriptions.length === options.limit ? offset + limit : undefined\n        }\n    }\n})",{"id":4001,"title":3941,"titles":4002,"content":422,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fsubscriptions#endpoints",[320],{"id":4004,"title":3945,"titles":4005,"content":4006,"level":417},"\u002Fintegrations\u002Fcontrollers\u002Fsubscriptions#retrieve",[320,3941],"GET \u002Fchurnkey\u002Fcustomers\u002F:customerId\u002Fsubscriptions\u002F:id This endpoint fetches Subscription by its id. Usually, implementation will include finding a subscription in your database and mapping it to the Subscription model. Must return Subscription model. See Subscription model documentation.See Error Responses. import { Subscription } from '..\u002Fmodels\u002FSubscription'\n\napp.get('\u002Fchurnkey\u002Fcustomers\u002F:customerId\u002Fsubscriptions\u002F:id', async (req, res) => {\n    const subscription = await db.findSubscriptionById(req.params.id)\n    if (!subscription) {\n        return res.status(404).send({ code: 404, message: 'Subscription not found' })\n    }\n    res.send(new Subscription(subscription))\n})",{"id":4008,"title":3950,"titles":4009,"content":4010,"level":417},"\u002Fintegrations\u002Fcontrollers\u002Fsubscriptions#list",[320,3941],"GET \u002Fchurnkey\u002Fcustomers\u002F:customerId\u002Fsubscriptions This endpoint fetches a list of subscriptions from your database. You should find subscriptions in your database (with pagination), map them to the Subscription model and return a paginated list. Learn more about pagination. import { Subscription } from '..\u002Fmodels\u002FSubscription'\n\napp.get('\u002Fchurnkey\u002Fcustomers\u002F:customerId\u002Fsubscriptions', async (req, res) => {\n    const limit = Number.parseInt(req.query.limit)\n    const offset = Number.parseInt(req.query.cursor) \n    const subscriptions = await db.findSubscriptions({ \n        limit, offset, \n        customerId: req.params.customerId \n    })\n    res.send({\n        data: subscriptions.map(c => new Subscription(c)),\n        next: subscriptions.length === limit ? offset + limit : undefined\n    })\n}) html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sjIoE, html code.shiki .sjIoE{--shiki-light:#E53935;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html pre.shiki code .sIfGp, html code.shiki .sIfGp{--shiki-light:#39ADB5;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sIO_5, html code.shiki .sIO_5{--shiki-light:#F76D47;--shiki-default:#0550AE;--shiki-dark:#79C0FF}",{"id":325,"title":324,"titles":4012,"content":4013,"level":368},[],"List and retrieve `Coupon` models. To implement the Coupons controller, you need to implement 2 API endpoints, we will use these endpoints to fetch coupons from your system.",{"id":4015,"title":3931,"titles":4016,"content":4017,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fcoupons#prerequisites",[324],"A code that can be redeemed for a discount.",{"id":4019,"title":3936,"titles":4020,"content":4021,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fcoupons#sdk",[324],"If you are using the SDK, you can implement the Coupons controller by following the code example below. You don't need to get into the details of the API endpoints, the SDK will take care of that for you. import { Integrator } from '@churnkey\u002Fsdk'\nimport { Context } from '..\u002FContext'\nimport { Coupon } from '..\u002Fmodels\u002FCoupon'\n\nexport const Coupons = Integrator.Coupons.config({\n    ctx: Context,\n    async retrieve(ctx, options) {\n        const yourCoupon = await ctx.db.findCoupon(options.id)\n        return new Coupon(yourCoupon)\n    },\n    async list(ctx, options) {\n        const yourCoupons = await ctx.db.listCoupons({\n            limit: options.limit,\n            offset: options.cursor \u002F\u002F the value you pass as `next` below\n        })\n        return {\n            data: yourCoupons.map(coupon => new Coupon(coupon)),\n            \u002F\u002F pass the next cursor if there are more items\n            next: yourCoupons.length === options.limit ? offset + limit : undefined\n        }\n    }\n})",{"id":4023,"title":3941,"titles":4024,"content":422,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fcoupons#endpoints",[324],{"id":4026,"title":3945,"titles":4027,"content":4028,"level":417},"\u002Fintegrations\u002Fcontrollers\u002Fcoupons#retrieve",[324,3941],"GET \u002Fchurnkey\u002Fcoupons\u002F:id This endpoint fetches Coupon by its id. Usually, implementation will include finding a coupon in your database and mapping it to the Coupon model. Must return Coupon model. See Coupon model documentation.See Error Responses. import { Coupon } from '..\u002Fmodels\u002FCoupon'\n\napp.get('\u002Fchurnkey\u002Fcoupons\u002F:id', async (req, res) => {\n    const coupon = await db.findCouponById(req.params.id)\n    if (!coupon) {\n        return res.status(404).send({ code: 404, message: 'Coupon not found' })\n    }\n    res.send(new Coupon(coupon))\n})",{"id":4030,"title":3950,"titles":4031,"content":4032,"level":417},"\u002Fintegrations\u002Fcontrollers\u002Fcoupons#list",[324,3941],"GET \u002Fchurnkey\u002Fcoupons This endpoint fetches a list of coupons from your database. You should find coupons in your database (with pagination), map them to the Coupon model and return a paginated list. Learn more about pagination. import { Coupon } from '..\u002Fmodels\u002FCoupon'\n\napp.get('\u002Fchurnkey\u002Fcoupons', async (req, res) => {\n    const limit = Number.parseInt(req.query.limit)\n    const offset = Number.parseInt(req.query.cursor) \n    const coupons = await db.findCoupons({ limit, offset })\n    res.send({\n        data: coupons.map(c => new Coupon(c)),\n        next: coupons.length === limit ? offset + limit : undefined\n    })\n})",{"id":4034,"title":201,"titles":4035,"content":3966,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fcoupons#webhooks",[324],{"id":329,"title":328,"titles":4037,"content":4038,"level":368},[],"List and retrieve `Product` models. To implement the Products controller, you need to implement 2 API endpoints, we will use these endpoints to fetch products from your system.",{"id":4040,"title":3931,"titles":4041,"content":4042,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fproducts#prerequisites",[328],"A code that can be redeemed for a discount.If you decided to implement products with families, you must implement a Families controller first.",{"id":4044,"title":3936,"titles":4045,"content":4046,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fproducts#sdk",[328],"If you are using the SDK, you can implement the Products controller by following the code example below. You don't need to get into the details of the API endpoints, the SDK will take care of that for you. import { Integrator } from '@churnkey\u002Fsdk'\nimport { Context } from '..\u002FContext'\nimport { Product } from '..\u002Fmodels\u002FProduct'\n\u002F\u002F import { Families } from '.\u002FFamilies'\n\nexport const Products = Integrator.Products.config({\n    ctx: Context,\n    type: Integrator.Product.Type.Standalone,\n    \u002F\u002F type: Integrator.Product.Type.Family,\n    \u002F\u002F Families: Families,\n    async retrieve(ctx, options) {\n        const yourProduct = await ctx.db.findProduct(options.id)\n        return new Product(yourProduct)\n    },\n    async list(ctx, options) {\n        const yourProducts = await ctx.db.listProducts({\n            limit: options.limit,\n            offset: options.cursor \u002F\u002F the value you pass as `next` below\n        })\n        return {\n            data: yourProducts.map(product => new Product(product)),\n            \u002F\u002F pass the next cursor if there are more items\n            next: yourProducts.length === options.limit ? offset + limit : undefined\n        }\n    }\n})",{"id":4048,"title":3941,"titles":4049,"content":422,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fproducts#endpoints",[328],{"id":4051,"title":3945,"titles":4052,"content":4053,"level":417},"\u002Fintegrations\u002Fcontrollers\u002Fproducts#retrieve",[328,3941],"GET \u002Fchurnkey\u002Fproducts\u002F:id This endpoint fetches Product by its id. Usually, implementation will include finding a product in your database and mapping it to the Product model. Must return Product model. See Product model documentation.See Error Responses. import { Product } from '..\u002Fmodels\u002FProduct'\n\napp.get('\u002Fchurnkey\u002Fproducts\u002F:id', async (req, res) => {\n    const product = await db.findProductById(req.params.id)\n    if (!product) {\n        return res.status(404).send({ code: 404, message: 'Product not found' })\n    }\n    res.send(new Product(product))\n})",{"id":4055,"title":3950,"titles":4056,"content":4057,"level":417},"\u002Fintegrations\u002Fcontrollers\u002Fproducts#list",[328,3941],"GET \u002Fchurnkey\u002Fproducts This endpoint fetches a list of products from your database. You should find products in your database (with pagination), map them to the Product model and return a paginated list. Learn more about pagination. import { Product } from '..\u002Fmodels\u002FProduct'\n\napp.get('\u002Fchurnkey\u002Fproducts', async (req, res) => {\n    const limit = Number.parseInt(req.query.limit)\n    const offset = Number.parseInt(req.query.cursor) \n    const products = await db.findProducts({ limit, offset })\n    res.send({\n        data: products.map(c => new Product(c)),\n        next: products.length === limit ? offset + limit : undefined\n    })\n})",{"id":4059,"title":201,"titles":4060,"content":4061,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Fproducts#webhooks",[328],"Coming soon. html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sjIoE, html code.shiki .sjIoE{--shiki-light:#E53935;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .sIfGp, html code.shiki .sIfGp{--shiki-light:#39ADB5;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sIO_5, html code.shiki .sIO_5{--shiki-light:#F76D47;--shiki-default:#0550AE;--shiki-dark:#79C0FF}",{"id":333,"title":332,"titles":4063,"content":4064,"level":368},[],"List and retrieve `Family` models. To implement the Families controller, you need to implement 2 API endpoints, we will use these endpoints to fetch families from your system.",{"id":4066,"title":3931,"titles":4067,"content":4068,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Ffamilies#prerequisites",[332],"A group of products or services.",{"id":4070,"title":3936,"titles":4071,"content":4072,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Ffamilies#sdk",[332],"import { Integrator } from '@churnkey\u002Fsdk'\nimport { Context } from '..\u002FContext'\nimport { Family } from '..\u002Fmodels\u002FFamily'\n\nexport const Families = Integrator.Families.config({\n    ctx: Context,\n    async retrieve(ctx, options) {\n        const yourFamily = await ctx.db.findFamily(options.id)\n        return new Family(yourFamily)\n    },\n    async list(ctx, options) {\n        const yourFamilies = await ctx.db.listFamilies({\n            limit: options.limit,\n            offset: options.cursor \u002F\u002F the value you pass as `next` below\n        })\n        return {\n            data: yourFamilies.map(family => new Family(family)),\n            \u002F\u002F pass the next cursor if there are more items\n            next: yourFamilies.length === options.limit ? offset + limit : undefined\n        }\n    }\n})",{"id":4074,"title":3941,"titles":4075,"content":422,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Ffamilies#endpoints",[332],{"id":4077,"title":3945,"titles":4078,"content":4079,"level":417},"\u002Fintegrations\u002Fcontrollers\u002Ffamilies#retrieve",[332,3941],"GET \u002Fchurnkey\u002Ffamilies\u002F:id This endpoint fetches Family by its id. Usually, implementation will include finding a family in your database and mapping it to the Family model. Must return Family model. See Family model documentation.See Error Responses. import { Family } from '..\u002Fmodels\u002FFamily'\n\napp.get('\u002Fchurnkey\u002Ffamilies\u002F:id', async (req, res) => {\n    const family = await db.findFamilyById(req.params.id)\n    if (!family) {\n        return res.status(404).send({ code: 404, message: 'Family not found' })\n    }\n    res.send(new Family(family))\n})",{"id":4081,"title":3950,"titles":4082,"content":4083,"level":417},"\u002Fintegrations\u002Fcontrollers\u002Ffamilies#list",[332,3941],"GET \u002Fchurnkey\u002Ffamilies This endpoint fetches a list of families from your database. You should find families in your database (with pagination), map them to the Family model and return a paginated list. Learn more about pagination. import { Family } from '..\u002Fmodels\u002FFamily'\n\napp.get('\u002Fchurnkey\u002Ffamilies', async (req, res) => {\n    const limit = Number.parseInt(req.query.limit)\n    const offset = Number.parseInt(req.query.cursor) \n    const families = await db.findFamilies({ limit, offset })\n    res.send({\n        data: families.map(c => new Family(c)),\n        next: families.length === limit ? offset + limit : undefined\n    })\n})",{"id":4085,"title":201,"titles":4086,"content":3966,"level":374},"\u002Fintegrations\u002Fcontrollers\u002Ffamilies#webhooks",[332],{"id":342,"title":341,"titles":4088,"content":4089,"level":368},[],"Cancels a subscription at specific date. To implement the Cancel action, you need to implement an endpoint and define features If you don't implement this action, the  will not be available to your customers, even if you implemented other actions.",{"id":4091,"title":3931,"titles":4092,"content":4093,"level":374},"\u002Fintegrations\u002Factions\u002Fcancel#prerequisites",[341],"You must implement a Subscriptions controller first.",{"id":4095,"title":3936,"titles":4096,"content":4097,"level":374},"\u002Fintegrations\u002Factions\u002Fcancel#sdk",[341],"If you are using the SDK, you can implement the Cancel action by following the code example below. You don't need to get into the details of the API endpoints, the SDK will take care of that for you. import { Integrator } from '@churnkey\u002Fsdk'\nimport { Subscriptions } from '..\u002Fcontrollers\u002FSubscriptions'\n\nexport const Cancel = Integrator.Cancel.config({\n    Subscriptions: Subscriptions,\n    features: {\n        \u002F\u002F define which schedules are supported, at least one is required\n        schedules: { \n            [Integrator.Cancel.Schedule.Immediate]: true,\n            [Integrator.Cancel.Schedule.EndOfPeriod]: true\n        }\n    },\n    async handle(ctx, options) {\n        const subscription = await this.subscriptions.retrieve({\n            customerId: options.customerId,\n            id: options.subscriptionId\n        })\n\n        switch (options.scheduledAt) {\n            case Integrator.Cancel.Schedule.Immediate:\n                await ctx.db.cancelSubscription(subscription)\n                break\n            case Integrator.Cancel.Schedule.EndOfPeriod:\n                await ctx.db.cancelSubscriptionAtThePeriodEnd(subscription)\n                break\n        }\n    }\n})",{"id":4099,"title":3941,"titles":4100,"content":422,"level":374},"\u002Fintegrations\u002Factions\u002Fcancel#endpoints",[341],{"id":4102,"title":4103,"titles":4104,"content":4105,"level":417},"\u002Fintegrations\u002Factions\u002Fcancel#handle","Handle",[341,3941],"POST \u002Fchurnkey\u002Factions\u002Fsubscription\u002Fcancel This endpoints handles the single subscription cancellation. You should find the subscription by customerId and subscriptionId and cancel it. Options for cancellation, provided in the request body. Must return empty response.See Error Responses. app.post('\u002Fchurnkey\u002Factions\u002Fsubscription\u002Fcancel', async (req, res) => {\n      const subscription = await db.findSubscription(req.body.customerId, req.body.subscriptionId)\n\n      switch (req.body.scheduledAt) {\n          case 'immediate':\n              await db.cancelSubscription(subscription)\n              break\n          case 'end_of_period':\n              await db.cancelSubscriptionAtThePeriodEnd(subscription)\n              break\n      }\n\n      res.send()\n  })",{"id":4107,"title":4108,"titles":4109,"content":4110,"level":417},"\u002Fintegrations\u002Factions\u002Fcancel#handle-all","Handle All",[341,3941],"POST \u002Fchurnkey\u002Factions\u002Fcustomer\u002Fcancel This endpoint handles cancellation of all customer's subscriptions. You should find all subscriptions by customerId and cancel them. This endpoint is optional. By default, when we need to cancel all subscriptions, we will call the Handle endpoint for each subscription. You can implement this endpoint to reduce the number of API calls and improve performance\u002Fend-user UX. Options for cancellation, provided in the request body. Must return empty response.See Error Responses. app.post('\u002Fchurnkey\u002Factions\u002Fcustomer\u002Fcancel', async (req, res) => {\n      const subscriptions = await db.findSubscriptionsByCustomerId(req.body.customerId)\n\n      switch (req.body.scheduledAt) {\n          case 'immediate':\n              await db.cancelSubscriptions(subscriptions)\n              break\n          case 'end_of_period':\n              await db.cancelSubscriptionsAtThePeriodEnd(subscriptions)\n              break\n      }\n\n      res.send()\n  })",{"id":4112,"title":4113,"titles":4114,"content":4115,"level":374},"\u002Fintegrations\u002Factions\u002Fcancel#features","Features",[341],"Features define which behavior is supported for the Cancel action. Depending on the features you enabled, requests body will have different options. For example, if you enable only end-of-period schedule, the request.body.scheduledAt will be always end-of-period. If you enable both schedules, request.body.scheduledAt can be either immediate or end-of-period. export interface Features {\n  enabled: boolean\n  schedules: {\n    immediate: boolean\n    end_of_period: boolean\n  }\n}\n\nexport const features: Features = {\n  enabled: true,\n  schedules: {\n    immediate: true,\n    end_of_period: false \u002F\u002F this schedule will be disabled\n  }\n} html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html pre.shiki code .sH4kE, html code.shiki .sH4kE{--shiki-light:#FF5370;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sjIoE, html code.shiki .sjIoE{--shiki-light:#E53935;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .sIfGp, html code.shiki .sIfGp{--shiki-light:#39ADB5;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sXZA8, html code.shiki .sXZA8{--shiki-light:#E53935;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sHdwX, html code.shiki .sHdwX{--shiki-light:#E2931D;--shiki-default:#0550AE;--shiki-dark:#79C0FF}",{"id":346,"title":345,"titles":4117,"content":4118,"level":368},[],"Pauses a subscription at specific date for a specific period. Required for  to work. To implement the Pause action, you need to implement an endpoint and define features",{"id":4120,"title":3931,"titles":4121,"content":4122,"level":374},"\u002Fintegrations\u002Factions\u002Fpause#prerequisites",[345],"You must implement a Subscriptions controller first.Pause is a part of the Cancel Flow. You must implement the Cancel action first.",{"id":4124,"title":3936,"titles":4125,"content":4126,"level":374},"\u002Fintegrations\u002Factions\u002Fpause#sdk",[345],"If you are using the SDK, you can implement the Pause action by following the code example below. You don't need to get into the details of the API endpoints, the SDK will take care of that for you. import { Integrator } from '@churnkey\u002Fsdk'\nimport { Subscriptions } from '..\u002Fcontrollers\u002FSubscriptions'\n\nexport const Pause = Integrator.Pause.config({\n    Subscriptions: Subscriptions,\n    features: {\n        \u002F\u002F define which start dates are supported, at least one is required\n        startDates: { \n            [Integrator.Pause.StartDate.Immediate]: true,\n            [Integrator.Pause.StartDate.EndOfPeriod]: true\n        },\n        \u002F\u002F define which durations are supported, at least one is required\n        durations: {\n            [Integrator.Pause.Duration.Period]: true,\n            [Integrator.Pause.Duration.Date]: true\n        },\n        allowAnnual: true\n    },\n    async handle(ctx, options) {\n        const subscription = await this.subscriptions.retrieve({\n            customerId: options.customerId,\n            id: options.subscriptionId\n        })\n\n        if (!options.allowAnnual) {\n            if (subscription.duration.unit === 'year') {\n                throw new Integrator.Error(400, 'Annual subscriptions are not allowed to be paused')\n            }\n        }\n\n        switch (options.startAt) {\n            case Integrator.Pause.StartDate.Immediate:\n                await ctx.db.pauseSubscription(subscription, {\n                    duration: options.duration\n                })\n                break\n            case Integrator.Pause.StartDate.EndOfPeriod:\n                await ctx.db.pauseSubscriptionAtThePeriodEnd(subscription, {\n                    duration: options.duration\n                })\n                break\n        }\n    }\n})",{"id":4128,"title":3941,"titles":4129,"content":422,"level":374},"\u002Fintegrations\u002Factions\u002Fpause#endpoints",[345],{"id":4131,"title":4103,"titles":4132,"content":4133,"level":417},"\u002Fintegrations\u002Factions\u002Fpause#handle",[345,3941],"POST \u002Fchurnkey\u002Factions\u002Fsubscription\u002Fpause This endpoints handles the single subscription pause. You should find the subscription by customerId and subscriptionId and pause it. Options for pause, provided in the request body. Must return empty response.See Error Responses. app.post('\u002Fchurnkey\u002Factions\u002Fsubscription\u002Fpause', async (req, res) => {\n      const subscription = await db.findSubscription(req.body.customerId, req.body.subscriptionId)\n      \n      if (!req.body.allowAnnual) {\n          if (subscription.duration.unit === 'year') {\n              return res.status(400).send({\n                  code: 400,\n                  message: 'Annual subscriptions are not allowed to be paused'\n              })\n          }\n      }\n\n      switch (req.body.startAt) {\n          case 'immediate':\n              await db.pauseSubscription(subscription, {\n                  duration: req.body.duration\n              })\n              break\n          case 'end_of_period':\n              await db.pauseSubscriptionAtThePeriodEnd(subscription, {\n                  duration: req.body.duration\n              })\n              break\n      }\n      res.send()\n  })",{"id":4135,"title":4108,"titles":4136,"content":4137,"level":417},"\u002Fintegrations\u002Factions\u002Fpause#handle-all",[345,3941],"POST \u002Fchurnkey\u002Factions\u002Fcustomer\u002Fpause This endpoint handles pause of all customer's subscriptions. You should find all subscriptions by customerId and pause them. This endpoint is optional. By default, when we need to pause all subscriptions, we will call the Handle endpoint for each subscription. You can implement this endpoint to reduce the number of API calls and improve performance\u002Fend-user UX. Options for pause, provided in the request body. Must return empty response.See Error Responses. app.post('\u002Fchurnkey\u002Factions\u002Fcustomer\u002Fcancel', async (req, res) => {\n      const subscriptions = await db.findSubscriptionsByCustomerId(req.body.customerId)\n\n      if (!req.body.allowAnnual) {\n          if (subscriptions.some(sub => sub.duration.unit === 'year')) {\n              return res.status(400).send({\n                  code: 400,\n                  message: 'Annual subscriptions are not allowed to be paused'\n              })\n          }\n      }\n\n      switch (req.body.startAt) {\n          case 'immediate':\n              await db.pauseSubscriptions(subscriptions, {\n                  duration: req.body.duration\n              })\n              break\n          case 'end_of_period':\n              await db.pauseSubscriptionsAtThePeriodEnd(subscriptions, {\n                  duration: req.body.duration\n              })\n              break\n      }\n\n      res.send()\n  })",{"id":4139,"title":4113,"titles":4140,"content":4141,"level":374},"\u002Fintegrations\u002Factions\u002Fpause#features",[345],"Features define which behavior is supported for the Pause action. Depending on the features you enabled, requests body will have different options. For example, if you enable only end-of-period start date, the request.body.startAt will be always end-of-period. If you enable both start dates, request.body.startAt can be either immediate or end-of-period. export interface Features {\n  enabled: boolean\n  startDates: {\n    immediate: boolean\n    end_of_period: boolean\n  },\n  durations: {\n    period: boolean\n    date: boolean\n  },\n  allowAnnual: boolean\n}\n\nexport const features: Features = {\n  enabled: true,\n  startDates: {\n    immediate: true,\n    end_of_period: false \u002F\u002F this start date will be disabled\n  },\n  durations: {\n      period: true,\n      date: false \u002F\u002F this duration type will be disabled\n  },\n  allowAnnual: true\n} html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html pre.shiki code .sH4kE, html code.shiki .sH4kE{--shiki-light:#FF5370;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sjIoE, html code.shiki .sjIoE{--shiki-light:#E53935;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .sIfGp, html code.shiki .sIfGp{--shiki-light:#39ADB5;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sIO_5, html code.shiki .sIO_5{--shiki-light:#F76D47;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sXZA8, html code.shiki .sXZA8{--shiki-light:#E53935;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sHdwX, html code.shiki .sHdwX{--shiki-light:#E2931D;--shiki-default:#0550AE;--shiki-dark:#79C0FF}",{"id":350,"title":349,"titles":4143,"content":4144,"level":368},[],"Extends a subscription trial period for a specific duration. Required for  to work. To implement the Extend Trial action, you need to implement an endpoint and define features",{"id":4146,"title":3931,"titles":4147,"content":4148,"level":374},"\u002Fintegrations\u002Factions\u002Fextend-trial#prerequisites",[349],"You must implement a Subscriptions controller first.Trial extension is a part of the Cancel Flow. You must implement the Cancel action first.",{"id":4150,"title":3936,"titles":4151,"content":4152,"level":374},"\u002Fintegrations\u002Factions\u002Fextend-trial#sdk",[349],"If you are using the SDK, you can implement the Extend Trial action by following the code example below. You don't need to get into the details of the API endpoints, the SDK will take care of that for you. import { Integrator } from '@churnkey\u002Fsdk'\nimport { Subscriptions } from '..\u002Fcontrollers\u002FSubscriptions'\n\nexport const ExtendTrial = Integrator.ExtendTrial.config({\n    Subscriptions: Subscriptions,\n    features: {\n        enabled: true,\n        multiple: true,\n        durations: {\n            period: true,\n            date: true\n        }\n    },\n    async handle(ctx, options) {\n        const subscription = await this.subscriptions.retrieve({\n            customerId: options.customerId,\n            id: options.subscriptionId\n        })\n\n        await ctx.db.extendSubscriptionTrial(subscription, {\n            duration: options.duration\n        })\n    }\n})",{"id":4154,"title":3941,"titles":4155,"content":422,"level":374},"\u002Fintegrations\u002Factions\u002Fextend-trial#endpoints",[349],{"id":4157,"title":4103,"titles":4158,"content":4159,"level":417},"\u002Fintegrations\u002Factions\u002Fextend-trial#handle",[349,3941],"POST \u002Fchurnkey\u002Factions\u002Fsubscription\u002Fextend-trial This endpoints handles the subscription trial extension. You should find the subscription by customerId and subscriptionId and extend it's trial. Options for trial extension, provided in the request body. Must return empty response.See Error Responses. app.post('\u002Fchurnkey\u002Factions\u002Fsubscription\u002Fextend-trial', async (req, res) => {\n      const subscription = await db.findSubscription(req.body.customerId, req.body.subscriptionId)\n      \n      await db.extendSubscriptionTrial(subscription, {\n          duration: req.body.duration\n      })\n      res.send()\n  })",{"id":4161,"title":4113,"titles":4162,"content":4163,"level":374},"\u002Fintegrations\u002Factions\u002Fextend-trial#features",[349],"Features define which behavior is supported for the Extend Trial action. Depending on the features you enabled, requests body will have different options. For example, if you enable only period duration type, the request.body.duration will be always of period type. If you enable both type, request.body.duration can be either of type period or date. export interface Features {\n  enabled: boolean\n  multiple: boolean\n  durations: {\n    period: boolean\n    date: boolean\n  }\n}\n\nexport const features: Features = {\n  enabled: true,\n  multiple: true,\n  durations: {\n      period: true,\n      date: false \u002F\u002F this duration type will be disabled\n  }\n} html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sH4kE, html code.shiki .sH4kE{--shiki-light:#FF5370;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sjIoE, html code.shiki .sjIoE{--shiki-light:#E53935;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .sIfGp, html code.shiki .sIfGp{--shiki-light:#39ADB5;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sXZA8, html code.shiki .sXZA8{--shiki-light:#E53935;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sHdwX, html code.shiki .sHdwX{--shiki-light:#E2931D;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}",{"id":354,"title":353,"titles":4165,"content":4166,"level":368},[],"Apply a coupon to a subscription. Required for  to work. To implement the Apply Coupon action, you need to implement an endpoint and define features",{"id":4168,"title":3931,"titles":4169,"content":4170,"level":374},"\u002Fintegrations\u002Factions\u002Fapply-coupon#prerequisites",[353],"You must implement a Subscriptions controller first.You must implement a Coupons controller first.Coupon application is a part of the Cancel Flow. You must implement the Cancel action first.",{"id":4172,"title":3936,"titles":4173,"content":4174,"level":374},"\u002Fintegrations\u002Factions\u002Fapply-coupon#sdk",[353],"If you are using the SDK, you can implement the Apply Coupon action by following the code example below. You don't need to get into the details of the API endpoints, the SDK will take care of that for you. import { Integrator } from '@churnkey\u002Fsdk'\nimport { Subscriptions } from '..\u002Fcontrollers\u002FSubscriptions'\nimport { Coupons } from '..\u002Fcontrollers\u002FCoupons'\n\nexport const ApplyCoupon = Integrator.ApplyCoupon.config({\n    Subscriptions: Subscriptions,\n    Coupons: Coupons,\n    features: {\n        enabled: true\n    },\n    async handle(ctx, options) {\n        const subscription = await this.subscriptions.retrieve({\n            customerId: options.customerId,\n            id: options.subscriptionId\n        })\n\n        const coupon = await this.coupons.retrieve({\n            id: options.coupon\n        })\n\n        await ctx.db.applyCoupon({ subscription, coupon })\n    }\n})",{"id":4176,"title":3941,"titles":4177,"content":422,"level":374},"\u002Fintegrations\u002Factions\u002Fapply-coupon#endpoints",[353],{"id":4179,"title":4103,"titles":4180,"content":4181,"level":417},"\u002Fintegrations\u002Factions\u002Fapply-coupon#handle",[353,3941],"POST \u002Fchurnkey\u002Factions\u002Fsubscription\u002Fapply-coupon This endpoint applies coupon to a subscription. You should find the subscription by customerId and subscriptionId and apply coupon to it. Options for this action, provided in the request body. Must return empty response.See Error Responses. app.post('\u002Fchurnkey\u002Factions\u002Fsubscription\u002Fapply-coupon', async (req, res) => {\n      const subscription = await db.findSubscription(req.body.customerId, req.body.subscriptionId)\n      const coupon = await db.findCoupon(req.body.id)\n\n      await db.applyCoupon({subscription, coupon})\n      res.send()\n  })",{"id":4183,"title":4113,"titles":4184,"content":4185,"level":374},"\u002Fintegrations\u002Factions\u002Fapply-coupon#features",[353],"Features define which behavior is supported for the Apply Coupon action. Depending on the features you enabled, requests body will have different options. export interface Features {\n  enabled: boolean\n}\n\nexport const features: Features = {\n  enabled: true,\n} html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sH4kE, html code.shiki .sH4kE{--shiki-light:#FF5370;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sjIoE, html code.shiki .sjIoE{--shiki-light:#E53935;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .sIfGp, html code.shiki .sIfGp{--shiki-light:#39ADB5;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sXZA8, html code.shiki .sXZA8{--shiki-light:#E53935;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sHdwX, html code.shiki .sHdwX{--shiki-light:#E2931D;--shiki-default:#0550AE;--shiki-dark:#79C0FF}",{"id":358,"title":357,"titles":4187,"content":4188,"level":368},[],"Changes the price and\u002For product of a subscription. Required for  to work. To implement the Change Price action, you need to implement an endpoint and define features",{"id":4190,"title":3931,"titles":4191,"content":4192,"level":374},"\u002Fintegrations\u002Factions\u002Fchange-price#prerequisites",[357],"You must implement a Subscriptions controller first.You must implement a Prices controller first.Price change is a part of the Cancel Flow. You must implement the Cancel action first.",{"id":4194,"title":3936,"titles":4195,"content":4196,"level":374},"\u002Fintegrations\u002Factions\u002Fchange-price#sdk",[357],"If you are using the SDK, you can implement the Change Price action by following the code example below. You don't need to get into the details of the API endpoints, the SDK will take care of that for you. import { Integrator } from '@churnkey\u002Fsdk'\nimport { Subscriptions } from '..\u002Fcontrollers\u002FSubscriptions'\nimport { Prices } from '..\u002Fcontrollers\u002FPrices'\n\nexport const ChangePrice = Integrator.ChangePrice.config({\n    Subscriptions: Subscriptions,\n    Prices: Prices,\n    features: {\n        enabled: true,\n        schedules: {\n            [Integrator.ChangePrice.Schedule.Immediate]: true,\n            [Integrator.ChangePrice.Schedule.EndOfPeriod]: true\n        },\n        prorate: true\n    },\n    async handle(ctx, options) {\n        const subscription = await this.subscriptions.retrieve({\n            customerId: options.customerId,\n            id: options.subscriptionId\n        })\n\n        const price = await this.prices.retrieve({\n            id: options.price\n        })\n\n        await ctx.db.changePrice({ subscription, price, at: options.scheduledAt, prorate: options.prorate })\n    }\n})",{"id":4198,"title":3941,"titles":4199,"content":422,"level":374},"\u002Fintegrations\u002Factions\u002Fchange-price#endpoints",[357],{"id":4201,"title":4103,"titles":4202,"content":4203,"level":417},"\u002Fintegrations\u002Factions\u002Fchange-price#handle",[357,3941],"POST \u002Fchurnkey\u002Factions\u002Fsubscription\u002Fapply-coupon This endpoint changes a price of subscription. You should find the subscription by customerId and subscriptionId and set a new price to it. Options for this action provided in the request body. Must return empty response.See Error Responses. app.post('\u002Fchurnkey\u002Factions\u002Fsubscription\u002Fchange-price', async (req, res) => {\n      const subscription = await db.findSubscription(req.body.customerId, req.body.subscriptionId)\n      const price = await db.findPrice(req.body.id)\n\n      await db.changePrice({subscription, price, at: req.body.scheduledAt, prorate: req.body.prorate})\n      res.send()\n  })",{"id":4205,"title":4113,"titles":4206,"content":4207,"level":374},"\u002Fintegrations\u002Factions\u002Fchange-price#features",[357],"Features define which behavior is supported for the Change Price action. Depending on the features you enabled, requests body will have different options. export interface Features {\n  enabled: boolean\n  schedules: {\n      immediate: boolean,\n      'end-of-period': boolean\n  },\n  prorate: boolean\n}\n\nexport const features: Features = {\n  enabled: true,\n  schedules: {\n      immediate: true,\n      'end-of-period': true\n  },\n  prorate: true\n} html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sH4kE, html code.shiki .sH4kE{--shiki-light:#FF5370;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sjIoE, html code.shiki .sjIoE{--shiki-light:#E53935;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .sIfGp, html code.shiki .sIfGp{--shiki-light:#39ADB5;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sXZA8, html code.shiki .sXZA8{--shiki-light:#E53935;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .sHdwX, html code.shiki .sHdwX{--shiki-light:#E2931D;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sH12a, html code.shiki .sH12a{--shiki-light:#E53935;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}",{"id":362,"title":361,"titles":4209,"content":4210,"level":368},[],"Learn how to make your integration available to Churnkey. Before following this guide, make sure that you have implemented all the required controllers and actions.",{"id":4212,"title":4213,"titles":4214,"content":4215,"level":374},"\u002Fintegrations\u002Fpublish#get-integration-token","Get Integration Token",[361],"To obtain your Integration Token, go to the Churnkey dashboard -> Settings -> Billing Providers: Choose Custom Provider and copy the Integration Token: Never store your Integration Token in your codebase. Use environment variables or a secure vault to store it.",{"id":4217,"title":4218,"titles":4219,"content":4220,"level":374},"\u002Fintegrations\u002Fpublish#make-integration-publicly-available","Make integration publicly available",[361],"First you need to create Integration instance, where you should pass your Context and all the controllers and actions you implemented.import { Integrator } from '@churnkey\u002Fsdk'\nimport { Context } from '.\u002FContext'\nimport { Customers } from '.\u002Fcontrollers\u002FCustomers'\nimport { Prices } from '.\u002Fcontrollers\u002FPrices'\nimport { Subscriptions } from '.\u002Fcontrollers\u002FSubscriptions'\nimport { Coupons } from '.\u002Fcontrollers\u002FCoupons' \u002F\u002F optional\nimport { Cancel } from '.\u002Factions\u002FCancel' \u002F\u002F optional\nimport { ApplyCoupon } from '.\u002Factions\u002FApplyCoupon' \u002F\u002F optional\nimport { ExtendTrial } from '.\u002Factions\u002FExtendTrial' \u002F\u002F optional\nimport { ChangePrice } from '.\u002Factions\u002FChangePrice' \u002F\u002F optional\nimport { Pause } from '.\u002Factions\u002FPause' \u002F\u002F optional\n\nexport const Integration = new Integrator.Integration({\n  ctx: Context,\n  name: 'YourCompanyName',\n  modules: {\n      controllers: {\n          Customers,\n          Prices,\n          Subscriptions,\n          Coupons \u002F\u002F optional\n      },\n      actions: {\n          Cancel, \u002F\u002F optional\n          ApplyCoupon, \u002F\u002F optional\n          ExtendTrial, \u002F\u002F optional\n          ChangePrice, \u002F\u002F optional\n          Pause \u002F\u002F optional\n      }\n  }\n})\nNext, in your router file, you should expose the Integration to the internet. Authentication and features manifest are handled by the SDK, so you don't need to worry about it.import express from 'express'\nimport { Integration } from '.\u002Fchurnkey\u002FIntegration'\nimport { Context } from '.\u002Fchurnkey\u002FContext'\n\nconst app = express()\nIntegration.expose({\n  app: app, \u002F\u002F express app instance\n  token: process.env.CK_INTEGRATION_TOKEN, \u002F\u002F your integration token\n  ctx(req, req) {\n      return new Context(\n          \u002F\u002F initialize your context here\n          \u002F\u002F parameters can vary depending on your implementation\n      )\n  } \n}) If SDK is not available in your preferred programming language, you should follow this guide to make your integration available to Churnkey.FeaturesYou should add an endpoint which will return a list of supported features, we call it Feature Manifest. This manifest tells us what Controllers, Actions\nand their behavior are supported by your integration.import { features } from '..\u002Factions\u002FCancel'\n\napp.get('\u002Fchurnkey\u002Ffeatures', async (req, res) => {\n    res.send({\n        controllers: {\n            customers: true,\n            prices: {\n                enabled: true,\n                type: 'product'\n            }\n            subscriptions: true,\n            coupons: true \u002F\u002F optional\n            products: { \u002F\u002F optional\n                enabled: true,\n                type: 'family'\n            },\n            families: true \u002F\u002F optional\n        },\n        actions: {\n            cancel: features, \u002F\u002F optional\n            ... \u002F\u002F other actions features if you have them\n        }\n    })\n})\nAuthenticationEach request to your implemented endpoints will contain a Authorization header with a Bearer token. You should compare this token with your Integration Token, if they match you should consider request authorized.\u002F\u002F add express middleware before all your routes\napp.use(async (req, res, next) => {\n    if (req.headers.authorization !== process.env.CK_INTEGRATION_TOKEN) {\n        return res.status(401).send({ code: 401, message: 'Unauthorized' })\n    }\n    next()\n})\nVerify endpointsMake sure that you implemented all required Controller and Action endpoints and your auth middleware is protecting them from unauthorized access. ::",{"id":4222,"title":4223,"titles":4224,"content":4225,"level":374},"\u002Fintegrations\u002Fpublish#verify-your-integration","Verify your Integration",[361],"Go to the Churnkey dashboard -> Settings -> Billing Providers. You should see that your Custom Provider is pending verification: Add your API URL to the Custom Provider. This is your API root URL, we will append the endpoint with \u002Fchurnkey prefix to it automatically: Click on the Verify button to verify your integration: You should see results and list of supported features soon:",{"id":4227,"title":2200,"titles":4228,"content":4229,"level":374},"\u002Fintegrations\u002Fpublish#troubleshooting",[361],"If you see any errors during the verification process, the most common issues are: Incorrect Integration TokenIncorrect data format (e.g. wrong Model implementation)Incorrect endpoint implementation (e.g. wrong url or wrong response)Features endpoint is not implemented Generally, you should be able to fix most of the issues by following the error message you see. If you are still stuck, feel free to reach out to our support team, we are happy to help you. Keep in mind that we're following modular architecture, so if one of the optional modules doesn't work, other modules should work fine.",{"id":4231,"title":4232,"titles":4233,"content":4234,"level":374},"\u002Fintegrations\u002Fpublish#updating-integration","Updating integration",[361],"Over time you may want to add new features or fix bugs in your integration. We cache the Feature Manifest, so if your update includes changes to the Feature Manifest, you should re-verify your integration. If you only update the implementation, you don't need to re-verify it.",{"id":4236,"title":4237,"titles":4238,"content":4239,"level":417},"\u002Fintegrations\u002Fpublish#manual-verification","Manual verification",[361,4232],"You can go to the Churnkey dashboard -> Settings -> Billing Providers and click on the Verify button to re-verify your integration.",{"id":4241,"title":4242,"titles":4243,"content":4244,"level":417},"\u002Fintegrations\u002Fpublish#automatic-verification","Automatic verification",[361,4232],"You can call the verify endpoint programmatically from your CI\u002FCD or during the server start. This way, you can ensure that your integration is always up-to-date and verified. curl -X POST https:\u002F\u002Fapi.churnkey.co\u002Fv2\u002Fverify -H \"Authorization: Bearer $CK_API_KEY\"\n\u002F\u002F in your server file\nfetch('https:\u002F\u002Fapi.churnkey.co\u002Fv2\u002Fverify', {\n    method: 'POST',\n    headers: {\n        'Authorization': `Bearer ${process.env.CK_API_KEY}`\n    }\n})\nimport { Integrator } from '@churnkey\u002Fsdk'\nIntegrator.verify(process.env.CK_API_KEY) html pre.shiki code .sqgCv, html code.shiki .sqgCv{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#CF222E;--shiki-default-font-style:inherit;--shiki-dark:#FF7B72;--shiki-dark-font-style:inherit}html pre.shiki code .sWneD, html code.shiki .sWneD{--shiki-light:#39ADB5;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .saDVj, html code.shiki .saDVj{--shiki-light:#90A4AE;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html pre.shiki code .sX5mu, html code.shiki .sX5mu{--shiki-light:#39ADB5;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .sasMN, html code.shiki .sasMN{--shiki-light:#91B859;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}html pre.shiki code .siFmp, html code.shiki .siFmp{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6E7781;--shiki-default-font-style:inherit;--shiki-dark:#8B949E;--shiki-dark-font-style:inherit}html pre.shiki code .s4vtu, html code.shiki .s4vtu{--shiki-light:#9C3EDA;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .sum78, html code.shiki .sum78{--shiki-light:#90A4AE;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .s8eFF, html code.shiki .s8eFF{--shiki-light:#39ADB5;--shiki-default:#CF222E;--shiki-dark:#FF7B72}html pre.shiki code .siEnl, html code.shiki .siEnl{--shiki-light:#6182B8;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sZ24s, html code.shiki .sZ24s{--shiki-light:#E53935;--shiki-default:#1F2328;--shiki-dark:#E6EDF3}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sjIoE, html code.shiki .sjIoE{--shiki-light:#E53935;--shiki-default:#8250DF;--shiki-dark:#D2A8FF}html pre.shiki code .sWFk-, html code.shiki .sWFk-{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#953800;--shiki-default-font-style:inherit;--shiki-dark:#FFA657;--shiki-dark-font-style:inherit}html pre.shiki code .sH4kE, html code.shiki .sH4kE{--shiki-light:#FF5370;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sIO_5, html code.shiki .sIO_5{--shiki-light:#F76D47;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .seIyd, html code.shiki .seIyd{--shiki-light:#E2931D;--shiki-default:#953800;--shiki-dark:#FFA657}html pre.shiki code .smky3, html code.shiki .smky3{--shiki-light:#91B859;--shiki-default:#0550AE;--shiki-dark:#79C0FF}html pre.shiki code .sH12a, html code.shiki .sH12a{--shiki-light:#E53935;--shiki-default:#0A3069;--shiki-dark:#A5D6FF}",{"id":4246,"title":44,"body":4247,"description":4736,"extension":4737,"meta":4738,"navigation":4739,"path":45,"seo":4740,"sitemap":4741,"stem":46,"__hash__":4742},"content\u002F2.cancel-flows\u002F5.version-history.md",{"type":4248,"value":4249,"toc":4717},"minimark",[4250,4259,4262,4266,4269,4282,4285,4297,4311,4326,4335,4341,4349,4353,4356,4386,4392,4395,4397,4401,4458,4466,4474,4478,4481,4488,4498,4509,4512,4517,4526,4534,4538,4541,4547,4550,4578,4584,4587,4594,4612,4620,4624,4631,4646,4649,4652,4662,4665,4672,4675,4678,4684,4687,4693,4696,4702,4705,4707,4710,4712,4715],[4251,4252,4253,4254,4258],"p",{},"The Cancel Flow builder saves your changes automatically as you edit. That is convenient most of the time, but ",[4255,4256,4257],"strong",{},"auto-save also means that a stray click, an accidental deletion, or a mid-edit mistake can be written to your draft before you notice",". Version control gives you a safety net around that behavior: you can step backward through your recent edits, throw away a messy draft, or jump back to any version of the flow you have previously published.",[4251,4260,4261],{},"This page explains how each control works, when to reach for it, and what actually gets versioned behind the scenes.",[4263,4264,878],"h2",{"id":4265},"why-this-exists",[4251,4267,4268],{},"There are two moments where customers historically lost work. The first is the small one: you delete a step, move something by mistake, or paste the wrong copy into a header, and auto-save persists the change before you realize. The second is larger: you spend an afternoon reworking a flow, decide the new version is worse than the one you shipped last month, and want the old one back.",[4251,4270,4271,4274,4275,4278,4279,4281],{},[4255,4272,4273],{},"Undo\u002Fredo"," addresses the first case. ",[4255,4276,4277],{},"Version history with restore"," addresses the second. ",[4255,4280,903],{}," is the middle ground — when you want to throw away everything you have touched in this editing session and return to the currently live flow.",[4263,4283,883],{"id":4284},"accessing-version-history",[4251,4286,4287,4288,4292,4293,4296],{},"Open any flow in the builder (",[4289,4290,4291],"code",{},"app.churnkey.co\u002Fbuilder\u002F:flowId","). In the top toolbar you will find a ",[4255,4294,4295],{},"History"," dropdown next to the Undo and Redo buttons. On smaller screens the same action lives inside the collapsed menu button.",[4251,4298,4299,4300,4302,4303,4306,4307,4310],{},"Clicking ",[4255,4301,4295],{}," opens a short menu with two options: ",[4255,4304,4305],{},"View version history"," and ",[4255,4308,4309],{},"Discard unsaved changes",". The second option is disabled when your draft is in sync with the last published version.",[4312,4313,4317,4318],"div",{"className":4314},[4315,4316],"flex","justify-center","\n  ",[4319,4320],"img",{"src":4321,"alt":4322,"className":4323},"\u002Fimg\u002Fcancel_flow\u002Fhistory-menu.png","History dropdown menu with \"View version history\" and \"Discard unsaved changes\"",[4324,4325],"rounded-lg","shadow-lg",[4327,4328,4334],"figcaption",{"className":4329},[4330,4331,4332,4333],"text-center","text-sm","text-gray-600","mt-1","The History dropdown exposes both entry points into version control.",[4251,4336,4337,4338,4340],{},"Choosing ",[4255,4339,4305],{}," opens the Version History drawer on the right side of the editor. The drawer lists every published version of this flow, newest first, and stays open while you continue to work so you can compare what is in front of you against previous snapshots.",[4312,4342,4317,4344],{"className":4343},[4315,4316],[4319,4345],{"src":4346,"alt":4347,"className":4348},"\u002Fimg\u002Fcancel_flow\u002Fversion-history-drawer.png","Version History drawer showing a timeline of published versions",[4324,4325],[4327,4350,4352],{"className":4351},[4330,4331,4332,4333],"The drawer lists every published version of the flow with date, time, and a Restore shortcut.",[4251,4354,4355],{},"Each entry in the timeline shows:",[4357,4358,4359,4367,4374,4380],"ul",{},[4360,4361,4362,4363,4366],"li",{},"A ",[4255,4364,4365],{},"version number"," (auto-incremented every time you publish).",[4360,4368,4369,4370,4373],{},"The ",[4255,4371,4372],{},"date and time"," the version was published.",[4360,4375,4362,4376,4379],{},[4255,4377,4378],{},"Live"," badge on the version currently being served to your customers.",[4360,4381,4362,4382,4385],{},[4255,4383,4384],{},"Restore"," button on every version that is not the live one.",[4251,4387,4388,4389,4391],{},"If you have unpublished changes in the draft, the ",[4255,4390,4309],{}," option in the History dropdown becomes enabled, giving you a shortcut to revert without leaving the builder.",[4263,4393,888],{"id":4394},"undo-and-redo",[4251,4396,890],{},[4398,4399,893],"h3",{"id":4400},"keyboard-shortcuts",[4402,4403,4404,4420],"table",{},[4405,4406,4407],"thead",{},[4408,4409,4410,4414,4417],"tr",{},[4411,4412,4413],"th",{},"Action",[4411,4415,4416],{},"macOS",[4411,4418,4419],{},"Windows \u002F Linux",[4421,4422,4423,4439],"tbody",{},[4408,4424,4425,4429,4434],{},[4426,4427,4428],"td",{},"Undo",[4426,4430,4431],{},[4289,4432,4433],{},"⌘Z",[4426,4435,4436],{},[4289,4437,4438],{},"Ctrl+Z",[4408,4440,4441,4444,4449],{},[4426,4442,4443],{},"Redo",[4426,4445,4446],{},[4289,4447,4448],{},"⌘⇧Z",[4426,4450,4451,4454,4455],{},[4289,4452,4453],{},"Ctrl+Shift+Z"," or ",[4289,4456,4457],{},"Ctrl+Y",[4251,4459,4460,4461,4306,4463,4465],{},"The same actions are available as ",[4255,4462,4428],{},[4255,4464,4443],{}," buttons in the builder toolbar. On mobile screens the buttons move into the collapsed toolbar menu.",[4312,4467,4317,4469],{"className":4468},[4315,4316],[4319,4470],{"src":4471,"alt":4472,"className":4473},"\u002Fimg\u002Fcancel_flow\u002Fundo-redo-toolbar.png","Undo and Redo buttons in the builder toolbar",[4324,4325],[4327,4475,4477],{"className":4476},[4330,4331,4332,4333],"Undo, Redo, and the History dropdown live next to the Saved indicator in the builder toolbar.",[4398,4479,898],{"id":4480},"how-far-back-can-you-go",[4251,4482,4483,4484,4487],{},"The builder stores up to ",[4255,4485,4486],{},"50 recent states"," per editing session. Once you pass that limit, the oldest state drops off the bottom of the stack.",[4251,4489,4490,4491,4494,4495,4497],{},"Continuous changes are grouped together so that the undo stack stays useful. The builder waits ",[4255,4492,4493],{},"500 milliseconds"," after you stop typing (or dragging, or clicking) before it records a new snapshot. In practice this means that typing a paragraph of copy is a single undo, not one undo per keystroke — ",[4289,4496,4433],{}," takes you back to before you started typing, not back one character.",[4499,4500,4502],"alert",{"type":4501},"tip",[4251,4503,4504,4505,4508],{},"Undo and redo are ",[4255,4506,4507],{},"session-local",". They track the changes you have made since you opened the builder on this device. Closing the tab, refreshing the page, or switching devices clears the stack. To recover something from a previous session, use Version History.",[4263,4510,903],{"id":4511},"discard-changes",[4251,4513,4514,4516],{},[4255,4515,903],{}," reverts your current draft to match the version that is live for your customers right now. Use it when you have been experimenting, decide nothing you did is worth keeping, and want a clean slate.",[4251,4518,4519,4520,4522,4523,4525],{},"To discard, click ",[4255,4521,4295],{}," in the toolbar and choose ",[4255,4524,4309],{},". The option is only active when your draft diverges from the last published version.",[4312,4527,4317,4529],{"className":4528},[4315,4316],[4319,4530],{"src":4531,"alt":4532,"className":4533},"\u002Fimg\u002Fcancel_flow\u002Fdiscard-changes-confirmation.png","Discard Changes confirmation dialog",[4324,4325],[4327,4535,4537],{"className":4536},[4330,4331,4332,4333],"Discard Changes asks for confirmation before replacing your draft with the live version.",[4251,4539,4540],{},"Churnkey asks you to confirm before discarding, because the action cannot be undone from the drawer itself:",[4542,4543,4544],"blockquote",{},[4251,4545,4546],{},"This will revert all unpublished changes to the last published version.",[4251,4548,4549],{},"Confirming replaces your draft with the live version. Specifically, the following fields are restored:",[4357,4551,4552,4559,4564,4572],{},[4360,4553,4554,4555,4558],{},"All ",[4255,4556,4557],{},"steps"," and their configuration",[4360,4560,4369,4561],{},[4255,4562,4563],{},"flow name",[4360,4565,4369,4566,4306,4569],{},[4255,4567,4568],{},"brand image",[4255,4570,4571],{},"primary color",[4360,4573,4574,4575],{},"The list of ",[4255,4576,4577],{},"translated languages",[4251,4579,4580,4581,4583],{},"If you discard by mistake, you can still press ",[4289,4582,4433],{}," to bring the draft back — undo works across this action too, as long as you have not closed the tab.",[4263,4585,908],{"id":4586},"restoring-a-previously-published-version",[4251,4588,4589,4590,4593],{},"Restoring goes one step further than Discard: instead of returning to the current live flow, you can pick ",[4255,4591,4592],{},"any version in the timeline"," and roll the draft back to its contents.",[4595,4596,4597,4600,4603,4609],"ol",{},[4360,4598,4599],{},"Open the Version History drawer from the toolbar.",[4360,4601,4602],{},"Find the version you want to restore. Each entry shows its publish timestamp.",[4360,4604,4605,4606,4608],{},"Click ",[4255,4607,4384],{}," on that entry.",[4360,4610,4611],{},"Confirm in the warning modal that appears.",[4312,4613,4317,4615],{"className":4614},[4315,4316],[4319,4616],{"src":4617,"alt":4618,"className":4619},"\u002Fimg\u002Fcancel_flow\u002Frestore-confirmation-modal.png","Restore confirmation modal warning about overwriting the draft",[4324,4325],[4327,4621,4623],{"className":4622},[4330,4331,4332,4333],"Restore warns you before overwriting the draft — the action is undoable via ⌘Z.",[4251,4625,4626,4627,4630],{},"Restoring rewrites your current draft with the steps, flow name, branding, and translations from the version you selected. ",[4255,4628,4629],{},"Restoring does not re-publish the flow."," Your customers continue to see the live version until you review the restored draft and publish it yourself — this lets you make further edits on top of an old version before shipping it.",[4499,4632,4634],{"type":4633},"info",[4251,4635,4636,4639,4640,4642,4643,4645],{},[4255,4637,4638],{},"Restore is undoable."," If you click Restore on the wrong version, press ",[4289,4641,4433],{}," (or ",[4289,4644,4438],{},") to bring back the draft you had before. The restore counts as a single entry in the undo stack, so one undo is enough.",[4263,4647,913],{"id":4648},"what-gets-versioned",[4251,4650,4651],{},"Understanding what lands in the timeline — and what does not — will save you some head-scratching the first time you go looking for something.",[4251,4653,4654,4657,4658,4661],{},[4255,4655,4656],{},"Only published versions appear in Version History."," Every time you click ",[4255,4659,4660],{},"Publish",", Churnkey takes a snapshot of the flow in its current state, locks that snapshot so it cannot be edited, and advances your working draft on top of it. That snapshot is what shows up in the drawer.",[4251,4663,4664],{},"Drafts in progress are not versioned. If you are halfway through redesigning a step and close the builder, the draft is auto-saved, but nothing new lands in the timeline. You are protected during that editing session by the undo stack, and protected against losing the live flow by Discard and Restore.",[4251,4666,4667,4668,4671],{},"This model keeps the timeline focused on ",[4255,4669,4670],{},"states you actually shipped to customers",". You are not wading through dozens of half-finished drafts to find the last version that went live.",[4263,4673,918],{"id":4674},"frequently-asked-questions",[4398,4676,922],{"id":4677},"does-restoring-an-old-version-publish-it-automatically",[4251,4679,4680,4681,4683],{},"No. Restore only replaces your current draft. You still need to click ",[4255,4682,4660],{}," when you are ready for customers to see it. That gives you a chance to tweak the restored flow before shipping it.",[4398,4685,927],{"id":4686},"can-i-preview-a-version-before-restoring-it",[4251,4688,4689,4690,4692],{},"Not directly. The timeline shows each version's publish timestamp, and restoring itself is undoable — so the recommended workflow is to restore the version you are considering, inspect the draft in the builder, and press ",[4289,4691,4433],{}," if it was not what you wanted.",[4398,4694,932],{"id":4695},"does-undo-work-after-i-publish",[4251,4697,4698,4699,4701],{},"Publishing is not itself undone by ",[4289,4700,4433],{},". Undo and redo track edits to your draft. Once a version is published it lives in the timeline, and if you need to roll back, use Restore to bring an earlier published version into the draft and publish again.",[4398,4703,937],{"id":4704},"are-ab-test-variants-versioned-the-same-way",[4251,4706,939],{},[4398,4708,942],{"id":4709},"why-did-my-undo-history-disappear",[4251,4711,944],{},[4398,4713,947],{"id":4714},"what-happens-to-the-live-flow-while-i-am-editing",[4251,4716,949],{},{"title":422,"searchDepth":417,"depth":417,"links":4718},[4719,4720,4721,4725,4726,4727,4728],{"id":4265,"depth":374,"text":878},{"id":4284,"depth":374,"text":883},{"id":4394,"depth":374,"text":888,"children":4722},[4723,4724],{"id":4400,"depth":417,"text":893},{"id":4480,"depth":417,"text":898},{"id":4511,"depth":374,"text":903},{"id":4586,"depth":374,"text":908},{"id":4648,"depth":374,"text":913},{"id":4674,"depth":374,"text":918,"children":4729},[4730,4731,4732,4733,4734,4735],{"id":4677,"depth":417,"text":922},{"id":4686,"depth":417,"text":927},{"id":4695,"depth":417,"text":932},{"id":4704,"depth":417,"text":937},{"id":4709,"depth":417,"text":942},{"id":4714,"depth":417,"text":947},"Protect your work in the Cancel Flow builder with undo, redo, discard, and one-click restore of any previously published version.","md",{},true,{"title":44,"description":4736},{"loc":45},"KQyiv_ezZgtRTHqbzCPYdIoVcEAyccxC3wLNnxYmbIk",[4744,4745],{"title":40,"path":41,"stem":42,"description":817,"children":-1},{"title":48,"path":49,"stem":50,"description":952,"children":-1},1777057707087]