Developer documentation
Connect customer data to Churnor
Set up a product environment, send activity, connect billing signals, and understand recorded risk assessments.
Overview
Churnor monitors SaaS customer risk using customer activity and subscription signals. Bring in data through the Events API, CSV imports, or supported billing webhooks. Review scores, explanations, and recorded activity in the dashboard.
Setup & quickstart
- Save your API key. Sign in, choose a product environment and create a key under Integrations. Keep
CHURNOR_API_KEYandCHURNOR_API_BASE_URL=https://churnor.vercel.appin server-only environment variables. - Send customer updates and activity. Download the TypeScript server helper. Call it after signup, profile changes, and successful product actions have been saved. Use your existing customer and record IDs.
- Check the connection. The connection check requires real activity received under the current key and a saved score. Key creation, customer-only updates, and dashboard tests do not verify the connection.
Your app sends updates when customers use it. Churnor does not scan your database.
import { createChurnor } from "./churnor";
const churnor = createChurnor();
// After signup or a profile update has been saved:
const customerResult = await churnor.upsertCustomer({
customer_id: savedUser.id,
email: savedUser.email,
name: savedUser.name,
plan: savedUser.plan,
created_at: savedUser.createdAt,
});
// After a product action has been saved:
const eventResult = await churnor.trackEvent({
customer_id: savedAction.userId,
event_id: savedAction.id,
event: "report_exported",
timestamp: savedAction.createdAt,
});
// Keep failed updates for retry, or pass them to your queue.
// .stored says whether data arrived; .score_saved says whether its score was saved.
// .retryable says whether retrying can help. Do not log the key or customer data.
if (!customerResult.ok || !eventResult.ok) {
// Resend the same saved records after fixing the reported problem.
}The helper makes up to three attempts with a ten-second timeout, increasing delays, and the same event ID, timestamp, and payload. It retries network failures, HTTP 429, and server errors. Longer waits return a retryable failure so your app can redeliver through its queue.
Data flow
Events are authenticated, validated, associated with the active product environment, and stored. Event storage and score completion are reported separately. A successful response can have score_saved=false; retry the same saved update to repair its score. Billing webhooks verify provider signatures and process supported subscription or payment changes.
Authorized dashboard reads refresh missing assessments and assessments older than 24 hours. Opening a customer also updates a score saved under earlier scoring rules. There is no scheduled background inactivity scan while the application is unused.
API authentication
Send Authorization: Bearer YOUR_API_KEY and Content-Type: application/json to the customer and event APIs. Keys belong to an environment. Never put a secret key in browser code or a public repository.
The Events API uses Bearer authentication, not an X-Churnor-Signature HMAC header. Provider billing endpoints instead verify their own signed deliveries against the saved signing secret.
Ingest events
POST /api/v1/events
customer_idoruser_id: a nonempty external customer identifier. When both are provided, customer_id takes precedence.event: a nonempty name of up to 100 characters.timestamp: optional parseable date string; omitted timestamps use the current time.properties: optional object of event context.idempotency_keyorevent_id: optional stable delivery identifier, with idempotency_key taking precedence.
Use the same delivery identifier on retry to avoid another stored copy. A minimal customer record is created when the external customer ID is new. Successful responses include received, deduplicated, customer_id, user_id, and the event name; event_id may identify the stored record.
Batch processing
POST /api/v1/events/batch takes an events array containing 1–1,000 items. Each uses the single-event fields. Supply original timestamps when backfilling activity.
{
"events": [
{
"customer_id": "example_customer",
"event": "report_exported",
"idempotency_key": "example_delivery_1",
"properties": {
"format": "csv"
}
}
]
}The response reports received, inserted, and deduplicated counts. Use stable delivery identifiers and inspect the response before retrying a batch.
Customer records
POST /api/v1/customers creates or updates a record by external customer ID. Provide customer_id or user_id. Optional fields include email, name, plan, nonnegative mrr, status, properties, and created_at.
{
"customer_id": "example_customer",
"name": "Example account",
"plan": "example_plan",
"mrr": 25
}PATCH /api/v1/customers/<external_customer_id> updates supplied email, name, plan, mrr, status, or properties fields for an existing customer. Use the external ID, URL-encoded as a path segment, rather than Churnor's internal UUID.
Review customer risk profiles and history in the authenticated dashboard. These customer routes do not provide a public GET scoring API.
Risk scoring
Scores use deterministic rules and saved evidence. Normal activity is based on this customer’s recorded use. Churnor needs at least four earlier active days spanning 28 days to estimate usual gaps between visits. Missing history alone does not raise risk.
Customers normally active more than seven days apart use a 28-day activity window against the preceding 56 days, with full 84-day coverage required. More frequent users keep the 14-day comparison. Unequal windows are compared as activity rates.
Confidence is Low, Medium, or High according to independent product, support, and billing evidence. Several usage signals count as one evidence family. Confidence describes available evidence and is not a probability of churn.
Signals & weights
Inactivity is considered only with sufficient history and a gap longer than twice the usual activity gap (and longer than seven days). The existing inactivity weights are 10, 25, 40, or 50 points for 7, 14, 30, or 60 days. Activity declines of 20%, 40%, or 70% add 8, 20, or 35 points when a baseline exists. Stopped core features add 15 points with sufficient history. Repeated unresolved support problems add at most 15 points in total.
Scheduled cancellation or cancellation adds 35 points; failed payment or past-due state adds 25; a downgrade adds 15. Recent product activity can reduce the score by 10 or 15 points. The result stays between 0 and 100. Billing, support, and outcome events do not count as product activity. Set is_product_activity:false for other administrative events, and is_core:true for important product actions. A default event-name match also identifies common core workflows.
Repeated support problems
Send support_reported through the existing Events API with a stable event ID, actual timestamp, and properties containing report_id, issue_id, category, and status (open or resolved). Use the same issue ID only for the same underlying problem. Reports are deduplicated by report ID; categories alone do not merge unrelated problems.
{
"customer_id": "your-existing-customer-id",
"event_id": "your-saved-report-id",
"event": "support_reported",
"timestamp": "2026-10-01T12:00:00Z",
"properties": {
"report_id": "your-saved-report-id",
"issue_id": "your-existing-issue-id",
"category": "reporting_data",
"status": "open"
}
}Categories: billing_confusion, integration_failure, product_bug, missing_capability, onboarding_difficulty, performance_reliability, reporting_data, authentication_access. Send support_resolved with the issue ID and actual resolution timestamp. Three distinct reports of the same still-open problem within 21 days qualify. Resolution clears earlier reports; reopening requires new reports. Send tags and identifiers, rather than ticket text or attachments.
Outcomes & validation
Send customer_churned only after service actually ends, or customer_active_confirmed after your app confirms service remains active. Supply a stable event ID, the actual outcome timestamp, and properties.source_id. Optional fields are outcome_source and cancellation_reason (up to 2,000 characters). These events use the same authentication, project limits, and retries as other activity.
{
"customer_id": "your-existing-customer-id",
"event_id": "your-saved-outcome-id",
"event": "customer_churned",
"timestamp": "2026-10-02T12:00:00Z",
"properties": {
"source_id": "your-saved-outcome-id",
"outcome_source": "application",
"cancellation_reason": "Customer-provided reason, if available"
}
}Validation compares high-risk checks (71 or above) with confirmed outcomes over 30 days. It uses checks recorded before the outcome, never a current score reconstructed after cancellation. Older checks without a reliable recording timestamp remain visible in history but are excluded from validation. A scheduled cancellation, missing event, or old status alone cannot confirm churn or retention.
Open Dashboard → Validation for counts and warning time, or Customer Detail for the saved warning beside the reported outcome. Counts cover the last 120 days and one evaluation per customer. Small samples and incomplete follow-up cannot establish prediction accuracy. Possible false alarms may include customers helped by an intervention. Stripe’s ended_at and Paddle’s confirmed canceled_at preserve subscription end dates. A subscription ending does not prove the whole customer account left; send customer_churned after your app confirms service ended. Dodo outcomes also require an explicit app-reported end date when the provider payload does not establish one. Historical scores retain their saved rules and evidence.
What the Validation results mean
- Warned before the customer left: at least one high-risk score was saved before confirmed service ended, within the preceding 30 days.
- No high-risk warning before leaving: eligible saved checks exist in those 30 days, but none reached 71.
- High risk, then confirmed active: the app explicitly confirmed the customer remained active at least 30 days after the warning. This is a possible false alarm; an intervention may also have helped the customer stay.
- Waiting for the 30-day follow-up: the warning is less than 30 days old and no confirmed end is recorded.
- No eligible checks before leaving: service ended, but Churnor lacks a check saved before that end in the comparison window.
- Outcome unknown: the app has not supplied enough confirmed follow-up. No cancellation event alone does not prove the customer stayed active.
Simple examples
A high-risk score saved on September 20 followed by confirmed service ending on October 2 gives 12 days of warning. A score sent afterward with an earlier date cannot count as that warning.
A warning on August 20 followed by an explicit active confirmation on September 20 can count as high risk, then confirmed active. Without that confirmation, the outcome stays unknown.
Limits of these results
Average warning time uses only customers warned before leaving. Missing history and unknown outcomes never count as correct or incorrect predictions. The view uses one evaluation per customer and the latest confirmed customer end in the last 120 days. A scheduled cancellation is intent, not an actual end. A provider-confirmed subscription ending does not establish that the whole customer account left.
Older checks without a reliable recording date stay visible in Risk history but cannot prove a warning happened before an outcome. The dashboard reports counts rather than an accuracy percentage. Fewer than 30 eligible churned and 30 eligible retained customers cannot support a meaningful percentage; larger samples still need careful follow-up and do not guarantee future performance. Saved warning reasons and reported cancellation reasons can be compared, but they do not prove what caused the customer to leave. Churnor does not automatically change scoring weights from these results.
Risk tiers
- Healthy: 0–40.
- At Risk: 41–70.
- High Risk: 71–100.
Inspect reasons and activity before choosing an intervention. These tiers do not promise a retention outcome or a cancellation timeline.
Custom event delivery
Send events from your application to the authenticated Events API. Keep retries and error handling in your backend. Churnor does not automatically deliver outbound risk alerts to Slack, a CRM, or a custom callback.
For billing provider delivery, use the provider-specific endpoints and signing secrets in the guides below. Product API keys and provider signing secrets serve different purposes.
CSV import
Open Dashboard → Settings → Import CSV. Map a column to customer_id or user_id. Optional mapped fields include email, name, plan, mrr, and last_active.
Use stable identifiers that match your API and billing data. Imported last-active values provide activity context; arbitrary historical event streams require the Events API. Review imported and failed-row counts, errors, and any reported assessment failures instead of assuming every row succeeded.
Stripe customer data
Connect your SaaS customers' subscription and payment events to Churnor with signed Stripe webhooks. You need a Churnor product environment and access to your Stripe account's webhook settings. This connector receives customer billing signals; it is separate from paying for a Churnor plan.
- Open Dashboard → Integrations → Stripe and copy the environment's endpoint URL.
- In Stripe Workbench → Webhooks, create an event destination for your account. Choose a webhook endpoint, use snapshot events, enter the copied URL, and select the event types below.
- Reveal the destination's signing secret and save it in Churnor. Use the same test/sandbox or live environment for the endpoint and secret.
- Deliver or replay a supported signed event. Select Check delivery / Retry verification in Churnor. Saving a secret leaves the connector awaiting its first event; successful processing verifies delivery.
Endpoint: /api/webhooks/stripe?project_id=<YOUR_PROJECT_ID>
Subscription and payment events to select
customer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.payment_failedinvoice.payment_succeeded
Match billing to customer activity
Use the Stripe customer ID (cus_…) as customer_id in Events API requests and CSV imports for this environment. The connector uses that Stripe ID as the customer's external ID. It does not map an application ID from churnor_customer_id or merge customers by email.
What arrives, and what does not
Supported deliveries update available customer details, subscription signals, and recurring revenue when the payload provides it. Billing deliveries are not counted as product activity. Send product events separately to measure usage.
Registering an endpoint receives future changes; it does not fetch existing subscriptions or backfill historical activity. Use CSV for initial customer records. Recurring revenue reflects the supplied billing data, not a guaranteed net-revenue reconciliation.
Troubleshoot delivery
Check the endpoint's project ID, selected event types, signing secret, and test/live environment. Review Stripe delivery logs, correct the configuration, and replay a supported event. Duplicate event IDs are handled without storing another copy. Rotating the secret resets Churnor's verification status.
Open the dedicated Stripe setup guide
Paddle customer data
Connect your SaaS customers’ billing data to Churnor. You need a Churnor product environment and access to your provider’s webhook settings. Signed webhooks deliver subscription changes and payment events without scheduled fetching. This connector is separate from Churnor’s own platform billing.
- Open Dashboard → Integrations → Paddle and copy your environment’s endpoint URL.
- In Paddle, open Events → Notifications, create a notification destination with URL delivery, add the copied URL, and select the events listed below. Keep the endpoint and secret in the same sandbox/test or live environment.
- Select the provider environment and enter an API key plus the endpoint signing secret. Paddle needs
customer.readandsubscription.read; Dodo read-only access is sufficient. Churnor validates access, encrypts the credentials, and starts importing existing records. - Send a signed test delivery or replay a supported event from the provider. Select Check delivery / Retry verification in Churnor. A successfully processed event changes the status to Verified.
Endpoint: /api/webhooks/paddle-customers?project_id=<YOUR_PROJECT_ID>
Supported events
subscription.createdsubscription.updatedsubscription.canceledsubscription.past_duesubscription.pausedsubscription.resumedtransaction.payment_failedtransaction.completed
What Churnor stores
Customer IDs, available customer details, subscription state, plan, currency, cancellation intent, payment failure, and recurring revenue when the payload provides it. Scores are recalculated during delivery. When no new events arrive, authorized dashboard reads refresh assessments older than 24 hours; there is no background inactivity scan. Billing events do not count as product activity. A lower recurring amount on a newer subscription update produces a downgrade signal.
Recurring revenue is a monthly equivalent of the supplied pre-tax recurring amount. Paddle uses recurring catalog item prices and quantities; Dodo uses recurring_pre_tax_amount and payment frequency. These figures may differ from net billed revenue after discounts or usage charges. Unknown amounts remain unavailable. Mixed-currency subscription totals are not combined.
Match billing and product activity
Use the provider’s customer ID as customer_id in Churnor’s Events API and CSV imports. If your existing Churnor records use your application’s customer ID, set churnor_customer_id in the subscription’s custom_data before connecting and importing. Use the same value across that customer’s subscriptions. That value is your external customer ID, not Churnor’s internal UUID. Churnor remembers the provider-to-customer mapping; it does not merge records by email.
History, retries, and connection checks
Connecting imports existing customers and current subscription state through the provider API. The import continues in the background when setup closes; progress is saved and transient failures retry. This is a snapshot, not a backfill of historical transactions or product activity. Import completion does not verify webhook delivery. Webhooks handle future changes without cron jobs. Retry import or Import latest records starts an explicit resync. Event IDs prevent duplicate storage, and older subscription snapshots cannot overwrite newer billing state. Unsupported events are acknowledged and ignored; they do not verify the connection.
If verification is waiting, check the endpoint URL, selected event, test/live environment, and signing secret. Replay a supported signed event after correcting the settings. Saving or rotating a secret resets verification. Dodo CLI mock events without signatures cannot verify a connection; use a signed provider delivery.
Paddle webhook setup documentationOpen the dedicated Paddle setup guide
Dodo Payments customer data
Connect your SaaS customers’ billing data to Churnor. You need a Churnor product environment and access to your provider’s webhook settings. Signed webhooks deliver subscription changes and payment events without scheduled fetching. This connector is separate from Churnor’s own platform billing.
- Open Dashboard → Integrations → Dodo Payments and copy your environment’s endpoint URL.
- In Dodo Payments, open Developer → Webhooks, create an endpoint, add the copied URL, and select the events listed below. Keep the endpoint and secret in the same sandbox/test or live environment.
- Select the provider environment and enter an API key plus the endpoint signing secret. Paddle needs
customer.readandsubscription.read; Dodo read-only access is sufficient. Churnor validates access, encrypts the credentials, and starts importing existing records. - Send a signed test delivery or replay a supported event from the provider. Select Check delivery / Retry verification in Churnor. A successfully processed event changes the status to Verified.
Endpoint: /api/webhooks/dodo-customers?project_id=<YOUR_PROJECT_ID>
Supported events
subscription.activesubscription.updatedsubscription.plan_changedsubscription.cancelledsubscription.expiredsubscription.past_duesubscription.on_holdsubscription.pausedsubscription.unpausedsubscription.renewedsubscription.failedpayment.failedpayment.succeeded
What Churnor stores
Customer IDs, available customer details, subscription state, plan, currency, cancellation intent, payment failure, and recurring revenue when the payload provides it. Scores are recalculated during delivery. When no new events arrive, authorized dashboard reads refresh assessments older than 24 hours; there is no background inactivity scan. Billing events do not count as product activity. A lower recurring amount on a newer subscription update produces a downgrade signal.
Recurring revenue is a monthly equivalent of the supplied pre-tax recurring amount. Paddle uses recurring catalog item prices and quantities; Dodo uses recurring_pre_tax_amount and payment frequency. These figures may differ from net billed revenue after discounts or usage charges. Unknown amounts remain unavailable. Mixed-currency subscription totals are not combined.
Match billing and product activity
Use the provider’s customer ID as customer_id in Churnor’s Events API and CSV imports. If your existing Churnor records use your application’s customer ID, set churnor_customer_id in the subscription’s metadata before connecting and importing. Use the same value across that customer’s subscriptions. That value is your external customer ID, not Churnor’s internal UUID. Churnor remembers the provider-to-customer mapping; it does not merge records by email.
History, retries, and connection checks
Connecting imports existing customers and current subscription state through the provider API. The import continues in the background when setup closes; progress is saved and transient failures retry. This is a snapshot, not a backfill of historical transactions or product activity. Import completion does not verify webhook delivery. Webhooks handle future changes without cron jobs. Retry import or Import latest records starts an explicit resync. Event IDs prevent duplicate storage, and older subscription snapshots cannot overwrite newer billing state. Unsupported events are acknowledged and ignored; they do not verify the connection.
If verification is waiting, check the endpoint URL, selected event, test/live environment, and signing secret. Replay a supported signed event after correcting the settings. Saving or rotating a secret resets verification. Dodo CLI mock events without signatures cannot verify a connection; use a signed provider delivery.
Dodo Payments webhook setup documentationOpen the dedicated Dodo Payments setup guide
HTTP examples
Use your language's HTTP client to send the same JSON schema. This server-side fetch example is not a Churnor SDK.
// Run on your server; do not expose the key in browser code.
const response = await fetch("https://churnor.vercel.app/api/v1/events", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CHURNOR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"customer_id": "example_customer",
"event": "report_exported",
"idempotency_key": "example_delivery_1",
"properties": {
"format": "csv"
}
}),
});
if (!response.ok) throw new Error(`Churnor returned ${response.status}`);
const result = await response.json();Limits & retries
Customer POST/PATCH requests and single events share a default 100 requests per 60 seconds per project across all its keys and server instances. Batches have a separate 20-request limit and accept up to 1,000 events each. A window starts with its first request. Monthly workspace allowances are separate.
Responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix seconds). HTTP 429 includes Retry-After in seconds. Wait, then resend the same event IDs, timestamps, and data. If the shared counter is unavailable, HTTP 503 includes Retry-After: 5. Invalid credentials and invalid data should be corrected before retrying.
HTTP status codes
- 200 / 201: accepted requests; inspect the returned fields.
- 400: invalid JSON or payload. Error responses may include validation details.
- 401: missing, invalid, or revoked authentication.
- 404: the requested customer is unavailable in this environment.
- 429: request rate limited.
- 500: ingestion or processing failed.
- 503: temporarily unavailable; follow Retry-After.
Error responses use an error string and may include details. Billing and dashboard routes have their own response shapes; inspect their logs when troubleshooting.
Setup and monitoring guides
Choose a guide for the data you want to connect or the customer workflow you want to review.
- Stripe Integration Setup Guide
Connect Stripe subscription and payment events to Churnor. Configure signed webhooks, match customer IDs, and verify a supported event delivery.
- Paddle Integration Setup Guide
Connect Paddle Billing to Churnor with signed webhooks. Learn supported events, customer matching, revenue limitations, and delivery verification.
- Dodo Payments Integration Setup Guide
Connect Dodo Payments customer billing data to Churnor. Set up signed webhooks, supported events, customer matching, and delivery verification.
- CSV Customer Import Guide for Churn Monitoring
Prepare a customer CSV for Churnor, map external IDs and supported fields, review import results, and learn when to use the Events API instead.
- A Practical Guide to SaaS Churn Monitoring
Learn how to bring product activity and billing changes together, review customer risk evidence, and prioritize follow-up with Churnor.
- Customer Event Tracking for SaaS Churn Monitoring
Choose meaningful product events, send them to Churnor’s Events API, and understand how event names, timestamps, and customer IDs are used.
- Match Customer IDs Across Billing and Product Events
Keep a stable external customer ID across Churnor’s Events API, CSV imports, Stripe, Paddle, and Dodo Payments.