Back to home

Leavely Documentation

Leavely helps you understand why users cancel your product. When someone clicks your cancel button, Leavely opens a short feedback modal and captures the reason before the cancellation happens.

That feedback is then turned into insights in /stats so you can understand churn, spot patterns, and improve your product.

Most integrations take less than 5 minutes.

Quickstart

Follow these steps to connect Leavely to your cancel flow.

  1. Create a project in /projects and copy the projectKey.
  2. Add your website domain in Allowed domains. This helps prevent other websites from sending data to your project.
  3. Choose one integration method below:
    • No-code link for Webflow, Framer, or Shopify
    • HTML snippet
    • React / Next.js
    • Vue
    • Angular
    • Stripe cancellation flow
  4. Test your cancellation flow: click your cancel button, submit feedback, then verify the result in /stats.

Core concepts

These are the main concepts you need to understand before integrating Leavely.

  • projectKey (public): identifies your project and is safe to use in browser code and frontend snippets.
  • projectSecret (private): shown once in /projects and must stay on your server. Never expose it in frontend code.
  • Allowed domains: restricts which websites can send uninstall feedback to your project.
  • Cancel flow: when a user clicks your cancel button, Leavely opens a short feedback modal before the cancellation is completed.
  • Browser endpoint: /api/leavely/uninstall receives feedback submitted from the modal.
  • No-code redirect: next= controls where users are sent after the modal flow is finished.

Integration options

All integrations follow the same flow:

  • The user clicks your cancel button
  • Leavely opens a feedback modal
  • The feedback is sent to your project
  • Your normal cancellation flow continues

1) No-code link (Webflow, Framer, Shopify)

This is the easiest option if you do not want to write custom code. Replace the values with your own. projectKey identifies the project, and next defines where the user should go after the flow is complete.

https://your-domain.com/cancel?projectKey=pk_live_xxxxxxxxx&next=https%3A%2F%2Fyour-domain.com%2Faccount

2) HTML button integration

Use this if you already have a cancel button on a regular website. The CANCEL_SELECTOR value lets you connect Leavely to your existing button.

<!-- 1) Load Leavely SDK -->
<script src="/sdk/leavely.js"></script>

<!-- 2) Add a cancel button -->
<button id="cancel-subscription" type="button">Cancel subscription</button>

<!-- 3) Open Leavely on click -->
<script>
  (function () {
    var PROJECT_KEY = "pk_live_xxxxxxxxx";
    var CANCEL_SELECTOR = "#cancel-subscription";

    var btn = document.querySelector(CANCEL_SELECTOR);
    if (!btn) return;

    btn.addEventListener("click", function (e) {
      e.preventDefault();
      if (!window.leavely || typeof window.leavely.open !== "function") return;

      window.leavely.open({
        projectKey: PROJECT_KEY,
        endpoint: "/api/leavely/uninstall"
      });
    });
  })();
</script>

3) React / Next.js (App Router)

Load the SDK with next/script using strategy="afterInteractive". Frontend code should only use projectKey.

"use client";

import { useState } from "react";
import Script from "next/script";

declare global {
  interface Window {
    leavely?: {
      open: (args: {
        projectKey: string;
        endpoint?: string;
      }) => Promise<unknown>;
    };
  }
}

export default function CancelButton() {
  const [ready, setReady] = useState(false);

  async function onCancel() {
    if (!window.leavely || typeof window.leavely.open !== "function") return;

    await window.leavely.open({
      projectKey: "pk_live_xxxxxxxxx",
      endpoint: "/api/leavely/uninstall",
    });
  }

  return (
    <>
      <Script
        src="/sdk/leavely.js"
        strategy="afterInteractive"
        onLoad={() => setReady(true)}
      />
      <button type="button" onClick={onCancel} disabled={!ready}>
        Cancel subscription
      </button>
    </>
  );
}

4) Vue

Load the SDK dynamically in onMounted, then call window.leavely.open() from your cancel action.

<script setup lang="ts">
import { onMounted, ref } from "vue";

const ready = ref(false);

function loadLeavelyScript(src = "/sdk/leavely.js"): Promise<void> {
  return new Promise((resolve, reject) => {
    if (typeof window === "undefined") return resolve();

    const already = document.querySelector('script[data-leavely="1"]');
    if (already) return resolve();

    const s = document.createElement("script");
    s.src = src;
    s.async = true;
    s.dataset.leavely = "1";
    s.onload = () => resolve();
    s.onerror = () => reject(new Error("Failed to load Leavely SDK"));
    document.head.appendChild(s);
  });
}

onMounted(async () => {
  await loadLeavelyScript();
  ready.value = !!window.leavely && typeof window.leavely.open === "function";
});

async function onCancel() {
  if (!window.leavely || typeof window.leavely.open !== "function") return;

  await window.leavely.open({
    projectKey: "pk_live_xxxxxxxxx",
    endpoint: "/api/leavely/uninstall",
  });
}
</script>

<template>
  <button type="button" :disabled="!ready" @click="onCancel">
    Cancel subscription
  </button>
</template>

5) Angular

Load the SDK in AfterViewInit, then open the modal when the user clicks your cancel button.

// cancel-subscription.component.ts
import { AfterViewInit, Component } from "@angular/core";

@Component({
  selector: "app-cancel-subscription",
  template: '<button type="button" (click)="onCancel()" [disabled]="!ready">Cancel subscription</button>',
})
export class CancelSubscriptionComponent implements AfterViewInit {
  ready = false;

  async ngAfterViewInit(): Promise<void> {
    await this.loadLeavelyScript();
    this.ready = !!window.leavely && typeof window.leavely.open === "function";
  }

  private loadLeavelyScript(src = "/sdk/leavely.js"): Promise<void> {
    return new Promise((resolve, reject) => {
      const already = document.querySelector('script[data-leavely="1"]');
      if (already) return resolve();

      const s = document.createElement("script");
      s.src = src;
      s.async = true;
      s.dataset.leavely = "1";
      s.onload = () => resolve();
      s.onerror = () => reject(new Error("Failed to load Leavely SDK"));
      document.head.appendChild(s);
    });
  }

  async onCancel(): Promise<void> {
    if (!window.leavely || typeof window.leavely.open !== "function") return;

    await window.leavely.open({
      projectKey: "pk_live_xxxxxxxxx",
      endpoint: "/api/leavely/uninstall",
    });
  }
}

6) Stripe integration

If your product uses Stripe subscriptions, the usual flow is:

  • Open the Leavely feedback modal
  • Then cancel the subscription on your backend

This lets you capture the cancellation reason before the subscription is actually canceled.

Simple: open Leavely and cancel immediately.

async function onCancel() {
  // 1) Open Leavely feedback (non-blocking)
  if (window.leavely && typeof window.leavely.open === "function") {
    void window.leavely.open({
      projectKey: "pk_live_xxxxxxxxx",
      endpoint: "/api/leavely/uninstall",
    });
  }

  // 2) Cancel subscription on your backend
  await fetch("/api/stripe/cancel", { method: "POST" });
}

Advanced: cancel only after feedback is submitted.

async function onCancel() {
  if (!window.leavely || typeof window.leavely.open !== "function") {
    await fetch("/api/stripe/cancel", { method: "POST" });
    return;
  }

  const result = await window.leavely.open({
    projectKey: "pk_live_xxxxxxxxx",
    endpoint: "/api/leavely/uninstall",
  });

  if (result && typeof result === "object" && (result as any).status === "submitted") {
    await fetch("/api/stripe/cancel", { method: "POST" });
  }
}

Verified MRR impact requires connecting Stripe via /api/stripe/connect. Revenue data is queried from /api/stats/revenue and matched to uninstall reasons on a best-effort basis.

Viewing results

Once users start canceling, you can view the insights directly in your dashboard.

Open /stats to explore the collected feedback.

The dashboard helps you understand why users cancel by showing:

  • Most common cancellation reasons
  • Trends over time
  • Country and device signals
  • Recent feedback messages
  • Estimated churn revenue impact (if Stripe is connected)

Security & privacy

Leavely is designed so you can safely use it in frontend code while keeping sensitive data private.

  • Browser snippets should use only projectKey, never projectSecret.
  • Enable Allowed domains to reduce unauthorized browser usage.
  • Google Analytics is loaded only after consent in this app.

Troubleshooting

If something is not working as expected, check the following first.

  • Modal does not open: make sure the SDK is loaded from /sdk/leavely.js.
  • 401 on dashboard APIs: make sure you are authenticated.
  • 402 billing required: upgrade your plan before using gated features.
  • Domain error (403): add your site host to Allowed domains for the project.
  • Wrong redirect in no-code flow: verify the next= URL is properly encoded.