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.