Predict on Pendo

Guy Dvorkin, 2026-10-06, for the 2026-10-07 conversation with the Pendo Predict team. The worked example runs on synthetic, Pendo-shaped data, generated and seeded the night before the call. No number in section 4 says anything about Pendo's real customers. Facts about Pendo come only from Pendo's own pages, quoted under Sources at the bottom (superscript numbers).

The sections follow the JD's day-to-day: discovery, data model and metric, build and present, integrate, playbooks, expectations.

1. Pendo as the customer

2. The metric conversation

Predict "is decided in the first conversation: which metric the model predicts, and whether the customer's data can carry it." Two candidates for Pendo:

Renewal churn risk Predict add-on adoption propensity
Label Renewal opportunity Closed Lost, dated by close date 38,39 Predict add-on closed won within N days
Volume needed 200 churned + 500 renewed, or one year 33 Enough past add-on wins; young product, likely thin
Who acts CSM on the account AE / expansion rep
Risk Label hygiene in Salesforce Too few positives in year one

Recommendation: churn first, adoption second. Churn has the labels, a documented setup path 33-47, and the larger dollar exposure. Adoption propensity is the second model once Predict has enough wins to learn from.

The question that decides it on the first call: "Which renewal cohort has clean labels, and who owns them?" If nobody owns Closed Lost reasons in Salesforce, the first deliverable is a label review, not a model.

3. Can the data carry it

Source Objects Join key
Pendo (Pendo's own instance) Accounts, Visitors, Pages, Features, Track Events, Guides, NPS; account and visitor metadata 67-73 Account ID
Salesforce Account, Opportunity (renewal and expansion), Contract Account ID carried as a field or Pendo metadata 35,70
Billing Invoices, tier, MAU contracted Salesforce Account ID
Support Tickets, severity 36 Salesforce Account ID

Five discovery questions each:

CSM leader Data owner
What does "churned" mean here: non-renewal, downgrade, or both? Where does the renewal outcome live, and is the close date the decision date?
How far ahead do you need to know to change the outcome? Is the Pendo Account ID on the Salesforce Account, and for what share of accounts?
What does a CSM do today when an account looks at risk? Which fields get overwritten without history (stage, owner, ARR)?
How many accounts can one CSM act on in a week? Which events are tagged retroactively vs as Track Events with no history?
Which past churns surprised you, and why? Who approves read access to Salesforce and the warehouse, and how long does it take?

4. The worked example (synthetic)

Seeded generator (demo/gen.py --seed 7): 2,000 Pendo-shaped accounts, 365 days of daily usage, renewal opportunities, support tickets. The churn mechanics are hidden in the generator and the model has to find them. Three quarterly cutoffs give 1,468 labelled rows, 204 churned and 1,264 renewed, a churn rate of 13.9%. One cutoff alone gave 79 churners, under Pendo's own 200 minimum 33, which is why three are stacked.

From demo/results.json:

What the drivers mean: narrow feature use, low guide completion, and no admin login in weeks push risk up. The generator's strongest mechanic was a usage decline, yet the model credits feature breadth, because both fall together. Importance is not cause.

What the metrics do not mean: nothing about Pendo's customers. And recall 39.3% is not a miss against Pendo's 70% bar 44: that bar does not state how many accounts are flagged, and recall rises as the list grows. How many accounts a CSM team can act on sets the list size, so that number is agreed with the CSM leader, not read off the model.

SQL, run on Postgres 16: demo/queries/01-wau.sql, 02-trend-window.sql, 03-churn-label.sql, and the decision base 04-features.sql.

The numbers, from results.json

MetricValue
Accounts generated2,000
Labelled rows (3 cutoffs)1,468
Churned / renewed204 / 1,264
Churn rate13.9%
Train / test1,027 / 441 (61 churned)
AUC, logistic regression0.855
AUC, gradient boosting0.843
AUC, out of time0.842
Top 10%: churners caught24 of 44
Precision, top 10%54.5%
Recall, top 10%39.3%
Lift, top 10%3.9x
Open renewals scored509
High / Watch / Healthy30 / 54 / 425

Calibration

Fifth of test setAccountsMean predictedObserved churn
1890.0030.000
2880.0180.034
3880.0650.068
4880.1590.136
5880.5120.455

Top 8 drivers (permutation importance)

FeatureAUC drop when shuffled
feature_breadth_28d0.135
guide_completion_rate0.024
segment=Enterprise0.018
segment=SMB0.014
days_since_admin0.008
plan_tier=Base0.008
events_per_visitor_day0.003
tickets_30d0.002

The CSM view: top 10 open renewals

What a CSM would see on the Salesforce Account: the score, the band, and why.

AccountScoreBandRenewalTierARRWhy
A12430.891High2026-11-18Base$25,900Uses 2.3 features a day (typical 5.4); No admin login in 90 days (typical 2); 11.7 feature events per user-day (typical 14.9)
A12590.851High2026-10-10Core$35,400Uses 1.9 features a day (typical 5.4); No admin login in the 90-day window; Guide completion 39% (typical 61%)
A11370.832High2026-10-04Core$15,900Uses 2.3 features a day (typical 5.4); 10.3 feature events per user-day (typical 14.9); No admin login in the 90-day window
A18950.825High2026-12-14Base$9,700Uses 2.6 features a day (typical 5.4); No admin login in 90 days (typical 2); Guide completion 38% (typical 61%)
A09350.823High2026-11-30Base$10,700Uses 2.5 features a day (typical 5.4); No admin login in the 90-day window; 12.0 feature events per user-day (typical 14.9)
A04830.813High2026-10-25Base$9,500Uses 1.7 features a day (typical 5.4); Guide completion 36% (typical 61%); 11.5 feature events per user-day (typical 14.9)
A07070.811High2026-12-12Base$23,700Uses 2.4 features a day (typical 5.4); Guide completion 43% (typical 61%); 12.4 feature events per user-day (typical 14.9)
A18110.804High2026-11-13Base$10,500Uses 1.9 features a day (typical 5.4); Guide completion 40% (typical 61%); No admin login in 64 days (typical 2)
A19790.794High2026-11-05Base$27,600Uses 2.7 features a day (typical 5.4); No admin login in the 90-day window; 12.4 feature events per user-day (typical 14.9)
A13000.791High2026-10-11Core$72,90012 high-severity tickets in 90 days (typical 0); Uses 2.2 features a day (typical 5.4); Guide completion 40% (typical 61%)

The SQL

1. Weekly activity, last 12 full weeks

-- 1. Weekly activity per account, last 12 FULL weeks. Data ends Wed 2026-09-30, so the week of
--    2026-09-28 is partial (3 days) and is excluded; left in, it showed 52,610 visitor-days
--    against 93,510 the week before: a 44% "drop" that is only the calendar.
--    date_trunc buckets days into ISO weeks; count(DISTINCT day) = active days that week.
--    First: the headline, weekly active accounts. Then the per-account detail.

WITH weeks AS (
  SELECT account_id,
         date_trunc('week', day)::date AS week,
         day,
         active_visitors
  FROM account_daily_usage
  WHERE day >= date_trunc('week', DATE '2026-09-30') - INTERVAL '12 weeks'
    AND day <  date_trunc('week', DATE '2026-09-30')
)
SELECT week,
       count(DISTINCT account_id)   AS weekly_active_accounts,
       sum(active_visitors)         AS visitor_days
FROM weeks
GROUP BY week
ORDER BY week;

WITH weeks AS (
  SELECT account_id,
         date_trunc('week', day)::date AS week,
         day,
         active_visitors
  FROM account_daily_usage
  WHERE day >= date_trunc('week', DATE '2026-09-30') - INTERVAL '12 weeks'
    AND day <  date_trunc('week', DATE '2026-09-30')
)
SELECT account_id,
       week,
       count(DISTINCT day)    AS active_days,
       sum(active_visitors)   AS visitor_days,
       max(active_visitors)   AS peak_daily_visitors
FROM weeks
GROUP BY account_id, week
ORDER BY account_id, week
LIMIT 20;

Output, Postgres 16:

    week    | weekly_active_accounts | visitor_days 
------------+------------------------+--------------
 2026-07-06 |                   1867 |        95353
 2026-07-13 |                   1859 |        95297
 2026-07-20 |                   1849 |        95359
 2026-07-27 |                   1841 |        94676
 2026-08-03 |                   1836 |        94415
 2026-08-10 |                   1829 |        94629
 2026-08-17 |                   1824 |        94242
 2026-08-24 |                   1815 |        93958
 ... first 8 shown
(12 rows)

 account_id |    week    | active_days | visitor_days | peak_daily_visitors 
------------+------------+-------------+--------------+---------------------
 A0001      | 2026-07-06 |           7 |           48 |                  12
 A0001      | 2026-07-13 |           7 |           54 |                  15
 A0001      | 2026-07-20 |           7 |           74 |                  20
 A0001      | 2026-07-27 |           6 |           63 |                  20
 A0001      | 2026-08-03 |           5 |           44 |                  19
 A0001      | 2026-08-10 |           7 |           75 |                  16
 A0001      | 2026-08-17 |           6 |           52 |                  14
 A0001      | 2026-08-24 |           5 |           52 |                  14
 ... first 8 shown
(20 rows)

2. Last 4 weeks vs the 4 before (window functions)

-- 2. Usage trend per account: last 4 full weeks vs the 4 weeks before, with window functions.
--    Weekly visitor-days -> 4-week rolling sum -> lag() by 4 weeks for the prior block.
--    Two details that change the answer:
--    * The calendar spine (accounts x weeks): a week with zero usage has no rows, and without
--      the spine the window would skip it and overstate the trend.
--    * Full weeks only: data ends Wed 2026-09-30, the last full week starts 2026-09-21.

WITH spine AS (
  SELECT a.account_id, w::date AS week
  FROM accounts a
  CROSS JOIN generate_series(date_trunc('week', DATE '2026-09-30') - INTERVAL '12 weeks',
                             date_trunc('week', DATE '2026-09-30') - INTERVAL '1 week',
                             INTERVAL '1 week') AS w
),
weekly AS (
  SELECT s.account_id, s.week, coalesce(sum(u.active_visitors), 0) AS visitor_days
  FROM spine s
  LEFT JOIN account_daily_usage u
         ON u.account_id = s.account_id
        AND date_trunc('week', u.day) = s.week
  GROUP BY s.account_id, s.week
),
rolling AS (
  SELECT account_id, week,
         sum(visitor_days) OVER (PARTITION BY account_id ORDER BY week
                                 ROWS BETWEEN 3 PRECEDING AND CURRENT ROW) AS last_4w
  FROM weekly
),
compared AS (
  SELECT account_id, week, last_4w,
         lag(last_4w, 4) OVER (PARTITION BY account_id ORDER BY week) AS prev_4w
  FROM rolling
)
SELECT c.account_id, a.plan_tier, a.contract_end,
       c.last_4w, c.prev_4w,
       round(100.0 * (c.last_4w - c.prev_4w) / nullif(c.prev_4w, 0), 1) AS change_pct
FROM compared c
JOIN accounts a USING (account_id)
WHERE c.week = date_trunc('week', DATE '2026-09-30') - INTERVAL '1 week'
  AND c.prev_4w >= 20            -- tiny accounts: a percentage of 5 visitor-days is noise
  AND a.renewed IS NULL          -- open renewals only: the ones a CSM can still save
ORDER BY change_pct ASC
LIMIT 20;

Output, Postgres 16:

 account_id | plan_tier | contract_end | last_4w | prev_4w | change_pct 
------------+-----------+--------------+---------+---------+------------
 A1887      | Core      | 2026-10-29   |      18 |      46 |      -60.9
 A0532      | Core      | 2026-11-02   |      21 |      40 |      -47.5
 A1589      | Base      | 2026-12-24   |      15 |      26 |      -42.3
 A1701      | Base      | 2026-12-25   |      18 |      31 |      -41.9
 A1822      | Core      | 2026-10-27   |      15 |      25 |      -40.0
 A0681      | Base      | 2026-11-07   |      24 |      40 |      -40.0
 A0483      | Base      | 2026-10-25   |      24 |      38 |      -36.8
 A1035      | Base      | 2026-11-28   |      50 |      78 |      -35.9
 ... first 8 shown
(20 rows)

3. The churn label, joined to 90 days of usage

-- 3. The churn label, Pendo Predict's own definition (support article, 01-pendo-site.md rows 38-39):
--    a renewal opportunity Closed Lost, dated by Opportunity close date.
--    Window: renewals that closed 2026-07-01 .. 2026-09-28 (90 days).
--    Joined to each account's usage in the 90 days BEFORE its close date.

WITH labelled AS (
  SELECT o.account_id,
         o.close_date,
         (o.stage = 'Closed Lost') AS churned
  FROM sf_opportunities o
  WHERE o.type = 'renewal'
    AND o.stage IN ('Closed Won', 'Closed Lost')
    AND o.close_date >= DATE '2026-07-01'
    AND o.close_date <  DATE '2026-07-01' + 90
),
pre_usage AS (
  SELECT l.account_id, l.close_date, l.churned,
         count(u.day)                       AS active_days_90d,
         coalesce(sum(u.active_visitors),0) AS visitor_days_90d,
         max(u.day)                         AS last_active_day
  FROM labelled l
  LEFT JOIN account_daily_usage u
         ON u.account_id = l.account_id
        AND u.day >= l.close_date - 90
        AND u.day <  l.close_date
  GROUP BY l.account_id, l.close_date, l.churned
)
SELECT churned,
       count(*)                                  AS accounts,
       round(avg(active_days_90d), 1)            AS avg_active_days_90d,
       round(avg(visitor_days_90d), 0)           AS avg_visitor_days_90d,
       round(avg(close_date - last_active_day), 1) AS avg_days_silent_before_close
FROM pre_usage
GROUP BY churned
ORDER BY churned;

WITH labelled AS (
  SELECT o.account_id, o.close_date, (o.stage = 'Closed Lost') AS churned
  FROM sf_opportunities o
  WHERE o.type = 'renewal'
    AND o.stage IN ('Closed Won', 'Closed Lost')
    AND o.close_date >= DATE '2026-07-01'
    AND o.close_date <  DATE '2026-07-01' + 90
)
SELECT l.account_id, a.plan_tier, l.close_date, l.churned,
       count(u.day)                       AS active_days_90d,
       coalesce(sum(u.active_visitors),0) AS visitor_days_90d,
       max(u.day)                         AS last_active_day
FROM labelled l
JOIN accounts a USING (account_id)
LEFT JOIN account_daily_usage u
       ON u.account_id = l.account_id
      AND u.day >= l.close_date - 90
      AND u.day <  l.close_date
WHERE l.churned
GROUP BY l.account_id, a.plan_tier, l.close_date, l.churned
ORDER BY visitor_days_90d ASC
LIMIT 20;

Output, Postgres 16:

 churned | accounts | avg_active_days_90d | avg_visitor_days_90d | avg_days_silent_before_close 
---------+----------+---------------------+----------------------+------------------------------
 f       |      455 |                76.5 |                  625 |                          1.2
 t       |       79 |                67.4 |                  342 |                          1.5
(2 rows)

 account_id | plan_tier | close_date | churned | active_days_90d | visitor_days_90d | last_active_day 
------------+-----------+------------+---------+-----------------+------------------+-----------------
 A1693      | Base      | 2026-07-28 | t       |              15 |               15 | 2026-07-27
 A0521      | Base      | 2026-07-30 | t       |              24 |               32 | 2026-07-28
 A1079      | Core      | 2026-09-19 | t       |              32 |               38 | 2026-09-18
 A0565      | Base      | 2026-08-13 | t       |              30 |               43 | 2026-08-07
 A0533      | Ultimate  | 2026-08-14 | t       |              39 |               49 | 2026-08-07
 A1262      | Base      | 2026-07-19 | t       |              39 |               52 | 2026-07-16
 A0965      | Base      | 2026-07-24 | t       |              41 |               60 | 2026-07-23
 A1223      | Base      | 2026-07-07 | t       |              42 |               64 | 2026-07-06
 ... first 8 shown
(20 rows)

5. Activation

Write back to the Salesforce Account with the four fields Predict documents 48-53:

Field Type 52 Holds
Pendo Score Text The band: High / Watch / Healthy in the example; Predict's own four tiers in production 48
Pendo Explain Text Area (Long) The top 3 drivers, in plain words with the typical value next to each
Pendo Trend Text Up / Down / New since the last score 50
Pendo Score Date Date When the band last changed 51

Playbook per band:

Band Owner SLA Action
High CSM, manager copied Call within 5 business days Exec sponsor call, usage review against the drivers, renewal plan in the Opportunity
Watch CSM Within 15 business days Targeted in-app guide on the feature gap, admin re-engagement
Healthy Expansion owner Quarterly Look for expansion: MAU headroom, next tier, add-ons

The CSM sees "why", not only "how much": "Uses 2.3 features a day (typical 5.4)". A driver is shown only when it points the same way as the data overall, so a CSM never reads "bigger account, more risk" from a collinear coefficient.

6. Day 90

Targets, not promises:

7. Three questions for Itai

  1. What does a good day 90 look like for this contract: how many customers live, how many POVs?
  2. Where do Predict deployments stall today: data access, data quality, or the customer not acting on the scores?
  3. How do the original Forwrd automated models and the LLM layer split the work?

Sources

Every Pendo fact above, quoted from Pendo's own pages, fetched 2026-10-06.

  1. 2. Customer count: "See why more than 14K companies use Pendo to create better software experiences" www.pendo.io
  2. 3. Customer count, home page: "Join 14,000+ teams innovating with Pendo" www.pendo.io
  3. 7. Tier 1: Base: "For teams ready to scale with analytics and in-app guidance." www.pendo.io
  4. 8. Tier 2: Core adds Session Replay: "For teams who want to see the full user journey and take quick action to improve it." www.pendo.io
  5. 9. Tier 3: Ultimate, marked most popular; bundle adds Sentiment, Orchestrate, Listen, Data Sync: "For enterprises and product leaders coordinating adoption across systems." www.pendo.io
  6. 10. Ultimate includes Sentiment: "Sentiment (NPS, PMF, CSAT surveys)" www.pendo.io
  7. 11. Price driver is MAU plus functionality: "Pendo pricing combines two things: the number of Monthly Active Users (MAUs) you track and the functionality you choose as part of your plan." www.pendo.io
  8. 12. MAU definition: "Monthly active users (MAUs) are the number of unique visitors who were active in your app over the last 30 calendar days." www.pendo.io
  9. 13. Predict is an add-on to any plan: "Add Pendo AI to any Pendo plan" www.pendo.io
  10. 14. Predict is priced on prediction volume: "Custom prediction volume" www.pendo.io
  11. 26. Pendo runs Predict on itself; horizon claim: "how her team spots churn risk up to 6 months out" www.pendo.io
  12. 33. Minimum data for a churn model: "At least one year of usage data, or enough history to capture 200 churned accounts and 500 renewed accounts, whichever is greater." support.pendo.io
  13. 34. Labels must live in the CRM with dates: "Churn and renewal events are tracked in your CRM with a date field for each outcome." support.pendo.io
  14. 35. The join key: "Accounts in your CRM map to accounts in Pendo using a shared identifier or metadata field." support.pendo.io
  15. 36. Support data is optional: "Ticket volume, severity, resolution trends" support.pendo.io
  16. 37. Define the metric before configuring: "Define what the model should predict before configuring anything in the product." support.pendo.io
  17. 38. Churn label example: "Account has a renewal opportunity with a status of Closed Lost" support.pendo.io
  18. 39. Label date field: "Example date field: Opportunity.Close_Date" support.pendo.io
  19. 40. Audience: who gets scored: "Account has an open renewal opportunity that isn’t Closed Won or Closed Lost, with a close date in the next 90 days" support.pendo.io
  20. 41. History needed vs prediction window: "You need two to three times the prediction window in historical data." support.pendo.io
  21. 42. Decision base = one row per scoring unit: "Join objects, aggregate time-based data (for example, usage over the last 90 days), and structure records so each represents a single scoring unit." support.pendo.io
  22. 43. Data hygiene flags: "High blank rate fields." support.pendo.io
  23. 44. Pendo's accuracy bar. **inferred**: the definition given is recall on churners: "Accuracy. Percentage of churned accounts correctly identified. Aim for 70 percent or higher." support.pendo.io
  24. 45. Simulation before activation: "Preview how the model scores your current audience." support.pendo.io
  25. 46. Playbooks feed the agent: "Upload playbooks that reflect your retention strategies." support.pendo.io
  26. 47. Scores go back into Pendo as metadata: "Store scores as account metadata for segmentation, guides, and reporting." support.pendo.io
  27. 48. Output field 1: four tiers: "Pendo Predict scores each record as either Excellent, Good, Fair, or Poor based on the propensity to convert." support.pendo.io
  28. 49. Output field 2: drivers: "Pendo Explain - This explains the top signals influencing the model's score." support.pendo.io
  29. 50. Output field 3: trend: "Pendo Trend - Values are either Up, Down, New or empty (no change)." support.pendo.io
  30. 51. Output field 4: date: "Pendo Score Date - the date of the recent score change." support.pendo.io
  31. 52. Explain field type in Salesforce: "Pendo Explain - field type should be Text Area (Long)" support.pendo.io
  32. 53. Write-back is scheduled: "This is the automation scheduler, for example, every day at 12 am." support.pendo.io
  33. 56. Salesforce integration is paid: "This integration is available as a paid add-on for all Pendo subscriptions." www.pendo.io
  34. 59. Snowflake via Data Sync, paid: "Pendo Data Sync is a paid-add on." www.pendo.io
  35. 67. Data model: two identities: "Metadata is collected under Visitor IDs and Account IDs" support.pendo.io
  36. 68. Metadata examples: "Metadata includes information about your visitors and accounts, such as user role, account type, subscription revenue, and more." support.pendo.io
  37. 69. Default account metadata (also: first visit, last visit, days active, events, time on site): "Number of visitors" support.pendo.io
  38. 70. Metadata can come from Salesforce: "These metadata fields are pulled in from Salesforce through our integration" support.pendo.io
  39. 71. Track Events: custom events tied to visitor and account: "Track Events programmatically notify Pendo that a particular action occurred at a specific time and associate this event to a designated visitor and account." support.pendo.io
  40. 72. Track Events have no backfill. **inferred**: a new churn signal tagged today has no history: "there's no historical data associated with Track Events" support.pendo.io
  41. 73. How Pendo defines account adoption: "Account adoption. Percentage of unique accounts that generated the Track Event, calculated by dividing the total number of these accounts by the total number of active accounts in the app." support.pendo.io