Docs

RetentBase API integration guide

End-to-end setup instructions for Hosted Flow and Advanced API integration.

Before you start

Public docs intentionally avoid workspace-specific secrets and identifiers. Create a workspace first, then use /dashboard/docs to access your actual workspaceKey and Signing Secret.

Hosted helper API POST /api/v1/cancel-link is available on Core and Advanced. Session diagnostics (GET /api/v1/cancel-sessions/:id) and custom cancellation ingestion/update APIs require Advanced.

Choose integration model

Call the RetentBase API at https://api.retentbase.com. If you use the hosted cancellation flow, redirect customers to the cancel host at https://cancel.retentbase.com.

Plan model: Core = Hosted flow only. Advanced = Hosted flow or Custom API + advanced analytics.

Advanced API: complete reference

Use this integration when your product owns the cancellation UX and RetentBase is your analytics and outcome ledger.

Keep this flow standalone: do not pass hosted session identifiers into the custom API.

Canonical request schema

Use eventId, reasonKey, optional outcome, an optionaloffer object, optional context, and optional metadata.

Use the same eventId on retries to keep ingestion idempotent.

POST /api/v1/cancellations (recommended first call)

await fetch("https://api.retentbase.com/api/v1/cancellations", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer " + process.env.RETENTBASE_API_KEY,
  },
  body: JSON.stringify({
    environment: "sandbox",
    eventId: "cancel_evt_8f30a7bf",
    userId: "user_456",
    reasonKey: "too_expensive",
    outcome: "churned", // omit to leave outcome pending for PATCH
    offer: {
      type: "discount",
      accepted: true,
      label: "20% off next month",
    },
    reasonText: "Needs lower monthly price",
    context: {
      plan: "pro",
      mrr: 129,
      tenureDays: 42,
    },
    occurredAt: new Date().toISOString(),
    metadata: {
      source: "billing_settings_page",
    },
  }),
});

PATCH /api/v1/cancellations/{eventId}

await fetch("https://api.retentbase.com/api/v1/cancellations/cancel_evt_8f30a7bf", {
  method: "PATCH",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer " + process.env.RETENTBASE_API_KEY,
  },
  body: JSON.stringify({
    environment: "sandbox",
    outcome: "recovered",
    effectiveAt: new Date().toISOString(),
    metadata: {
      actor: "success_playbook",
      channel: "cs_call",
    },
  }),
});
POST/api/v1/cancellations

Creates or reuses a cancellation event (idempotent by environment + eventId).

Notes

  • eventId is your idempotency key. Reuse it for safe retries.
  • Keep environment consistent if you plan to PATCH the event later.

Availability

Advanced plan only

Auth

Authorization: Bearer <Workspace API Key> or x-api-key

Content-Type

application/json

Idempotency

Reuse the same eventId for safe retries.

JSON Body

FieldTypeRequiredDescription
environment"prod" | "sandbox"NoEnvironment for the event. Defaults to prod.
eventIdstringYesStable idempotency key from your system.
userIdstringNoOptional external customer identifier from your system.
reasonKeystringYesMust match an enabled reason key in the workspace.
outcome"churned" | "recovered" | "abandoned"NoOptional final outcome. Omit to create a pending event and update later.
offer.type"none" | "discount" | "pause"NoOffer shown to the customer. Defaults to none when omitted.
offer.acceptedbooleanNoWhether the customer accepted the offer. Defaults to false.
offer.labelstringNoOptional offer label for analytics.
reasonTextstringNoOptional free-text customer note.
context.planstringNoOptional customer plan label.
context.mrrnumberNoOptional monthly recurring revenue used for revenue cohorts.
context.tenureDaysintegerNoOptional customer tenure in days used for lifecycle cohorts.
occurredAtstring(ISO datetime)NoOptional event timestamp. Defaults to current time.
metadataobjectNoOptional flat object (string/number/boolean/null values).

Success Responses

StatusMeaning
201Event created successfully.
200Existing event returned (idempotent replay).

Error Responses

StatusMeaning
400Validation failed (missing/invalid fields).
401Missing or invalid API key.
402Workspace is read-only due to expired subscription access.
403Advanced plan required or demo workspace blocked.
415Content-Type must be application/json.
429Rate limit exceeded.
PATCH/api/v1/cancellations/{eventId}

Updates final business outcome for an existing cancellation event.

Notes

  • Use the same environment as the original POST request.
  • PATCH only sets the final billing outcome; it does not change the reason or offer data.

Availability

Advanced plan only

Auth

Authorization: Bearer <Workspace API Key> or x-api-key

Content-Type

application/json

Path Parameters

FieldTypeRequiredDescription
eventIdstringYesThe eventId from the original POST request.

JSON Body

FieldTypeRequiredDescription
environment"prod" | "sandbox"NoEnvironment for the update.
outcome"churned" | "recovered"YesFinal billing outcome.
effectiveAtstring(ISO datetime)NoOptional timestamp for the effective outcome.
metadataobjectNoOptional flat object recorded with the outcome update.

Success Responses

StatusMeaning
200Outcome updated.

Error Responses

StatusMeaning
400Invalid body or path parameter.
401Missing or invalid API key.
403Advanced plan required or demo workspace blocked.
404Event not found in selected environment.
409Event exists but in the other environment.
415Content-Type must be application/json.
429Rate limit exceeded.

Implementation checklist

  • Store workspace API keys and any result-token verification helpers only in server-side code.
  • Use stable `eventId` values to guarantee idempotent retries.
  • Use sandbox mode (`environment: "sandbox"`) before enabling production traffic.
  • Sandbox traffic stays isolated from retention health, weekly issue detection, and scheduled report emails.
  • Set Stripe webhook to `POST /api/stripe/webhook` and include `checkout.session.completed`, `customer.subscription.*`, `invoice.paid`, and `invoice.payment_failed`.
  • Use the dashboard billing surface for checkout and Stripe portal access instead of hard-coding plan changes.
  • Monitor `GET /api/health?deep=1` (authorized) for Stripe, notifications, and webhook pipeline health.
  • Log non-2xx responses with status + body to speed up integration debugging.