# Freemius Troubleshooting

> Diagnose and fix common stumbles when integrating Freemius with the JS SDK (`@freemius/sdk`) + React Checkout SDK (`@freemius/checkout`) on a Hono + Vite + Prisma stack. Use when a Freemius integration misbehaves — checkout redirect or webhooks not firing, entitlement not syncing, a TypeScript error on an SDK type, a missing webhook event, Prisma/driver-adapter failures, or Next.js → Vite/Hono porting issues. Seeded from the Freemius Lovable FAQ plus real findings captured while building the reference app with these Skills.

- Skill: `freemius/freemius-troubleshooting` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add freemius/freemius-troubleshooting`
- Raw SKILL.md: https://api.skillmd.com/api/skills/freemius/freemius-troubleshooting/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: freemius (https://skillmd.com/u/freemius)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/freemius/freemius-troubleshooting

---


# Freemius Troubleshooting

When a Freemius integration breaks, the failure is almost always in one of five
places: **webhooks/redirect plumbing**, **SDK types/events**, **the
stack/porting layer**, **checkout/portal wiring**, or **secrets/dashboard
config**. This Skill is a diagnostic index — find the symptom, jump to the
reference, apply the fix.

Source FAQ:
<https://freemius.com/help/documentation/ai/lovable/#troubleshooting>

## How to use this Skill

1. Match your symptom in the table below.
2. Open the linked reference for the root cause + fix.
3. If it is a `freemius-core` / `freemius-checkout` / `freemius-customer-portal`
   concern, the reference points you back to the exact section of those Skills.

## Symptom → fix index

| Symptom                                                                                                                                             | Likely cause                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | Reference                                                                                                                               |
| --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| Checkout completes but the user never gets access                                                                                                   | Redirect URL or webhook not configured / not public                                                                                                                                                                                                                                                                                                                                                                                                                                         | [references/webhooks-and-redirect.md](references/webhooks-and-redirect.md)                                                              |
| Webhook signature verification fails                                                                                                                | Raw body was parsed before handing to the listener                                                                                                                                                                                                                                                                                                                                                                                                                                          | [references/webhooks-and-redirect.md](references/webhooks-and-redirect.md)                                                              |
| TS error: `Type '"lifetime"' is not assignable` on an entitlement `type`                                                                            | Entitlement `type` is `subscription \| oneoff`, not `lifetime`                                                                                                                                                                                                                                                                                                                                                                                                                              | [references/sdk-types-and-events.md](references/sdk-types-and-events.md)                                                                |
| Subscribing to `license.quota.changed` does nothing / type error                                                                                    | That event does not exist in `@freemius/sdk@0.3.0`                                                                                                                                                                                                                                                                                                                                                                                                                                          | [references/sdk-types-and-events.md](references/sdk-types-and-events.md)                                                                |
| TS error: `Object is possibly 'false'` on `license` in a webhook handler                                                                            | Array-subscription `license` is a union incl. the deleted shape                                                                                                                                                                                                                                                                                                                                                                                                                             | [references/sdk-types-and-events.md](references/sdk-types-and-events.md)                                                                |
| `prisma generate` / client fails wanting a driver adapter; `url` rejected                                                                           | Prisma 7 dropped classic `datasource url` — pin Prisma 6                                                                                                                                                                                                                                                                                                                                                                                                                                    | [references/stack-and-porting.md](references/stack-and-porting.md)                                                                      |
| `process.env.NEXT_PUBLIC_*` is `undefined` in the browser                                                                                           | Vite uses `import.meta.env.VITE_*`, not `process.env`                                                                                                                                                                                                                                                                                                                                                                                                                                       | [references/stack-and-porting.md](references/stack-and-porting.md)                                                                      |
| `@/...` or `@shared/...` import cannot be resolved                                                                                                  | Missing alias in `vite.config.ts` + `tsconfig` paths                                                                                                                                                                                                                                                                                                                                                                                                                                        | [references/stack-and-porting.md](references/stack-and-porting.md)                                                                      |
| Installed skills via `npx skills add` but Claude Code (CLI) doesn't see them / slash commands missing                                               | Claude Code isn't a default install target; skills went to `~/.agents/skills/` with no `~/.claude/skills/` symlink                                                                                                                                                                                                                                                                                                                                                                          | [references/stack-and-porting.md](references/stack-and-porting.md)                                                                      |
| Tried the `npx skills add … -a claude-code` CLI route on **Claude Desktop** (Claude.ai app) and it doesn't apply                                    | The CLI / `-a claude-code` flag is a Claude Code concept only; Desktop installs each Skill as a per-Skill `.zip` via **Customize → Skills → "+" → Upload a skill** (core first; Code execution enabled under Settings → Capabilities)                                                                                                                                                                                                                                                       | [references/stack-and-porting.md](references/stack-and-porting.md)                                                                      |
| Secret key / API token leaks to the browser, or 401 from Freemius                                                                                   | A Freemius call was made client-side instead of server-side                                                                                                                                                                                                                                                                                                                                                                                                                                 | [references/checkout-and-portal.md](references/checkout-and-portal.md)                                                                  |
| Checkout overlay opens empty / unconfigured                                                                                                         | Overlay not primed from a server-created `CheckoutSerialized`                                                                                                                                                                                                                                                                                                                                                                                                                               | [references/checkout-and-portal.md](references/checkout-and-portal.md)                                                                  |
| Upgrade route 500s: "Payment method update is not allowed for lifetime licenses"                                                                    | `checkout.create({ licenseId })` calls a payment-method-update endpoint that rejects renewing licenses (`expiration: null`) — use `setLicenseUpgradeByKey`                                                                                                                                                                                                                                                                                                                                  | [references/checkout-and-portal.md](references/checkout-and-portal.md)                                                                  |
| User "upgraded" but now has two subscriptions / double-billed                                                                                       | Upgrade offered as a **plain subscribe** checkout to an already-subscribed user — use a license-upgrade checkout (`setLicenseUpgradeByKey`) + entitlement-aware surfaces                                                                                                                                                                                                                                                                                                                    | [references/checkout-and-portal.md](references/checkout-and-portal.md)                                                                  |
| Portal actions 404 / token invalid                                                                                                                  | One `/api/portal` endpoint must serve data **and** signed actions                                                                                                                                                                                                                                                                                                                                                                                                                           | [references/checkout-and-portal.md](references/checkout-and-portal.md)                                                                  |
| One-off / lifetime purchase doesn't behave like the subscription demo                                                                               | Different modality — use `billing_cycle: 'lifetime'`, type oneoff                                                                                                                                                                                                                                                                                                                                                                                                                           | [references/checkout-and-portal.md](references/checkout-and-portal.md)                                                                  |
| Upgraded to **Lifetime** but still gated / credits still charged / not shown as active (invoice is correct)                                         | `freemius.entitlement.getActive()` filters to `type: 'subscription'` — a one-off Lifetime row is invisible, so `getUserEntitlement()` returns null                                                                                                                                                                                                                                                                                                                                          | [references/sdk-types-and-events.md](references/sdk-types-and-events.md)                                                                |
| Coupon'd subscription "renews" ~100 years out (`USD 0 · renews 2126`)                                                                               | 100%-off coupon scoped to "First payment **and renewals**"                                                                                                                                                                                                                                                                                                                                                                                                                                  | [references/checkout-and-portal.md](references/checkout-and-portal.md)                                                                  |
| Re-subscribe blocked ("already on an active subscription") after a cancel                                                                           | Admin cancelled the **license**, leaving the **subscription** orphaned-active                                                                                                                                                                                                                                                                                                                                                                                                               | [references/checkout-and-portal.md](references/checkout-and-portal.md)                                                                  |
| Subscribed user still sees the paywall / 402 despite an active subscription; `user_fs_entitlement` table is empty                                   | Checkout used the bare hosted redirect with no overlay `purchaseCompleted` sync and no Dashboard checkout-redirect URL — nothing ever wrote the entitlement row                                                                                                                                                                                                                                                                                                                             | [references/checkout-and-portal.md](references/checkout-and-portal.md)                                                                  |
| `user_fs_entitlement` empty for **every** user; sync POST returns `400 "License ID is required in the request body"` (frontend `.catch()` hides it) | The overlay `purchaseCompleted` POST was routed through `checkout.request.createProcessor` / `processRedirect` (or `syncEndpoint` pointed at `/api/checkout/redirect?action=process_purchase`). That path expects a **signed redirect / `license_id` in the body** the overlay payload lacks. Overlay sync must be a plain `POST /api/purchase` that reads the license from the payload → `syncEntitlementByLicenseId` — **separate endpoint from the hosted `GET /api/checkout/redirect`** | [freemius-checkout → references/checkout-provider.md](../freemius-checkout/references/checkout-provider.md)                             |
| A gated feature (e.g. image OCR on Premium) shows a generic **"OCR failed. Please try another file."**                                              | The server's tier/entitlement gate (`403`/`402` with a reason like `tier_upgrade_required`/`image_requires_pro`) was swallowed into the catch-all error. A gate is not a failure — branch on status+reason and open the **paywall with an upgrade path**, not the error toast                                                                                                                                                                                                               | [freemius-checkout → references/paywall.md](../freemius-checkout/references/paywall.md)                                                 |
| Purchase succeeds but there's **no pricing page** / no way to browse or buy plans without hitting a paywall first                                   | Build shipped the paywall but skipped the standalone pricing route — a dedicated `/pricing` page (Subscribe + Topup tables from `GET /api/checkout/pricing`) + nav link is a **required deliverable**                                                                                                                                                                                                                                                                                       | [freemius-checkout → references/pricing-tables.md](../freemius-checkout/references/pricing-tables.md)                                   |
| Success toast says **"credits added to your account"** after buying a **subscription** (or wrong copy for Lifetime)                                 | Post-purchase copy didn't branch on the synced purchase type — use the re-fetched entitlement tier: `lifetime` → "Lifetime access unlocked", subscription → "&lt;tier&gt; unlocked", credits pack → "page credits added"                                                                                                                                                                                                                                                                    | [freemius-checkout → references/checkout-provider.md](../freemius-checkout/references/checkout-provider.md)                             |
| Credit balance shows the **old value** after an OCR (metered) run until a manual page refresh                                                       | The server debits credits correctly (`credit -= pages`), but the metered action's success handler never refreshes the displayed balance — the badge is stale until a full reload. Refresh the display without a reload: either read the new balance from the action's response, or `await` the same `refreshEntitlement()` (`GET /api/entitlement` → `credit`) the purchase flow uses. This is the consumption twin of the post-purchase entitlement refresh                                | [freemius-checkout → references/paywall.md](../freemius-checkout/references/paywall.md)                                                 |
| **`/pricing` shows nothing** for a Lifetime / top-tier holder ("nothing is shown anymore")                                                          | Every buy surface is correctly hidden (they own the top tier), but hiding them all leaves a **blank page**. Render a friendly "you own everything" empty-state instead — never a blank page. (A credits-only viewer must still see Subscribe + Topup.)                                                                                                                                                                                                                                      | [freemius-checkout → references/pricing-tables.md](../freemius-checkout/references/pricing-tables.md)                                   |
| Account-page **"Change plan"** renders as a bare **underlined link with no padding**, misaligned next to Cancel                                     | "Change plan" is a router `<Link>` given a button class (e.g. `btn-outline`) that only inherits padding / radius / `text-decoration:none` from a base `.btn` class — applied alone to an `<a>` it loses all of it. Make the button/outline class **self-contained**, or put both base+modifier classes on the `<Link>`                                                                                                                                                                      | [freemius-customer-portal → references/subscription-and-upgrade.md](../freemius-customer-portal/references/subscription-and-upgrade.md) |
| Retention coupon: clicking **"Apply discount & keep my plan"** just closes the wizard, **nothing happens**, Dashboard shows **0 redemptions**       | The apply POST sent an **empty `{}` body** — the SDK's renewal-coupon action requires `{ couponId: <coupons[0].coupon_id> }` and 400s otherwise; AND the wizard ignored `res.ok`, so the failed call closed as if it succeeded. Send the real `couponId` and check `res.ok` (show error + keep open on failure)                                                                                                                                                                             | [freemius-customer-portal → references/cancellation-and-coupon.md](../freemius-customer-portal/references/cancellation-and-coupon.md)   |
| After a **hosted checkout/upgrade** the browser lands on `/checkout-result?success=true` showing a **blank page with only the header**              | The server GET redirect handler bounces to `/checkout-result`, but the SPA router (`App.tsx` `<Routes>`) has **no matching client route** — the redirect target is a real page the build forgot to ship. Add a `/checkout-result` route that reads `success`, refreshes entitlement once on mount, shows the confirmation, and routes back into the app                                                                                                                                     | [freemius-core → references/purchase-processing.md](../freemius-core/references/purchase-processing.md)                                 |
| Entitlement doesn't update after a cancel/renew; webhook seems dead — but handler, URL and signature all check out                                  | Lifecycle event fired before the webhook URL was active/propagated in the Dashboard — never delivered (not retro-sent)                                                                                                                                                                                                                                                                                                                                                                      | [references/webhooks-and-redirect.md](references/webhooks-and-redirect.md)                                                              |

## First-resort checks (the Lovable FAQ baseline)

The official FAQ's top recommendations, applied to this stack:

1. **Ask the agent to re-run the failing step.** These Skills are modular —
   re-invoke the specific reference (e.g. `freemius-core` →
   `purchase-processing.md`) rather than re-doing the whole integration.
2. **Webhooks + checkout redirect are the two critical paths.** Verify both end
   to end first; most "no access after paying" reports are one of these
   misconfigured. Freemius emails you when webhook processing fails — paste that
   email content into the agent to pinpoint the failure.
3. **After publishing/deploying, update all URLs.** A redirect or webhook URL
   still pointing at `localhost` (or an old domain) silently breaks the flow.
   `PUBLIC_APP_URL` must be the live domain.
4. **One-off purchases need a different approach** than the subscription path —
   see the checkout/portal reference for the `lifetime` modality.
5. **Still stuck?** Contact Freemius support (support@freemius.com) with the
   product id, the failing step, and screenshots.

## Capturing new entries

This Skill is meant to grow. When a Skill defect is found during a validation
gate, log it in the gate's "Skill defects found" table, fix the offending Skill,
then add a troubleshooting entry here (symptom → cause → fix) so the next
integrator skips the stumble.

