Cancel Sessions
Cancel session API endpoints — create, query, advance, and complete cancel flow sessions at the edge.
Cancel flow sessions are managed via the Cloudflare edge worker. These endpoints create and drive cancel flow sessions stored in KV, with final outcomes persisted to SpacetimeDB on completion.
For background on how sessions work, see Building Flows > Session Management at the Edge.
Base URL
Cancel session endpoints use the edge worker base URL:
https://edge.lostchurn.com/api/cancel-sessionStart a Session
Create a new cancel flow session for a subscriber.
POST /api/cancel-session/startRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
merchant_id | string | Yes | Your merchant UUID |
customer_id | string | Yes | The customer's UUID |
subscription_id | string | Yes | The subscription being canceled |
Example
curl -X POST https://edge.lostchurn.com/api/cancel-session/start \
-H "Content-Type: application/json" \
-d '{
"merchant_id": "m_abc123",
"customer_id": "cus_def456",
"subscription_id": "sub_ghi789"
}'Response
{
"token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"session": {
"sessionId": "s_xyz789",
"cancelFlowId": "cf_abc123",
"merchantId": "m_abc123",
"customerId": "cus_def456",
"subscriptionId": "sub_ghi789",
"currentStep": 1,
"status": "in_progress",
"responses": [],
"acceptedOffer": null,
"startedAt": "2026-03-13T10:00:00.000Z"
}
}The token is a UUID that serves as both the session identifier and access credential. Store it securely -- it is the only way to interact with this session.
The worker looks up the merchant's active cancel flow from SpacetimeDB when creating the session. If no active cancel flow is found for the merchant, the request returns a 404 error.
Get Session State
Retrieve the current session state and the configuration for the current step.
GET /api/cancel-session/:tokenPath Parameters
| Parameter | Type | Description |
|---|---|---|
token | string | The session token returned by the start endpoint |
Example
curl https://edge.lostchurn.com/api/cancel-session/a1b2c3d4-e5f6-7890-abcd-ef1234567890Response
{
"session": {
"sessionId": "s_xyz789",
"cancelFlowId": "cf_abc123",
"merchantId": "m_abc123",
"customerId": "cus_def456",
"subscriptionId": "sub_ghi789",
"currentStep": 1,
"status": "in_progress",
"responses": [],
"acceptedOffer": null,
"startedAt": "2026-03-13T10:00:00.000Z"
},
"step": {
"stepType": "survey",
"configJson": "{\"question\": \"Why are you canceling?\", \"options\": [...]}"
}
}The step object contains the step type and its JSON configuration, loaded from the cancel_flow_step table in SpacetimeDB. If the step configuration cannot be loaded, the step field is null but the session is still returned.
Error Responses
| Status | Meaning |
|---|---|
404 | Session not found or expired (sessions expire after 1 hour of inactivity) |
Advance to Next Step
Record the subscriber's response to the current step and advance to the next one.
POST /api/cancel-session/:token/advancePath Parameters
| Parameter | Type | Description |
|---|---|---|
token | string | The session token |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
response_json | string | No | JSON-encoded response data from the current step |
step_type | string | No | The type of step being completed (e.g., survey, offer) |
Example
curl -X POST https://edge.lostchurn.com/api/cancel-session/a1b2c3d4-e5f6-7890-abcd-ef1234567890/advance \
-H "Content-Type: application/json" \
-d '{
"response_json": "{\"reason\": \"too_expensive\"}",
"step_type": "survey"
}'Response
{
"session": {
"sessionId": "s_xyz789",
"currentStep": 2,
"status": "in_progress",
"responses": [
{
"stepType": "survey",
"responseJson": "{\"reason\": \"too_expensive\"}",
"acceptedOffer": null
}
]
}
}Each advance appends the response to the session's responses array and increments currentStep. If the response_json contains an accepted_offer field, it is extracted and stored on the session.
Error Responses
| Status | Meaning |
|---|---|
404 | Session not found or expired |
400 | Session has already been completed |
Complete Session
Finalize the session with an outcome. This persists the result to SpacetimeDB and deletes the KV session.
POST /api/cancel-session/:token/completePath Parameters
| Parameter | Type | Description |
|---|---|---|
token | string | The session token |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
outcome | string | Yes | Either saved (subscriber retained) or churned (subscriber canceled) |
reason | string | Yes | Primary cancellation reason (e.g., too_expensive, missing_features) |
sub_reason | string | No | Secondary reason for more granular reporting |
free_text | string | No | Free-text feedback from the subscriber |
Example
curl -X POST https://edge.lostchurn.com/api/cancel-session/a1b2c3d4-e5f6-7890-abcd-ef1234567890/complete \
-H "Content-Type: application/json" \
-d '{
"outcome": "churned",
"reason": "too_expensive",
"sub_reason": "budget_cuts",
"free_text": "Love the product but we had to cut costs."
}'Response
{
"completed": true,
"outcome": "churned",
"session_id": "s_xyz789"
}What Happens on Completion
- The session status is updated to the specified outcome (
savedorchurned) - The worker calls the
complete_cancel_session_from_kvreducer in SpacetimeDB, which:- Inserts a
cancel_feedbackrow with the full response history - Emits a
recovery_event(eitherCancelFlowCompletedorCancelFlowSaved) - Updates subscription status if the outcome is
churned
- Inserts a
- The KV session record is deleted
Once a session is completed, the token is invalidated and cannot be used again. If the SpacetimeDB persistence fails, the session is still consumed from KV -- the completion is logged for retry via a dead-letter mechanism.
Error Responses
| Status | Meaning |
|---|---|
400 | Missing required fields (outcome, reason) or invalid outcome value |
404 | Session not found or expired |
Session Lifecycle Summary
POST /start → token issued, session created in KV (status: in_progress)
GET /:token → read session state + current step config
POST /:token/advance → record response, move to next step
POST /:token/complete → persist outcome to DB, delete KV record
(or session auto-expires after 1 hour)Next Steps
- Building Flows -- visual flow builder and session architecture
- Measuring Saves -- analyze cancel flow performance
- Webhooks -- receive
customer.subscription_canceledandcustomer.retainedevents