Internal briefing · engineering / product

Pankaj home-report thread is three bugs

The short-link failure is real and already diagnosed. It does not explain the Zillow free→paid upgrade miss, and it does not explain three Stripe checkouts with no user-visible payment history. Treat those as separate tickets. Do not pass every URL to Firecrawl.

What actually happened

One conversation, three incidents

Pankaj mixed them because they happened in one sitting. The teammate correctly closed the first one. Closing the thread there leaves the paid path unowned.

Incident What Pankaj saw Status in code Priority
Free report with redf.in + one Zillow URL Generate failed twice; reported to admin Host allowlist rejects redf.in before Firecrawl. Same listing on redfin.com works. P2
Free report with two full Zillow URLs, then upgrade Free report OK. Upgrade error. Three Stripe visits. “Credit was never used.” Not explained by the short-link bug. Unlock, checkout resume, and Inngest paid generation are still in play. P0
No payment history on dashboard Could not tell if Stripe charged 0, 1, or 3 times Dashboard lists reports and bundle credit count. No Stripe charge ledger. P1

Source: Slack 19 Aug 2026 · Rivvl listingFetcher.ts, stripe/checkout/route.ts, dashboard/page.tsx

Incident 1

Short links are a resolve-then-allowlist problem

The teammate is right about the cause. fetchHomeListing runs assertHttpsListingUrl first. Trusted roots are redfin.com, realtor.com, and zillow.com with a strict suffix match. redf.in is not a suffix of redfin.com, so the URL never reaches Firecrawl. The compare API itself only checks “is this http(s) and not localhost,” so the failure shows up mid-generation as a listing fetch error, not as a form validation error.

Pankaj’s proposed fix — “do all and every single URL and pass it to Firecrawl” — is the wrong direction. Vehicle scrape already uses a private-IP / localhost block. Homes listing fetch is stricter on purpose: it is a user-controlled URL that the server then fetches. An open allowlist is SSRF (internal hosts, metadata IPs, attacker-controlled pages that poison extraction). The allowlist comment in code exists to block redfin.com.evil.net as well as random hosts.

Correct shape

  1. Accept listing URLs plus known short hosts (redf.in, and later Zillow/Realtor shorteners if they show up in tickets).
  2. Server-side HTTPS GET/HEAD, follow redirects, keep the final URL.
  3. Re-run the existing trusted-host check on the final hostname.
  4. Only then scrape. Reject anything that does not land on Zillow / Redfin / Realtor.

Do not scrape the short URL itself. Firecrawl on redf.in still depends on redirect follow; the product contract should be “final listing page on a trusted host.”

Keep host allowlist on the resolved URL Add short-domain + redirect resolve Reject scrape any pasted URL

Incident 2 · P0

The Zillow upgrade miss is not the short-link bug

Pankaj later used two full zillow.com/homedetails/… URLs. Free generation succeeded. Upgrade failed. That path never hits the redf.in allowlist. Saying “there is no issue with report generation” is true for listing scrape and false for paid unlock.

Checkout already tries to reuse an open Stripe session stored on the report as upgradeSessionId, and it 409s when a prior session is settled. If the previous session is expired, abandoned, or complete-but-not-yet-settled, the code increments upgradeCheckoutAttempt and creates a new Checkout session. That matches “I went to Stripe three times and never got the previous payment.”

The copy “Your credit was not used” is about Rivvl report / bundle credits, not about whether Stripe captured a card. After a checkout error that is the wrong sentence. It is why Pankaj could not tell money-moved from generation-failed.

Hypothesis Would look like Where to look
Checkout created, payment never completed Three Checkout URLs, zero checkout.session.completed, no charge Stripe customer for Pankaj · session status open / expired
Payment captured, unlock never ran Paid session, report still free_complete verify-session, webhook, homes/report/[id] unlock
Unlock queued, paid generation crashed Paid, fullGenerationStatus not upgrade_complete, Inngest error home-report-upgrade · reportUpgradeRunner · Vercel / Inngest logs
New checkout because old session was not open Multiple sessions, same reportId, rising upgradeCheckoutAttempt stripe/checkout/route.ts resume branch

Do not patch until one of these is confirmed in logs. The screenshot Pankaj mentioned is the fastest discriminator.

Incident 3 · P1

Users cannot see whether they were charged

The dashboard loads profile, reports, and bundle_purchases.quantity. It does not list Stripe Checkout sessions, PaymentIntents, or charges. Bundle credits and “credit was not used” are a different ledger from card captures. Pankaj’s request here is correct: a payment history (date, amount, status, report id, Stripe receipt) is how a user tells “I bounced off Checkout” from “you charged me three times.”

Until that exists, ops should answer Pankaj from Stripe: list Checkout sessions for his customer id. Incomplete / expired sessions are not charges. paid + not refunded is a charge. If any paid session has no matching unlocked report, that is a refund or a retry-unlock, not a “try again and hope.”

Next lookup

What to pull before writing code for the upgrade

Pankaj asked to look at the logs. That is the right next step for incident 2. Incident 1 does not need logs; the allowlist is the proof.

  1. Admin ticket / error report he filed during the failed free runs (timestamp + generation id).
  2. Vercel logs around those timestamps for homes-listing “Blocked listing URL” vs later upgrade errors.
  3. Inngest home-report-upgrade runs for the successful free report id.
  4. Stripe: Checkout sessions for his customer, payment_status, refunds, and metadata reportId.
  5. PostHog: checkout_started / checkout_completed with checkoutFunnel = report_upgrade.

Decision

What to agree in the thread

Agree with the teammate on cause for redf.in. Disagree with Pankaj on “no URL restrictions.” Split ClickUp into: resolve known short links then re-check trusted hosts; investigate the paid unlock with logs/Stripe (do not guess); add a user-visible payment history so Checkout retries are not a black box.

The free Zillow success already proved scrape + free generation. The remaining product risk is “I paid and I still have a free report,” plus “I cannot see the charge.” That is worse than a short link bouncing, and it is still open.