[{"data":1,"prerenderedAt":717},["ShallowReactive",2],{"\u002Fblog\u002Fstripe-subscriptions-in-nuxt-pricing-checkout-and-webhooks-data":3},{"post":4,"surround":706},{"id":5,"title":6,"alternates":7,"authors":8,"badge":14,"body":16,"date":665,"dateModified":666,"description":26,"extension":667,"head":666,"hero_image_url":25,"json_ld":668,"meta":690,"navigation":691,"ogImage":666,"outbound_links":692,"path":693,"primary_keyword":694,"related_articles":695,"robots":666,"schemaOrg":666,"search_intent":696,"seo":697,"sitemap":698,"stem":699,"supporting_keywords":700,"tags":704,"__hash__":705},"blog_en\u002Fblog\u002Fstripe-subscriptions-in-nuxt-pricing-checkout-and-webhooks.md","Stripe subscriptions in Nuxt: pricing, checkout, and webhooks",[],[9],{"name":10,"to":11,"avatar":12},"Tom Han","https:\u002F\u002Fx.com\u002Ftomhan245",{"src":13},"https:\u002F\u002Fcdn.shipahe.ad\u002Ftomhan.webp",{"label":15},"How-to",{"type":17,"value":18,"toc":638},"minimark",[19,28,32,37,40,86,89,93,96,145,181,185,188,193,232,236,305,308,312,315,319,385,389,413,443,450,454,457,518,522,563,567,570,574,591,594],[20,21,22],"figure",{},[23,24],"img",{"src":25,"alt":26,"style":27},"https:\u002F\u002Fshipahe.ad\u002Fimages\u002Fblog\u002Fstripe-subscriptions-in-nuxt-pricing-checkout-and-webhooks\u002Fpost-821.webp","Set up Stripe subscriptions in Nuxt 3 the right way: pricing, Checkout, webhooks, trials, taxes, emails, portal, and admin so you can launch fast.","max-width:100%;border-radius:12px",[29,30,31],"p",{},"Subscriptions bring predictable revenue, but wiring them into a Nuxt app goes wrong in small ways that cost you time and churn. The biggest mistakes we see: trusting a success URL, mixing test and live IDs, and letting the client choose any price. Here is a working baseline for Stripe subscriptions in Nuxt 3, with the practical decisions we ship in our Nuxt SaaS starter so you can move fast and still sleep at night.",[33,34,36],"h2",{"id":35},"_1-plan-pricing-tiers-and-trials","1) Plan pricing tiers and trials",[29,38,39],{},"Design the model before you code. Your pricing rules drive navigation, access control, and analytics. Write them down so the app, Stripe, and support scripts all match.",[41,42,43,44,43,52,43,58,43,64,43,75],"ul",{},"\n  ",[45,46,47,51],"li",{},[48,49,50],"strong",{},"Map features to tiers."," Decide what Free, Starter, and Pro actually unlock. Every paid feature should live behind protected routes and server-side checks. A simple pattern: public marketing pages, an authenticated area for Free, and a premium area visible only when a subscription is active.",[45,53,54,57],{},[48,55,56],{},"Intervals and amounts."," Lead with monthly. Offer annual with a clear percentage discount, not a vague “save big.” Keep names human: Starter $12\u002Fmonth, Team $29\u002Fmonth. Avoid more than three tiers at launch.",[45,59,60,63],{},[48,61,62],{},"Trials."," Choose length (7 or 14 days are common), whether a card is required, and enforce one trial per account. If abuse is a risk, limit trials to verified email domains or one per payment method.",[45,65,66,69,70,74],{},[48,67,68],{},"Seats vs usage."," For per-seat pricing, you will pass a ",[71,72,73],"em",{},"quantity"," to Checkout and keep a seat count in your database. For usage, plan on metered billing and a daily job that reports usage to Stripe.",[45,76,77,80,81,85],{},[48,78,79],{},"International copy and currency."," If you localize, write pricing copy in your translation files now. Use ",[82,83,84],"code",{},"Intl.NumberFormat"," for currency display and show the currency you actually bill in.",[29,87,88],{},"Using a Nuxt SaaS starter kit means you can plug your tiers into a prebuilt pricing page, wire the calls to action once, and keep the offer consistent across the site and app.",[33,90,92],{"id":91},"_2-create-products-and-prices-in-stripe","2) Create products and prices in Stripe",[29,94,95],{},"Stripe uses Products and Prices. One Product per tier, multiple Prices for intervals or currencies.",[97,98,43,99,43,109,43,119,43,133,43,139],"ol",{},[45,100,101,104,105,108],{},[48,102,103],{},"Create products."," Add a Product for each tier. Match names and descriptions to what users see in-app. Use clear internal metadata like ",[82,106,107],{},"plan_key"," so support can search quickly.",[45,110,111,114,115,118],{},[48,112,113],{},"Add recurring prices."," For each Product, create Prices for monthly and annual with the right currency and amount. If you offer trials, set ",[71,116,117],{},"trial_period_days"," on the Price or pass it in Checkout when you create the session.",[45,120,121,124,125,128,129,132],{},[48,122,123],{},"Use lookup keys."," Set a ",[82,126,127],{},"lookup_key"," like ",[82,130,131],{},"starter_monthly"," instead of hardcoding Price IDs in code. You can then reference prices by lookup key in different environments safely.",[45,134,135,138],{},[48,136,137],{},"Taxes and addresses."," If you collect tax, enable Stripe Tax or configure manual tax rates. Require billing address and VAT\u002Ftax IDs in Checkout when needed.",[45,140,141,144],{},[48,142,143],{},"Stay in test mode."," Do all setup in test first. Keep a checklist of Price IDs or lookup keys to avoid mixing with live later.",[29,146,147,148,151,152,154,155,157,158,157,161,157,164,157,167,157,170,157,173,176,177,180],{},"In your app, keep a ",[71,149,150],{},"plans"," table that maps a ",[82,153,107],{}," to human labels and Stripe IDs. Suggested fields: ",[82,156,107],{},", ",[82,159,160],{},"stripe_product_id",[82,162,163],{},"stripe_price_id_monthly",[82,165,166],{},"stripe_price_id_annual",[82,168,169],{},"interval",[82,171,172],{},"features",[82,174,175],{},"active",". This lets you render pricing accurately and validate that any ",[82,178,179],{},"priceId"," coming from the client is actually one you sell.",[33,182,184],{"id":183},"_3-implement-a-subscription-checkout-in-nuxt","3) Implement a subscription checkout in Nuxt",[29,186,187],{},"Use a server route to create a Checkout Session. Never expose secret keys in the browser. Never grant access based on a success URL.",[189,190,192],"h3",{"id":191},"client-flow","Client flow",[41,194,43,195,43,211,43,218,43,221],{},[45,196,197,198,201,202,204,205,207,208,210],{},"User clicks a plan button. The client calls ",[82,199,200],{},"POST \u002Fapi\u002Fbilling\u002Fcheckout"," with a safe payload: ",[82,203,107],{}," (or a known ",[82,206,127],{},"), optional ",[82,209,73],{},", and the current organization or user ID.",[45,212,213,214,217],{},"The server validates the plan against your database, resolves to a Stripe Price (monthly or annual), and creates a Checkout Session in ",[71,215,216],{},"subscription"," mode. Return only the session URL.",[45,219,220],{},"Redirect to the session URL. Stripe handles SCA and 3D Secure.",[45,222,223,224,227,228,231],{},"Stripe sends users back to your ",[82,225,226],{},"success_url"," or ",[82,229,230],{},"cancel_url",". Show a friendly state, but do not activate access here. Wait for the webhook.",[189,233,235],{"id":234},"server-considerations","Server considerations",[41,237,43,238,43,248,43,268,43,277,43,283,43,289],{},[45,239,240,243,244,247],{},[48,241,242],{},"Associate customers."," When creating the session, pass the authenticated email and a saved ",[82,245,246],{},"customer"," ID if you have one. Returning customers keep their payment method and invoices in one place.",[45,249,250,253,254,227,257,260,261,263,264,267],{},[48,251,252],{},"Metadata."," Include ",[82,255,256],{},"user_id",[82,258,259],{},"org_id"," and ",[82,262,107],{}," in ",[82,265,266],{},"metadata"," so webhook handlers can join events to your records without extra queries.",[45,269,270,273,274,276],{},[48,271,272],{},"Quantities and seats."," For seat-based plans, pass ",[82,275,73],{}," to Checkout and store it on your subscription record. Update when admins add or remove seats.",[45,278,279,282],{},[48,280,281],{},"Billing Portal."," Enable the Stripe Billing Portal and add a button in your app’s billing page so customers can update cards, change plans, or cancel without support.",[45,284,285,288],{},[48,286,287],{},"Protect content."," Gate premium routes via server middleware and API guards. Hide UI affordances, but always enforce on the server too.",[45,290,291,294,295,260,298,263,301,304],{},[48,292,293],{},"Environment config."," Keep ",[82,296,297],{},"STRIPE_SECRET_KEY",[82,299,300],{},"STRIPE_WEBHOOK_SECRET",[82,302,303],{},"runtimeConfig",". Separate test and live values by environment.",[29,306,307],{},"A Nuxt SaaS starter kit like shipahe.ad includes subscription checkout for one-time and recurring payments, swappable providers behind a small interface, user authentication, and protected pages. You focus on plans and copy, not plumbing.",[33,309,311],{"id":310},"_4-handle-webhooks-for-lifecycle-events","4) Handle webhooks for lifecycle events",[29,313,314],{},"Webhooks are the source of truth for access. Create a secure endpoint, verify signatures against the raw request body, and make idempotent updates to your database.",[189,316,318],{"id":317},"events-to-handle","Events to handle",[41,320,43,321,43,338,43,363,43,369,43,379],{},[45,322,323,326,327,260,329,331,332,227,334,337],{},[48,324,325],{},"checkout.session.completed",". Read the session, capture ",[82,328,246],{},[82,330,216],{},", and attach the Stripe customer ID to your user or organization. Set subscription status to ",[71,333,175],{},[71,335,336],{},"trialing"," based on the object.",[45,339,340,343,344,157,347,157,350,157,353,157,356,359,360,362],{},[48,341,342],{},"customer.subscription.updated",". Track ",[82,345,346],{},"status",[82,348,349],{},"current_period_end",[82,351,352],{},"cancel_at_period_end",[82,354,355],{},"trial_end",[82,357,358],{},"items.price.id",", and ",[82,361,73],{},". This powers upgrade\u002Fdowngrade UI, trial banners, and renewal notices.",[45,364,365,368],{},[48,366,367],{},"invoice.payment_succeeded",". Confirm continued access, increment MRR metrics, and optionally send a payment confirmation email with a link to invoices.",[45,370,371,374,375,378],{},[48,372,373],{},"invoice.payment_failed",". Mark the account ",[71,376,377],{},"past_due",", show in-app notices, and email a short, actionable message with a link to update the card. Do not immediately revoke access if Stripe will retry.",[45,380,381,384],{},[48,382,383],{},"customer.subscription.deleted",". Move users to Free and remove premium capabilities. Preserve data but hide premium features.",[189,386,388],{"id":387},"verification-idempotency-and-retries","Verification, idempotency, and retries",[41,390,43,391,43,397,43,407],{},[45,392,393,396],{},[48,394,395],{},"Verify signatures."," Use your webhook signing secret to verify requests with the raw payload. In Nuxt, read the raw body before JSON parsing for signature checks.",[45,398,399,402,403,406],{},[48,400,401],{},"Idempotent updates."," Store processed Stripe ",[82,404,405],{},"event.id"," values in a table. If Stripe retries, your handler should be safe to run again.",[45,408,409,412],{},[48,410,411],{},"Fast acks, background jobs."," Do small updates inline, queue heavy work (reports, large emails) to a worker, and return 2xx quickly so Stripe stops retrying.",[29,414,415,416,419,420,157,423,157,426,157,429,157,431,157,433,157,435,359,437,439,440,442],{},"Keep a ",[71,417,418],{},"subscriptions"," table with fields like ",[82,421,422],{},"stripe_customer_id",[82,424,425],{},"stripe_subscription_id",[82,427,428],{},"price_id",[82,430,346],{},[82,432,349],{},[82,434,352],{},[82,436,355],{},[82,438,73],{},". If you support teams, scope subscriptions to an ",[82,441,259],{}," instead of a user.",[29,444,445,446,449],{},"For local testing, use the Stripe CLI to forward events to ",[82,447,448],{},"localhost"," and trigger scenarios. A typical flow: listen, run through Checkout in test mode, and confirm your webhook updates the subscription row exactly once.",[33,451,453],{"id":452},"_5-receipts-dunning-upgrades-and-proration","5) Receipts, dunning, upgrades, and proration",[29,455,456],{},"After the basics work, refine renewals, failed payments, and plan changes so customers always know what will happen next.",[41,458,43,459,43,468,43,477,43,486,43,499],{},[45,460,461,464,465,467],{},[48,462,463],{},"Receipts."," Let Stripe email receipts automatically. If you also want in-app messages, send a transactional email on ",[71,466,367],{}," with invoice links and a clear subject.",[45,469,470,473,474,476],{},[48,471,472],{},"Dunning."," Start simple with Stripe’s retry schedule. On ",[71,475,373],{},", show a banner and send a concise email with a secure link to your billing page or portal. Escalate tone only after the final failed attempt.",[45,478,479,482,483,485],{},[48,480,481],{},"Upgrades and proration."," Stripe prorates by default when upgrading mid-cycle. Before confirming a change, call the upcoming invoice endpoint to show the customer what they will pay now and what renewals look like. Reflect ",[82,484,349],{}," in UI.",[45,487,488,491,492,495,496,498],{},[48,489,490],{},"Trials to paid."," Send onboarding during the trial (",[71,493,494],{},"trial_will_end",") and right after ",[71,497,325],{},". The goal is clear “first value” before the first charge.",[45,500,501,504,505,157,508,157,511,359,514,517],{},[48,502,503],{},"Analytics."," Track ",[82,506,507],{},"plan_selected",[82,509,510],{},"checkout_started",[82,512,513],{},"checkout_completed",[82,515,516],{},"subscription_activated",". Look for drop-offs between pricing and Checkout, and between Checkout and activation.",[189,519,521],{"id":520},"common-pitfalls-and-how-to-avoid-them","Common pitfalls and how to avoid them",[41,523,43,524,43,530,43,536,43,542,43,548,43,554],{},[45,525,526,529],{},[48,527,528],{},"Trusting the success page."," Only webhooks should activate access. Success URLs are easy to spoof.",[45,531,532,535],{},[48,533,534],{},"Mismatched environments."," Test keys with live Price IDs will fail. Keep test and live IDs and webhook secrets cleanly separated.",[45,537,538,541],{},[48,539,540],{},"Letting the client pick any price."," Resolve a server-side plan to a known Stripe Price. Reject unknown or mismatched IDs.",[45,543,544,547],{},[48,545,546],{},"Missing access checks."," Hide links in the UI, but also enforce access in server middleware and API handlers.",[45,549,550,553],{},[48,551,552],{},"No admin tooling."," Give support a way to look up users by email, see subscription status, resend emails, and apply courtesy extensions.",[45,555,556,559,560,562],{},[48,557,558],{},"Skipping local webhook tests."," Use a tunneling or CLI tool to forward events to ",[82,561,448],{}," and exercise success, failure, cancel, upgrade, and downgrade paths.",[189,564,566],{"id":565},"tie-it-together-with-a-nuxt-saas-starter-kit","Tie it together with a Nuxt SaaS starter kit",[29,568,569],{},"If you are weighing a Vue\u002FNuxt starter versus going from scratch, a ready-made Nuxt SaaS kit saves days of glue work. The shipahe.ad kit ships with user auth and protected pages, subscription Checkout flows for one-time and recurring payments, transactional email templates, an admin panel, scheduled cron jobs, and built-in analytics. Drop in your plans and copy, wire Stripe keys, and you have a working paywall with clear webhooks and a billing page that customers can self-serve from.",[189,571,573],{"id":572},"key-takeaways","Key takeaways",[41,575,43,576,43,579,43,582,43,585,43,588],{},[45,577,578],{},"Decide tiers, intervals, trials, and access rules first. Put paid features behind protected routes and server checks.",[45,580,581],{},"Create Products and recurring Prices in Stripe test mode, use lookup keys, and store mappings in your database.",[45,583,584],{},"Create Checkout Sessions from a server endpoint. Activate access from webhooks only, never from success URLs.",[45,586,587],{},"Handle core lifecycle events, verify signatures on raw bodies, and make idempotent updates to your subscription table.",[45,589,590],{},"Use the Billing Portal, transactional emails, and basic analytics to reduce churn and clarify charges.",[29,592,593],{},"The same plumbing powers a SaaS, a web app, or an AI tool. With a solid Nuxt starter and disciplined webhook handling, you can launch fast and charge with confidence.",[595,596,599,603,607,610,614,617,621,624,628,631,635],"section",{"className":597},[598],"post-faq",[33,600,602],{"id":601},"faq","FAQ",[189,604,606],{"id":605},"how-do-i-test-stripe-webhooks-locally-in-a-nuxt-app","How do I test Stripe webhooks locally in a Nuxt app?",[29,608,609],{},"Run your Nuxt server and use a tunneling or CLI tool to forward Stripe events to your local webhook URL. Verify signatures and log each event to confirm your update logic.",[189,611,613],{"id":612},"should-i-mark-the-user-active-after-checkout-success-or-only-on-webhooks","Should I mark the user active after checkout success or only on webhooks?",[29,615,616],{},"Only on webhooks. The success page can be spoofed. Use events like checkout.session.completed and customer.subscription.updated to flip access in your database.",[189,618,620],{"id":619},"how-do-i-handle-free-trials-in-stripe-with-nuxt","How do I handle free trials in Stripe with Nuxt?",[29,622,623],{},"Set a trial on the Price or pass a trial during Checkout Session creation. Store trial end dates from webhooks and show them in your billing UI so users know when they will be charged.",[189,625,627],{"id":626},"can-i-switch-payment-providers-later-without-rewriting-my-nuxt-front-end","Can I switch payment providers later without rewriting my Nuxt front end?",[29,629,630],{},"Yes if your server abstracts checkout creation and billing operations. A starter kit that supports multiple, swappable providers helps you keep the client code stable.",[189,632,634],{"id":633},"whats-the-best-way-to-protect-premium-pages-in-nuxt","What’s the best way to protect premium pages in Nuxt?",[29,636,637],{},"Use authentication and route middleware to check subscription status before rendering. Also lock down the server APIs behind those pages to prevent direct access.",{"title":639,"searchDepth":640,"depth":640,"links":641},"",2,[642,643,644,649,653,658],{"id":35,"depth":640,"text":36},{"id":91,"depth":640,"text":92},{"id":183,"depth":640,"text":184,"children":645},[646,648],{"id":191,"depth":647,"text":192},3,{"id":234,"depth":647,"text":235},{"id":310,"depth":640,"text":311,"children":650},[651,652],{"id":317,"depth":647,"text":318},{"id":387,"depth":647,"text":388},{"id":452,"depth":640,"text":453,"children":654},[655,656,657],{"id":520,"depth":647,"text":521},{"id":565,"depth":647,"text":566},{"id":572,"depth":647,"text":573},{"id":601,"depth":640,"text":602,"children":659},[660,661,662,663,664],{"id":605,"depth":647,"text":606},{"id":612,"depth":647,"text":613},{"id":619,"depth":647,"text":620},{"id":626,"depth":647,"text":627},{"id":633,"depth":647,"text":634},"2026-09-26",null,"md",{"@context":669,"@graph":670},"https:\u002F\u002Fschema.org",[671,675],{"@type":672,"headline":6,"description":26,"image":25,"inLanguage":673,"datePublished":674},"BlogPosting","en","2026-09-26 03:03:34",{"@type":676,"mainEntity":677},"FAQPage",[678,682,684,686,688],{"@type":679,"name":606,"acceptedAnswer":680},"Question",{"@type":681,"text":609},"Answer",{"@type":679,"name":613,"acceptedAnswer":683},{"@type":681,"text":616},{"@type":679,"name":620,"acceptedAnswer":685},{"@type":681,"text":623},{"@type":679,"name":627,"acceptedAnswer":687},{"@type":681,"text":630},{"@type":679,"name":634,"acceptedAnswer":689},{"@type":681,"text":637},{},true,[],"\u002Fblog\u002Fstripe-subscriptions-in-nuxt-pricing-checkout-and-webhooks","nuxt",[],"Informational",{"title":6,"description":26},{"loc":693},"blog\u002Fstripe-subscriptions-in-nuxt-pricing-checkout-and-webhooks",[694,701,702,418,703],"stripe","saas","webhooks",[694,701,702,418,703],"PXxTTExF61y2DB9oxsXGaJ2DGtLNR4GU9xF3I2OMCFY",[707,712],{"title":708,"path":709,"stem":710,"description":711,"children":-1},"Stripe SaaS: A Step-by-Step Guide for Nuxt Builders","\u002Fblog\u002Fstripe-saas-step-by-step-guide-nuxt-builders","blog\u002Fstripe-saas-step-by-step-guide-nuxt-builders","Build a Stripe SaaS with a Nuxt starter kit: auth, checkout, subscriptions, webhooks, and billing UX. Step-by-step guide to ship fast with confidence.",{"title":713,"path":714,"stem":715,"description":716,"children":-1},"Best Nuxt Starter Kits for SaaS Projects (2026)","\u002Fblog\u002Ftop-saas-starter-kits","blog\u002Ftop-saas-starter-kits","Looking for the best Nuxt starter kit? I've reviewed the top Nuxt boilerplates to help you find a stack that doesn't get in your way.",1790409692616]