Entrance API
One endpoint mints a hosted cancel-flow link at the moment a customer tries to cancel. Call it from your server, then send the customer to the URL it returns.
Overview
The entrance API does not read or change anything in Stripe by itself. It takes a customer and a subscription, resolves the offer that customer should see, records the presentation, and returns a short-lived URL to the hosted cancel flow. Your app decides when to call it and where to send the browser.
If you have not connected Stripe or set up cancel flows yet, start with the setup guide.
Authentication
Every request carries your entrance key as a bearer token. Generate it in Settings → Cancel Flows. The key is shown once, so store it in your server environment and never in the browser.
Authorization: Bearer <your entrance key>Server only
The endpoint
POST /api/cancel-flow/entranceSend JSON with the two Stripe identifiers. The customer is whoever is trying to cancel, and the subscription is the one you would otherwise cancel.
Request body
| Field | Type | Description |
|---|---|---|
customerId | string | The Stripe customer id, for example cus_123. |
subscriptionId | string | The Stripe subscription id, for example sub_123. |
GET variant
The same input works as query parameters, and responds with a 307 redirect straight to the hosted flow. This is handy when your server wants to redirect the customer without handling the URL itself.
GET /api/cancel-flow/entrance?customerId=cus_123&subscriptionId=sub_123Response
A successful POST returns the hosted flow URL plus the offer that was chosen, so you can log or display it if you want to.
{
"ok": true,
"url": "https://app.churnguardian.com/cancel/eyJvcmdJZCI6...",
"combo": { "discountPct": 30, "durationMonths": 3 }
}Send the customer to url. The link is tied to that customer and subscription, needs no login, and expires after 7 days.
Errors
| Status | When it happens | What to do |
|---|---|---|
| 401 | The bearer key is missing or does not match an account, or Stripe is not connected. | Check the key and confirm Stripe is connected. |
| 400 | customerId or subscriptionId is missing. | Send both fields as non-empty strings. |
| 400 | A flow could not be created for that subscription. | Confirm the subscription exists on the connected Stripe account. |
Examples
curl
curl -X POST https://app.churnguardian.com/api/cancel-flow/entrance \
-H "Authorization: Bearer $CHURNGUARDIAN_ENTRANCE_KEY" \
-H "Content-Type: application/json" \
-d '{"customerId":"cus_123","subscriptionId":"sub_123"}'Node
const res = await fetch(
`${process.env.CHURNGUARDIAN_URL}/api/cancel-flow/entrance`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHURNGUARDIAN_ENTRANCE_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ customerId, subscriptionId })
}
);
const { url } = await res.json();
// Redirect the customer to the hosted flow.
redirect(url);Notes
- A customer is assigned to the adaptive or control arm the first time they appear, and stays there, so the comparison stays honest.
- Calling the endpoint records an offer presentation, which is what the cancel-flow dashboard counts.
- The hosted page is self-contained. You do not need to render anything on your side beyond the redirect.
