Skip to main content

Embedding the flow

How to integrate the cancellation flow with your application or service

Note

Version 2.0.0 of the flow library introduces some breaking changes. See the upgrade guide for information on upgrading from previous versions.

To pass cancellations through your ProsperStack cancellation flow, you'll need to integrate the ProsperStack JavaScript library with your application.

There are two options for including the ProsperStack JavaScript library in your application.

Install from npm

You can install the @prosperstack/flow package from npm.

$ npm install @prosperstack/flow

or, with yarn:

yarn add @prosperstack/flow

In your application, import the library:

import flow from "@prosperstack/flow";

Load via script tag

If you don't want to use npm, you can load it with a script tag.

<script src="https://cdn.prosperstack.com/flow/2.3.3/flow.min.js"></script>

If loaded via script tag, the library will be available at window.ProsperStack.flow.

Usage

Call the imported flow() method or window.ProsperStack.flow if loaded via script tag.

flow({
clientId: "acct_IrbFC8mvFtDAugC8k7uNlRn6",
subscription: {
platformId: "sub_H6X0WYrAzq2iGk",
},
});

or

window.ProsperStack.flow({
clientId: "acct_IrbFC8mvFtDAugC8k7uNlRn6",
subscription: {
platformId: "sub_H6X0WYrAzq2iGk",
},
});

Arguments

The flow() method accepts the following parameters:

The Payload object

  • clientId: string - (required) – Your unique client ID. You can find your client ID in your ProsperStack settings page.
  • flowId: string - (optional) - The unique ID of a cancellation flow to use. Bypasses your flow routing rules.

Depending on your subscription platform, the payload object requires additional properties.

Subscription platform integration
  • subscription: Subscription - (required) - Object containing details of the subscription being canceled.

    • platformId - (required) The ID of the subscription from your connected subscription platform that is being canceled.
    • properties - (optional) Object containing subscription custom properties.
  • subscriber: Subscriber - (optional) - Object containing details of the subscriber that is canceling.

Working with multiple flows

When using multiple flows, you only need to specify a clientId. Your flow route rules will determine which flow customers are routed to. If you want to bypass routing rules and send customers to a specific cancellation flow, you can specify the flowId in addition to the clientId.

Custom integration
  • subscriber: Subscriber - (required) - Object containing details of the subscriber that is canceling.

    • internalId: string | number - (required) - The internal ID in your application of the subscriber that is canceling.
    • name: string - (required) - Name of the subscriber that is canceling.
    • email: string - (required) - Email of the subscriber that is canceling.
    • properties - (optional) Object containing subscriber custom properties.
  • subscription?: Subscription - (optional) - Object containing details of the subscription that is being canceled.

    • mrr?: number - (optional) - MRR of the subscription that is being
      canceled.
    • currency?: string - (optional) - Three-letter ISO currency code (i.e. usd) that mrr is specified in.
    • trial?: boolean - (optional) - Designate that the subscription being canceled is a free trial.
    • properties - (optional) Object containing subscription custom properties.
Custom properties

Custom properties allow you to attach custom data to your subscriber and subscription objects for more powerful targeting and segmentation.

You'll need to create custom properties in ProsperStack before storing values in them. Learn more about managing custom properties.

Specify custom properties to attach to either a subscriber or subscription with the properties object. Use an object with the unique identifier of the property as the key, for example:

{
number_of_contacts: 5800,
is_professional: true,
last_contacted: "2021-05-04",
preferred_name: "Johnny"
}

Learn more about formatting values for custom properties.

Options

The Options object accepts the following properties:

  • onCompleted?: (result: FlowResult) => void — (optional)

    A callback that is called when the cancellation flow is completed. If not supplied, the method will return Promise<FlowResult>.

  • onClosed?: () => void — (optional)

    A callback that is called when the cancellation flow is closed.

  • onError?: (err: Error) => void — (optional)

    A callback that is called when an error occurs with the cancellation flow.

  • sign?: (payload: string) => string | Promise<string> — (optional)

    A callback that is called with the JSON-encoded flow payload to enable request signing. Expects a hex HMAC-SHA256 signature digest to be returned.

  • displayMode?: "full_screen" | "modal" — (optional)

    Launch the flow either full screen (default) or as a modal over the top of the current page. When presented as a modal, the flow layout changes slightly to accomodate the reduced dimensions, looking more like the mobile version. Requires @prosperstack/flow version 2.3.0.

When the flow is complete

To be notified when the cancellation flow is complete, the library can accept a callback or return a Promise.

NOTE: If using a confirmation step, the Promise or onCompleted callback may resolve before the flow is completely closed by the subscriber. See the confirmation step notes for more details.

The callback or Promise will provide a FlowResult object, which contains the following properties:

  • status: 'canceled' | 'saved' | 'deflected' | 'incomplete' - The result of the cancellation flow.
  • flowSession: FlowSession - The completed or aborted FlowSession object. This object contains details of the offers presented and accepted as well as all survey data for the cancellation flow session. For more details on the structure of the FlowSession object, see the API reference documentation for flow sessions.

The possible values of status are:

  • canceled - The customer chose to cancel their subscription.
  • saved - The customer accepted an offer and chose to keep their subscription.
  • deflected - The customer clicked the action button in a deflection card.
  • incomplete - The customer closed the cancellation flow without completing it.

Using a callback

flow(
{
clientId: "acct_IrbFC8mvFtDAugC8k7uNlRn6",
subscription: {
platformId: "sub_H6X0WYrAzq2iGk",
},
},
{
onCompleted: (result) => {
console.log("Flow complete!");
},
}
);

Using a Promise

flow({
clientId: "acct_IrbFC8mvFtDAugC8k7uNlRn6",
subscription: {
platformId: "sub_H6X0WYrAzq2iGk",
},
}).then((result) => {
console.log("Flow complete!");
});

Or with await:

const result = await flow({
clientId: "acct_IrbFC8mvFtDAugC8k7uNlRn6",
subscription: {
platformId: "sub_H6X0WYrAzq2iGk",
},
});

Showing a confirmation message after the flow

After a subscriber completes the flow, you can either use the status property of the flow result to display a confirmation message in your application, or you can add a confirmation step to have ProsperStack show a confirmation message for you.

If you use ProsperStack's confirmation step, note that the Promise (or onCompleted callback) will resolve after the subscriber cancels their subscription or accepts an offer, which occurs before the confirmation step is shown to the subscriber. If you have logic in your application that happens after the subscriber exits out of the cancellation flow (for example, redirecting to a different page in your application), we recommend using the onClosed callback to be notified when the subscriber closes the confirmation step and has completely exited out of the cancellation flow.

Complete examples

Using a subscription platform integration

<button id="cancel">Cancel subscription</button>

<script>
async function cancel() {
const result = await flow({
clientId: "acct_IrbFC8mvFtDAugC8k7uNlRn6",
subscription: {
platformId: "sub_H6X0WYrAzq2iGk",
properties: {
is_professional: true,
num_contacts: 5800,
},
},
subscriber: {
properties: {
preferred_name: "Johnny",
},
},
});

switch (result.status) {
case "canceled":
// Process the cancellation and whatever other logic your application
// requires when a customer cancels.
break;

case "saved":
// The customer accepted an offer and didn't cancel. Churn prevented!
// For most offers, no further action is needed.
console.log(result.flowSession);

// For custom offers, additional action is required.
if (
result.flowSession.offer_accepted.id ===
"offr_9bxR7jLsMvnA6NsoRjGlk5sh"
) {
// Whatever logic your application needs to process the custom offer.
}
break;

case "incomplete":
// No action needed!
break;
}
}

document.getElementById("cancel").addEventListener("click", cancel);
</script>

Using the custom integration

<button id="cancel">Cancel subscription</button>

<script>
async function cancel() {
const result = await flow({
clientId: "acct_IrbFC8mvFtDAugC8k7uNlRn6",
subscriber: {
internalId: "31245",
name: "Jane Smith",
email: "jane@example.com",
properties: {
preferred_name: "Jane",
},
},
subscription: {
mrr: 99.0,
trial: false,
properties: {
is_professional: true,
num_contacts: 5800,
},
},
});

switch (result.status) {
case "canceled":
// Process the cancellation and whatever other logic your application
// requires when a customer cancels.
cancelSubscription();
break;

case "saved":
// The customer accepted an offer and didn't cancel. Churn
// prevented!

// The details of the accepted offer will be present in the
// response payload.
const offerDetails = result.flowSession.offer_accepted.details;

// Metadata stored on the ProsperStack offer is returned as
// an object.
const offerMetadata = result.flowSession.offer_accepted.metadata;

// You can use the offer details to make the appropriate
// changes to the customer's subscription.
switch (offerDetails.type) {
case "coupon":
applyCoupon({
amountOff: offerDetails.amount_off,
});
break;

case "trial_extension":
extendTrial({
days: offerDetails.days,
});
break;

case "change_plan":
changePlan({
newPlanId: offerDetails.platform_plan_id,
});
break;

case "pause_subscription":
pauseSubscription({
length: offerDetails.interval_count,
});
break;

case "custom":
// Offer metadata can be used to pass custom data from
// a ProsperStack offer to your application.
const code = offerMetadata.code;
processCustomOffer(code);
break;
}
break;

case "incomplete":
// No action needed!
break;
}
}

document.getElementById("cancel").addEventListener("click", cancel);
</script>

Signing requests

In order to verify the authenticity of requests to begin a cancellation session, we recommend signing your requests with your account secret.

When calling the flow() method, you can either provide a callback function to the sign option property or supply a SignedPayload object in the payload parameter.

The sign callback

The sign callback is called with the JSON-encoded payload and expects a hex HMAC-SHA256 signature digest to be returned.

Here's an example of what calling a backend method to sign the request payload might look like using the sign property:

flow(
{
clientId: "acct_IrbFC8mvFtDAugC8k7uNlRn6",
subscription: {
platformId: "sub_H6X0WYrAzq2iGk",
},
},
{
sign: async (payload) => {
const response = await fetch("https://app.example.com/sign", {
body: {
payload,
},
});
const signature = await response.json();
return signature;
},
}
);

The SignedPayload object

Instead of using the sign option, you can also pass in a SignedPayload object to the flow() method.

The SignedPayload object should have the following properties:

  • payload: string - A string containing a JSON-encoded Payload object.
  • signature: string - The hex HMAC-SHA256 signature digest.

Here's an example of what calling the flow() method with a SignedPayload might look like:

flow({
payload:
'{"clientId":"acct_IrbFC8mvFtDAugC8k7uNlRn6","subscription":{"platformId":"sub_H6X0WYrAzq2iGk"}}',
signature: "c9efbfb8121c892b5de8c4e7c0c9182db7f3d87aa4ee463191e06fe36f4b00be",
});

Generating the signature

The payload signature is a hex HMAC-SHA256 digest computed from the JSON-encoded flow payload and your client secret.

Your client secret can be found in the Settings page of your ProsperStack dashboard under the Account section.

Computing the HMAC digest will look a little different depending on your server language, but the following is an example of what it might look like in Node.js:

import crypto from "crypto";

const payload = JSON.stringify({
clientId: "acct_IrbFC8mvFtDAugC8k7uNlRn6",
subscription: {
platformId: "sub_H6X0WYrAzq2iGk",
},
});

const secret = "my client secret";

const digest = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");

Creating the signature should take place on your backend server, so that the secret is not exposed to the client. Make sure not to do this in the browser!

Content Security Policy (CSP)

If your application uses a Content Security Policy, you'll need to add the following directives to your policy:

  • connect-srchttps://api.prosperstack.com
  • frame-srchttps://app.prosperstack.com

And if loading the library via a <script> tag from the CDN:

  • script-src - https://cdn.prosperstack.com

Flow changelog

2.3.0

  • Add flow displayMode option

2.2.1

  • Add handler for restarting the loading spinner for flow rerouting

2.2.0

  • Add sign property to flow options
  • Add support for new platform properties
  • Mark paymentProvider properties as @deprecated for removal in next major release

2.1.2

  • Hide loading spinner when flow initializes to prevent it showing through transparent backgrounds when using custom CSS
  • Add cancel_reason and question primary property to TypeScript type definitions

2.1.1

  • Add TypeScript type definitions for offer details and metadata

2.1.0

  • Add onError callback to receive any errors that occur during a flow
  • Properly export all TypeScript types from the root module

2.0.2

  • Fix TypeScript definition for payload subscriber object

2.0.1

BREAKING CHANGES:

  • Payload clientId is now required

OTHER CHANGES:

  • Payload flowId is now optional
  • Add onClosed callback to options to handle flow closed event

1.1.0

  • Improve error handling and add better user-facing error messages
  • Add support for properties in the subscriber and subscription objects

1.0.1

  • Add deflected to flow session result status

1.0.0

  • Initial release

Upgrade guides

2.0.0

In order to support the new multiple flows feature, version 2.0.0 now requires you to specify your clientId in the payload. You can continue to specify a flowId to direct customers to a specific flow, or omit it to route to your default flow.

You can find your client ID in the Settings screen under the Account section.

Version 2.0.0 also changes the secret key used for signed requests — you should now use the client secret found in the Account section in the Settings page to compute signatures.

Version 2.0.0 also adds an onClosed callback option so you can be notified when the cancellation flow is completely closed. This may be useful when using the new confirmation step feature.

Previous versions of the flow library (< 2.0.0) will continue to function as normal, so you'll only need to make these changes when you upgrade to 2.0.0.