Render testPlaywright, iPhone emulation: bytes, requests, LCP, CLS — and JS-off HTML
Link checkAll 104 llms.txt URLs hit for status codes
PlatformsG2, Google Ads Transparency, Meta & LinkedIn ad libraries, PH, Reddit, YT, X
AI engines3 buyer prompts run on Perplexity, permalinks retained
The five findings
filter ↓
F-01
Every hub page links to nothing
Critical
# extract every internal anchor from raw HTML
$ curl -s https://www.usefini.com/blog \
| grep -oE 'href="(/[^"]*|https://www\.usefini\.com[^"]*)"' \
| sort -u
https://www.usefini.com/blog← self-link
https://docs.usefini.com/introduction
https://security.usefini.com/
# same result on every collection page:
/blog 127 posts → 1 link
/hall-of-fame 35 profiles → 1 link
/podcast 11 episodes → 1 link
/resource-library 5 reports → 1 link
/comparison → 1 link
/podcast-episodes → 0 links
Child slugs exist in the HTML as Framer CMS payload strings — never as <a href>. Verified 4 Aug 2026.
Raw-HTML anchor extraction — no JS execution, i.e. exactly what a non-rendering crawler receives
Basis
Anchor text and internal links are primary relevance signals. GPTBot, ClaudeBot, PerplexityBot and CCBot largely do not execute JavaScript
So what
To those crawlers our blog, podcast, hall of fame and research library are unreachable by link. 1,555 pages, no link equity flowing between them
What the two crawler types actually receive
Exact fix — 6 steps, ~half a day
Open the Framer project → /blog page → select the CMS Collection List component that renders the post cards.Framer editor → Pages → blog → Layer panel
Select the card frame inside the list. In the right panel open Link → set it to the collection field Slug (Framer writes a real <a href> only when the card itself carries the link, not when an on-click override does).Right panel → Link → Collection Reference → Slug
Confirm the card title is a text layer inside that linked frame, so the slug text becomes the anchor text. Do not link the image alone.
Publish, then re-run the check on each page — the count must be the number of children, not 1.curl -s https://www.usefini.com/blog | grep -c 'href="/blog/'
In Search Console → URL Inspection → "Test live URL" → View crawled page on /blog. The raw HTML tab must now list the post URLs.
Done whencurl -s https://www.usefini.com/blog | grep -c 'href="/blog/' returns ≥ 127, and the same test passes on all nine collection pages.
F-02
We publish three different prices
Critical
# 1. homepage visible copy
$ curl -s https://www.usefini.com/ | strip-tags | grep -o '\$0\.[0-9]*'
$0.49 per resolution
# 2. homepage JSON-LD, same page, same request
$ curl -s https://www.usefini.com/ | jq '..|.offers?'
"description": "... 2,000 resolved tickets per month.
$3,600/mo ... Additional resolutions $0.89 each."
# 3. /pricing meta description
$ curl -s https://www.usefini.com/pricing | grep 'name="description"'
"See Fini pricing: pay per resolution at $0.69..."
# 4. our own G2 listing
Growth $0.69 1 Resolution
# bonus: languages, same page, two schema blocks
Organization → "50 languages"
SoftwareApplication → "130+ languages"
Four sources. Three prices. All published by us. Verified 4 Aug 2026.
Fetched each surface, extracted visible copy, JSON-LD offers and meta description separately
Basis
Retrieval-based models resolve conflicting facts by hedging or omitting. A hedged price loses a comparison to a competitor who published one number
Related
Same problem on entity data: LinkedIn says San Francisco / 51–200, YC says Amsterdam / 14, schema HQ is Wilmington DE, schema names 1 of 2 founders
Exact fix — 8 steps, ~2 hours
Get the real number from Deepak in writing: list price per resolution, the Growth plan monthly, the overage rate, and whether $0.49 is a volume tier or an outdated figure. Nothing else starts until this exists.
Framer → homepage → update the pricing text layer to the canonical number.Also check the pricing comparison module and any CTA microcopy
Framer → Site Settings → Custom Code → End of <head> → edit the SoftwareApplication JSON-LD: fix offers.price, offers.description and the language count.
In the same block fix Organization: language count to match, add Hakim as a second founder, and extend sameAs with the G2, YC, Crunchbase and YouTube URLs.
Update /pricing meta description + on-page copy to the canonical number.Framer → pricing → Page Settings → SEO
Update the price on every off-site profile: G2 (Sell on G2 → Product Profile → Pricing), HubSpot, Front, Gorgias, Zendesk and Intercom marketplace listings, Crunchbase, LinkedIn.
Regenerate llms.txt with a ## Facts block containing the canonical set, so models get one unambiguous source.
Done whenA search for the price string across usefini.com, the schema, llms.txt and all off-site profiles returns exactly one value. Re-audit quarterly.
F-03
Our headline claim reads as 0% to AI crawlers
Critical
# what a non-rendering crawler receives from the homepage
$ curl -s https://www.usefini.com/ \
| python3 -c "strip script/style, print text" \
| grep -o '0%.\{0,60\}'
0% Resolution Rate0% Accuracy Rate0+ Supported Languages0M+ Monthly RESOLUTIONS# the real values only exist after JS runs the count-uphumans + Googlebot → 90% / 99% / 130+ / 3M+GPTBot, ClaudeBot, PerplexityBot, CCBot → 0
Confirmed by fetching the homepage and stripping <script> before reading text. Verified 4 Aug 2026.
Where
usefini.com/ hero stat band, above the fold
Method
Raw HTML fetch, scripts stripped, text extracted — replicating a non-rendering crawler
Basis
The counters initialise at 0 and animate to the real figure in JS. Anything that doesn't run JS captures the initial value
Nuance
This is not "the site is broken" — humans and Google see it correctly. It is specifically a GEO defect, on the one claim the whole positioning rests on
Exact fix — 4 steps, ~30 minutes
Framer → homepage → select each of the four stat text layers in the hero band.
Change the default text content from 0 to the true value (90%, 99%, 130+, 3M+) so the correct number is what ships in the HTML.The count-up override then animates from a rendered-correct starting state
If the animation component forces a 0 start, either set its start value to the true figure minus a small delta, or drop the animation. A static correct number beats an animated wrong one.
Republish, then verify with the same command used to find it.curl -s https://www.usefini.com/ | grep -o '0% Resolution Rate' → must return nothing
Source:g2.com/products/fini/reviews · captured 4 Aug 2026 · Note the Sponsored — Aissist.io box: a 38-review competitor is buying placement on our profile page.
Intercom Fin3,898
Zoho Desk7,820
Agentforce1,202
Aissist.io38
Fini13
G2 review volume, same category. Bars drawn to true scale against Zoho Desk (7,820). Fini’s 13 is 0.17% of that bar — the hairline is the honest picture. G2 Score = satisfaction × market presence, and market presence is driven by review count — so 4.9/5 cannot rank on 13 reviews.
Opened the category and read all of page 1. Fini appears nowhere on it.
Also checked
Capterra, Trustpilot, TrustRadius, Gartner Peer Insights — no Fini profile found on any
Basis
G2 category pages are among the most-retrieved sources in AI answers. This is why reviews, not more content, is the real GEO lever
Maths
100+ customers × 25% response = 25+ reviews. The rating is already the best in the category
Source: G2 category page 1 of 37 · captured 4 Aug 2026 · Fini is not present on this page.
Exact fix — 90-day review sprint, 9 steps
Pull the customer list and score each account: live > 60 days, resolution rate at or above target, no open escalation. That is the eligible pool.
Rank by a proven-value moment — a resolution-rate milestone, a QBR win, a successful peak season. Ask at the moment, never on a quarterly blast.
Get the G2 review-collection link: Sell on G2 → Reviews → Review Generation → create a campaign link. Use a unique link per CSM so attribution is clean.
Write one three-line ask. Name the specific result they got, one link, state the time cost ("about four minutes"). No template merge fields beyond the first name.
Assign it: each CSM owns a named list with a monthly number. Put it in the CS scorecard, not in a marketing tracker — the request has to come from the person the customer trusts.
Offer the incentive G2 permits (gift card via G2's own program), and disclose it. Never draft the review.
At ~25 reviews, submit to Capterra + GetApp — one Gartner Digital Markets submission covers both.
At ~50, open Gartner Peer Insights — enterprise fintech buyers read it during procurement and it is heavily cited by AI engines.
Defend the profile: buy the G2 category placement that Aissist.io is currently using against us — only after the review count justifies the click.
Done when50 G2 reviews inside 90 days (≈ 4/week), Fini visible in the category grid, and G2 URLs appearing as citations in the monthly prompt panel.
F-05
Every comparison page claims we win every row
Strategic
Won. "best AI customer support agent for a fintech in regulated industries" → Lorikeet #1, Fini #2. Our only citation was usefini.com itself. perplexity.ai/search/cb497e22…
Lost. "best alternatives to Intercom Fin" → Quickchat, Helply, Pluno, Gorgias, Sierra/Decagon/Ada. Fini absent — beaten by companies far smaller than us. perplexity.ai/search/1281b323…
Method
Ran three real buyer prompts on Perplexity, retained the permalinks, recorded which vendors were named and which sources were cited
Finding
Scanned all five of our comparison surfaces for concession language — "when to choose them", "limitations", "better if". Zero instances. On every page.
Basis
Models discount sources that read as vendor marketing and quote ones that read as evaluations. Lorikeet beats us on our own vertical with a fraction of our content
Our comparison content is scattered across four incompatible places
Nothing links to anything. The two best pages are buried in the blog.
Exact fix — 10 steps, 6 pages in 30 days
Pick the six competitors by where we actually lose: Intercom Fin, Zendesk AI, Decagon, Sierra, Ada, Gorgias.
Build one Framer template with five fixed blocks: verdict paragraph → comparison table → "choose them if" → FAQ → CTA. Everything else is content entry.
Write the verdict block first, ≤100 words, stating plainly who should pick Fini and who shouldn't. This is the block AI engines extract verbatim, so it carries the whole page.
Build the table as a real HTML <table>, not Framer bar graphics. Rows: regulated fit · audit trail · price per resolution · guarantee · days to go live · channels · certifications./comparison currently has 0 tables — graphics are invisible to extraction
Write an honest "Choose <competitor> if…" section. Example: "Choose Fin if you're already deep in Intercom's helpdesk and want the fastest possible setup." This is what converts the page from an ad into a citable evaluation.
Add FAQPage JSON-LD with the five questions buyers actually ask (price, migration, compliance, accuracy, go-live).Framer → Page Settings → Custom Code → End of <head>
Migrate the two strong blog pages to /compare/ and 301 the old URLs.Framer → Site Settings → Redirects
Fix the Zendesk page: retitle to the query we want to win, and put Fini in the H1.
Refresh or retire /blog/fini-ai-vs-ada — it is stamped "Last Updated: Feb 17, 2025" and quotes stale competitor pricing.
Merge /comparison into /compare, 301 the loser, and link the hub to every child (this is F-01 step 4).
Done whenSix pages live under /compare with tables and concession sections, all /blog/fini-vs-* 301'd, and Fini named on the "Intercom Fin alternatives" prompt at the next monthly panel run.
The AI engines, tested directly
3 prompts · permalinks retained
Prompt run on Perplexity
Fini
Who won, and from which sources
Proof
best AI customer support agent for a fintech in regulated industries
#2
Lorikeet #1, Fini #2, Zowie #3 — our citation was usefini.com itself, theirs was independent
The pattern: we win the narrow vertical prompt and lose both high-volume ones. The winners on those two are tiny companies whose own blog posts got cited — they wrote the head page, we wrote 1,221 long-tail guides.
Where we stand against the field
all figures measured, not estimated
Paid search presence
Google Ads Transparency Center, all regions, all time · 4 Aug 2026
Decagon~300
Lorikeet66
Fini0
Lorikeet — our closest-stage competitor — is running a "Decagon Alternative" ad right now. That is the exact motion recommended for us, already validated in our category. adstransparency.google.com · lorikeetcx.ai
Followers / subscribers / upvotes. LinkedIn is the only real distribution asset — and its profile says San Francisco, 51–200 employees, industry "Chatbot Software".
X — 170 followers, 34 posts since Oct 2022
Recommendation: maintain as an entity signal, do not invest
youtube.com/@finiai · captured 4 Aug 2026 · The buried asset is the podcast: 11 interviews with Heads of Support, no transcripts, no schema.
Page weight — Framer is not the problem
Measured with Playwright, iPhone emulation, cold load · total transferred bytes
Lorikeet14.4 MB
Fini home13.4 MB
Decagon11.3 MB
Fini pricing5.2 MB
Fini guide4.0 MB
Verdict: keep Framer. Our TTFB is 267 ms against Decagon's 780 ms and Lorikeet's 417 ms, and our content is server-rendered. The one genuine issue is 8.85 MB of JavaScript on the homepage — cap it with a budget, don't migrate the site.
Progress re-check — 18 August
same tests, re-run two weeks on
Five of twelve findings fixed or materially improved
Every row re-tested with the identical command used on 4 August, so the comparison is like-for-like
F-02
Pricing is now coherent — and fixed better than I proposed
FIXED
Found
/pricing has been rebuilt as a real tiered rate card. Growth $3,600/mo ($3,000 annual, 2,000 resolutions, $0.89 overage) · Scale $9,000/mo ($7,500 annual, 8,000 resolutions, $0.69) · Enterprise custom, $0.49. Separate voice card at $0.89 / $0.59 / $0.35 per call. The homepage schema now matches the Growth tier exactly.
Evidence
Read every price occurrence in the visible copy of /pricing plus the JSON-LD on both pages
So what
$0.89, $0.69 and $0.49 are no longer three answers to one question — they are three tiers. The contradiction is gone. One residual: the homepage still leads with ‘$0.49 per resolution’, which is now identifiably the Enterprise floor, not the $0.89 entry rate.
F-06 / F-07 / F-08
The comparison pages landed — including the counterintuitive bit
FIXED
Found
Five pages now live at /compare/ (was one): ada, agentforce, decagon, intercom-fin, zendesk-ai. Each 4,257–5,881 words with 4–6 real HTML tables (the old /comparison had zero). Four of five now carry an explicit ‘choose <competitor> if…’ passage — that language appeared zero times across all five surfaces on 4 August. The Zendesk page’s URL, title and H1 now agree, and Ada’s stale ‘Feb 2025’ stamp is gone.
Evidence
sitemap parse, fetched all five pages, counted tables and words, keyword-scanned for concession language, read titles and H1s
So what
The concession sections were the single most counterintuitive recommendation in the pack and they have been taken. That is the mechanism by which an AI engine treats a page as an evaluation rather than an ad.
N-01
New problem: the old blog versions were never redirected
NEW
Found
All four old comparison posts still return 200: /blog/fini-ai-vs-intercom-fin, /blog/fini-vs-zendesk-ai, /blog/fini-ai-vs-ada, /blog/fini-vs-agentforce-… Every competitor now has two live Fini comparison pages targeting the same query.
Evidence
curl status codes on all four old URLs — 200, no Location header
So what
Fix this first. The two versions split their own ranking signals, which is the most likely reason the new pages have not moved yet. A search for ‘best alternatives to Intercom Fin 2026’ still returns Lorikeet, Decagon, Sierra, Ada and Forethought — Fini absent. One hour of redirects.
F-01 / F-03 / F-11
The three cheapest fixes are untouched
NOT FIXED
Found
Hub links: all nine collection pages still render exactly one internal link and zero links to their children — including /compare, which links to none of its five new pages. Hero stats: homepage still reads ‘0% Resolution Rate’ with JS stripped. llms.txt: untouched since 24 June, still 104 URLs with 9 non-200, and the five new /compare pages are not listed in it at all.
Evidence
Raw-HTML anchor extraction on 9 hubs; JS-stripped homepage grep; Last-Modified header plus a link-check of all 104 llms.txt URLs
So what
These are a half-day, thirty minutes and two hours respectively. All three now also hold back the comparison work that was done — we built five strong pages, left them unreachable by link, and didn’t tell the models they exist.
What was published in the two weeks
Sitemap 1,555 → 1,583
Section
4 Aug
18 Aug
Change
/glossary
126
147
+21
/compare
2
6
+4 — hub plus five competitor pages
/guides
1,221
1,225
+4
/blog
127
127
unchanged — the four old comparison posts are still live
Not verified this cycle: third-party guides now report roughly 47 G2 reviews against the 13 I verified on 4 August — if accurate, the review sprint has been actioned and is the biggest change in the pack. G2 blocks automated fetch and I had no live browser session, so I have flagged it rather than claimed it. Same for the ad libraries and the LinkedIn profile.
The five things I’d do this week
In order — the first one is an hour and unblocks the work already done
301 the four old /blog comparison pages to their /compare equivalents1 hour — they are competing with the pages we just built
Turn on real links in the CMS collection listshalf a day — still unfixed, and now also gating the five new comparison pages
Server-render the hero stats30 minutes, on the claim everything rests on
Regenerate llms.txt with the /compare set in it2 hours — we built the content and didn’t tell the models
Add FAQPage schema to the five comparison pages2 hours — the content is written; this is the markup that gets it quoted
The revenue maths
80% renew after year 1 · 60% after year 2
The retention curve gives a three-year customer — and that makes the business work
Under a flat-renewal assumption this looked like a business that couldn’t reach $5M. With the actual curve, steady-state ARR is $3.12M × 3.00 = $9.36M — the target is passed comfortably, on today’s pipeline.
$5M arrives in Year 2 — without adding a single lead
Cohort waterfall · each cohort decays at its own renewal rate
Cohort
End Y1
End Y2
End Y3
End Y4
Opening base
$1.60
$0.96
$0.58
$0.35
Y1 new
$3.12
$2.50
$1.50
$0.90
Y2 new
—
$3.12
$2.50
$1.50
Y3 new
—
—
$3.12
$2.50
Y4 new
—
—
—
$3.12
TOTAL ARR
$4.72
$6.58
$7.69
$8.36
$M. The opening base shrinks, but each new cohort more than replaces it. Pipeline volume is not the constraint — 3 leads a week already gets us past $5M.
What one customer is worth
$60k ACV · 20% gross margin · $17.5k CAC · 3.00-year life · LTV on gross profit
Lifetime value$36.0k
CAC$17.5k
Net per customer$18.5k
LTV:CAC 2.06× and +$18,500 net per customer. Payback is 17.5 months — longer than the first term, but comfortably inside a three-year life. That is a working-capital question, not a viability one.
On which LTV. This is gross-profit LTV ($12k/yr × 3.00). On revenue it would be $180,000, giving 10.29× — but CAC is a cash cost, so the comparison only means anything on profit, and the 3× benchmark is defined on gross margin. At a normal 80% SaaS margin the two differ by 1.25×; at our 20% they differ by five times. Both are in the workbook, labelled.
The year-2 renewal is the sensitive one
Year-1 renewal held at 80%; everything from year 3 compounds off year 2
Y2 renewal
Lifetime
LTV
LTV:CAC
Steady ARR
50%
2.60 yr
$31.2k
1.78×
$8.11M
60% today
3.00 yr
$36.0k
2.06×
$9.36M
70%
3.67 yr
$44.0k
2.51×
$11.44M
80%
5.00 yr
$60.0k
3.43×
$15.60M
60→70% adds two-thirds of a year of customer life. 60→80% doubles the tail and clears the 3.00× bar on retention alone. This is where retention effort pays best.
Closing the gap to a healthy 3.00×
Two routes, both computed live in the workbook
Lever
Today
Needs to reach
Gross margin
20%
29.2%
CAC per logo
$17.5k
$12.0k
Or year-2 renewal
60%
80%
Any one of the three gets there on its own. The audit fixes above are the CAC route — re-mixing channels toward review-led and GEO-led sourcing takes blended CAC from $17.5k toward $13.7k, which lifts LTV:CAC to 2.62×. Note that gets us closer but not all the way: channel mix alone does not reach 3.00× — margin or per-channel efficiency has to do the rest.
Caveat on that $13.7k. It rests on my assumed split of where logos come from today — only the 0% paid-search share is verified (Google Ads Transparency Center). One CRM query replaces it: closed-won by lead source, last 12 months, with spend by source. The workbook states the basis for every share and flags the outbound figure as the weakest.
The honest summary: this is a working business with a quality gap, not a broken one. The job is to protect an 80% first renewal, lift the second, and stop overpaying for logos.
ⓘ
Where I’d look first
One week, no budget
The drop from 80% to 60% at the second renewal is where the value leaks. Four queries separate the causes — all answerable from data Fini already has.
Product
Resolution rate, renewed vs churned accounts. If the ones that leave sit below the 90% guarantee, this is a product problem and no CS motion fixes it.
Expansion
ARR per account at month 24 vs month 1. Per-resolution pricing should grow with their ticket volume. Flat means nobody is working the account — and expansion is the cheapest ARR available.
Onboarding
Time-to-go-live, renewed vs churned. “Live in 14 days” is a strong promise; accounts that took longer start already disappointed.
Sourcing
Renewal rate by acquisition channel. 30% of logos come from outbound. If outbound renews worse, that alone rewrites the channel mix.
The order I'd do it in
nothing in week 1 needs budget
Week 1Free · ~1 day total
F-02
Publish one price everywhereGet the number from Deepak, then copy → schema → llms.txt → G2 → marketplaces
F-03
Server-render the hero statsFour text layers. Stops crawlers reading our resolution rate as 0%
F-01
Turn on real links in every CMS collection listNine pages. Makes 1,555 pages reachable without JS
—
Fix the LinkedIn profileLocation, headcount, and the "Chatbot Software" industry tag
—
Repair llms.txt2 dead links, 7 stale redirects, add the canonical facts block
F-04
Open the G2 review sprintCS-owned, 4 reviews a week, target 50
Days 1–30Build
F-05
Six comparison pages on one template, each with a "choose them if" section
—
Internal-linking template + lastmod in the sitemapOne template change upgrades 1,220 existing pages
—
Rebuild the ROI calculator — it's a 404 and it's listed in our llms.txt
—
Transcribe the 11 podcast episodes≈40,000 words of original, attributable content we already own
—
Attribution before the campaigns, not after
Days 30–90Spend
—
Google Ads: brand defence + competitor terms, landing on the new compare pages
—
Founder-led LinkedIn cadence, every post landing on-domain
—
PR: Microsoft partnership, the bank logos, EU AI Act timing, and the listicles AI engines cite
—
One named bank case study with real numbersICICI or PostFinance — the asset no competitor can match
—
Re-run the prompt panel and report share-of-voice against today's baseline
Two limits, stated up front. PageSpeed Insights hit its daily API quota twice, so the load figures come from my own Playwright measurement rather than a Lighthouse score. And without Ahrefs or Semrush I inferred backlink authority from verifiable listings rather than an index. Everything else here was observed directly and every command is reproducible.
Full row-by-row comments, the page-by-page deep dive and all 35 tests with methods are in the workbook — Fini GTM — investigation, evidence & fix plan.xlsx