AI DEVELOPMENT
Sep 23, 202610 min read22 reads

The Real Cost of Bad Software: Why Quality Pays Off

VS
Vikash Singh
Likes0
Shares0
The Real Cost of Bad Software: Why Quality Pays Off

TL;DR

The real cost of bad software is not the build price, it is everything after: wasted engineering time, slow delivery, lost customers, security risk, and technical debt. Poor software quality costs the US ~$2.41 trillion a year, and technical debt eats 20–40% of development time. Cheap software is a false economy; quality is what enables speed and growth over any real timeframe.

The Real Cost of Bad Software: Why Quality Pays Off

The real cost of bad software is almost never the price you paid to build it. It is everything that comes after: the slow delivery, the constant firefighting, the customers who quietly leave, the features you never ship because your team is busy patching. Bad software rarely fails in one loud, obvious moment. It drains you quietly, month after month, until one day the bill is enormous and no one can point to when it started.

Here is the scale, because it is genuinely staggering. Poor software quality costs the US economy an estimated $2.41 trillion a year, with roughly $1.52 trillion of that being technical debt, the accumulated cost of shortcuts and rushed work. For an individual business, unmanaged technical debt commonly consumes 20% to 40% of all development time, which means a large chunk of what you pay your engineers goes to servicing past mistakes instead of building your future. Quality is not a nice-to-have. It is one of the biggest hidden line items in your business.

This guide breaks down where the real cost of bad software actually hides, why cutting quality to save money almost always costs more, and how investing in quality pays off.

The quick answer: where bad software actually costs you

The price of bad software shows up in six places, most of them invisible on any invoice.

Wasted engineering time, as your team firefights bugs and works around fragile code instead of building. Slower delivery, as every change takes longer on a shaky foundation. Lost customers, who leave quietly after a crash, a slow page, or a broken checkout. Security and compliance risk, as weak, outdated code becomes a breach waiting to happen. Failed projects and features never shipped, as quality problems eat the roadmap. And reputation damage, as public failures become the story customers tell about you.

Notice the pattern: almost none of these appear on the development budget. That is exactly why bad software is so dangerous, its cost is real but hidden, so it grows unchecked until it becomes a crisis.

Why bad software is a business problem, not a technical one

It is tempting to file "software quality" under engineering and move on. That is the mistake that lets the cost grow.

Every technical problem is really a business problem wearing a technical disguise. A slow API is not an engineering detail; it is abandoned transactions and lost customers. A flaky checkout is not a bug; it is revenue leaking every day. Data sync errors are not a backend issue; they are eroded customer trust and a flood of support tickets. The technical symptom always has a business consequence attached, and the business consequence is usually far more expensive than the fix would have been.

This is why quality decisions cannot be left as purely technical ones. When a team cuts corners to hit a date, the saving is visible and immediate, and the cost is invisible and deferred, which makes cutting quality feel free. It is not free. It is a loan against your future, and the interest is brutal.

The biggest hidden cost: technical debt

Of all the costs of bad software, technical debt is the largest and the most invisible, so it deserves its own explanation.

Technical debt is the accumulated cost of shortcuts, quick fixes, and rushed decisions in your code. Like financial debt, it is not necessarily bad to take on deliberately, sometimes shipping fast is worth it, but it charges interest, and unmanaged debt compounds. The interest shows up as every future change taking longer, every new feature being harder to add, and every fix risking breaking something else.

The numbers are sobering. Technical debt alone accounts for roughly $1.52 trillion in the US, and it commonly consumes 20% to 40% of a team's development time. Put concretely: if you have a team of ten developers and technical debt eats 30% of their time, that is three full engineers' worth of salary going to servicing past shortcuts instead of building your product, every single year. And because it accrues quietly, delivery just slowly getting slower, most businesses do not notice until a migration, an audit, or an incident forces a reckoning. It is often called a silent company killer for exactly that reason.

The false economy: why cheap software costs more

Here is the trap that catches so many businesses. Cheap, fast, low-quality software looks like a saving at the moment you buy it, and it is more expensive by almost every measure over time.

The saving is real but tiny, and it is upfront and visible. The cost is large but deferred and hidden. You save on the build, then pay far more in maintenance, in rework, in lost customers, in the features you cannot ship because your team is stuck maintaining a mess. Study after study finds the same thing: catching and preventing quality problems early costs a fraction of fixing them later, which is exactly why cutting quality is a false economy. This is the same logic behind why skipping QA costs more than it saves, one specific, well-documented slice of this larger pattern.

The most expensive software a business can buy is the cheap software it has to rebuild. Paying a little more for quality upfront is not an expense; it is the avoidance of a much larger one later.

How quality actually pays off

Quality is not just the absence of these costs. It actively returns value, in ways that compound.

Faster delivery over time. Clean, well-built software is easier and quicker to change, so your team ships features faster, not slower, as the product grows. Quality is speed, over any horizon that matters.

More engineering capacity for what matters. When your team is not drowning in firefighting and workarounds, they spend their time building your future instead of patching your past. That recovered capacity is real money and real roadmap.

Customer trust and retention. Software that works reliably keeps customers. In a market where a crash or a slow page sends users to a competitor, reliability is a genuine competitive advantage.

Lower risk. Quality code, kept current, is more secure and more resilient, reducing the chance of the expensive breach or outage that can set a business back months.

The through-line: quality is not a cost center that competes with speed and growth. It is what enables speed and growth over any real timeframe. The businesses that treat quality as an investment outrun the ones that treat it as an expense to minimize.

How to protect yourself from the cost of bad software

You do not have to accept the hidden tax of bad software. A few disciplines prevent most of it.

Build quality in from the start, do not bolt it on. Proper architecture, testing, and code review from day one cost far less than fixing a mess later. Prevention beats cure by a wide margin.

Manage technical debt deliberately. Some debt is fine if taken on knowingly and paid down; the danger is debt that accrues invisibly and is never addressed. Track it, and budget time to reduce it.

Do not choose a partner on price alone. The cheapest quote often signals the corners that create the real cost later. Weigh what you are actually getting, and remember that a rebuild costs far more than doing it right once, which is why it pays to vet a development partner properly.

Insist on the unglamorous disciplines. Testing, project management, and code review are exactly the things cut under pressure, and exactly the things that prevent the biggest costs. A partner who takes them seriously is protecting your budget, not padding it.

Ready to invest in software that pays off?

The real cost of bad software is paid slowly, in wasted time, lost customers, and the future you cannot build because you are busy maintaining the past. Quality is not the expensive option. It is the one that costs less over any timeframe that matters, because it prevents the far larger bills that bad software guarantees.

The Craxinno team builds software with quality engineered in from day one, architecture, testing, and project management that protect your budget rather than drain it. See recent work in the Craxinno portfolio, explore our custom software development service, or email sales@craxinno.com.

Frequently Asked Questions

What is the real cost of bad software?+

The real cost of bad software is rarely the build price. It is what comes after: wasted engineering time spent firefighting bugs, slower delivery on a fragile foundation, customers lost to crashes and slow pages, security and compliance risk, features never shipped, and reputation damage. Most of these costs never appear on a development budget, which is exactly why they grow unchecked until they become a crisis.

How much does poor software quality cost businesses?+

Poor software quality costs the US economy an estimated $2.41 trillion a year, according to CISQ, with roughly $1.52 trillion of that being technical debt. For an individual business, unmanaged technical debt commonly consumes 20% to 40% of all development time, meaning a large share of engineering salary goes to servicing past shortcuts rather than building new value.

What is technical debt and why does it matter?+

Technical debt is the accumulated cost of shortcuts, quick fixes, and rushed decisions in your code. Like financial debt, it charges interest: every future change takes longer, every new feature is harder to add, and every fix risks breaking something. It commonly consumes 20% to 40% of development time and accrues quietly, so most businesses do not notice until a migration, audit, or incident forces a reckoning.

Is cheap software actually more expensive?+

Usually, yes, over time. Cheap, low-quality software offers a small, visible saving upfront, then costs far more in maintenance, rework, lost customers, and features you cannot ship because your team is stuck patching problems. Preventing quality issues early costs a fraction of fixing them later, which is why the cheapest software a business can buy is often the software it has to rebuild.

How does investing in software quality pay off?+

Quality returns value in ways that compound. Clean, well-built software is faster to change, so you ship features quicker as the product grows. It frees engineering capacity from firefighting to build new value. It keeps customers by working reliably, and it lowers security and outage risk. Quality is not a cost that competes with speed and growth, it is what enables both over any real timeframe.

Shares
Was this useful?

Technology Used

Node.jsNode.js
TypeScriptTypeScript
Next.jsNext.js
AWSAWS
ReactReact
VercelVercel

Tags & Keywords

Software QualityTechnical DebtCost of Poor QualitySoftware DevelopmentAI EngineeringBusiness ValueCode QualityProduct ReliabilitySoftware RiskBusiness Guide
VS
Written byVikash Singh

Sales and Marketing Team

View all posts

Continue with Blogs.

View all blogs
How to Build a Chatbot with the OpenAI API: Step-by-Step Python Tutorial
OpenAI API

How to Build a Chatbot with the OpenAI API: Step-by-Step Python Tutorial

How to Build a Chatbot with the OpenAI API: Step-by-Step Python Tutorial Quick answer: To build a chatbot with the OpenAI API, install the openai Python SDK, save your API key in an environment variable, and send each user message to client.responses.create() with your system prompt in instructions . Pass the previous reply's ID back as previous_response_id so the bot remembers the conversation, then stream the output to a web page through a small backend. That's the whole idea. The rest of this tutorial turns it into code you can run in about 20 minutes. One warning first. Search "OpenAI chatbot tutorial" and a good share of the top results still call openai.ChatCompletion.create() . That function was removed when the Python SDK hit version 1.0 back in November 2023, and the SDK is on version 3.29 as of October 2026. Copy that code today and you get an error before your bot says hello. Everything below is written against the current Responses API and tested on openai 3.29.0. What you'll build A support assistant called Ava for a made-up coffee subscription brand, Brewly. By the end you'll have: A terminal chatbot that streams replies word by word and remembers earlier messages. A FastAPI backend that keeps your API key off the browser. A plain HTML chat page that talks to that backend. A clear idea of what each conversation costs you. Python 3.9 or newer and an OpenAI account with billing enabled are all you need. No frameworks like LangChain. You'll understand every line, which matters the first time something breaks in production. Responses API vs Chat Completions vs Assistants: which one? OpenAI has three APIs that can power a chatbot, and older tutorials mix them up. API Status (Oct 2026) Who manages conversation history Use it for Responses API Recommended for all new projects You, or OpenAI via previous_response_id / Conversations New chatbots and agents Chat Completions Still supported You (resend the messages array) Existing apps you don't want to migrate yet Assistants API Listed under legacy APIs OpenAI (threads) Nothing new OpenAI's own migration guide says Responses is "recommended for all new projects," and reports 40–80% better cache utilization than Chat Completions in internal tests. Better cache hits mean lower bills on long chats, which is reason enough for a chatbot. So this tutorial uses the Responses API throughout. Step 1: Set up the project and your API key Create a folder, a virtual environment and install four packages: 1 2 3 4 mkdir openai - chatbot && cd openai - chatbot python - m venv . venv source . venv / bin / activate # Windows : . venv\Scripts\activate pip install openai python - dotenv fastapi uvicorn Generate a key at platform.openai.com → API keys , then create a .env file in the project folder: 1 2 OPENAI_API_KEY = sk - proj - your - key - here OPENAI_MODEL = gpt - 6 - luna Add .env to .gitignore right now, before your first commit. Leaked keys get scraped from public GitHub repos within minutes, and the bill lands on your account. What goes wrong here: if you see OpenAIError: Missing credentials. Please pass an api_key... , the SDK can't find the variable. Check that load_dotenv() runs before OpenAI() is created and that you started Python from the folder containing .env . Step 2: Send your first request Create hello.py : 1 2 3 4 5 6 7 8 9 from dotenv import load_dotenv from openai import OpenAI load_dotenv ( ) client = OpenAI ( ) # reads OPENAI_API_KEY from the environment response = client . responses . create ( model = "gpt-6-luna" , input = "Explain what an API key is in one sentence." , ) print ( response . output_text ) Run python hello.py . If a sentence comes back, your account, key and billing all work. response.output_text is a convenience property that joins all the text the model produced. The full response.output list also holds reasoning items and tool calls, which you'll need later for memory. Which model should a chatbot use? These are the current text models and their prices per 1 million tokens, from OpenAI's models page: Model Input Output Good for gpt-6-luna $0.10 $0.50 FAQ and support bots, high volume, fastest replies gpt-6.1-sol $2 $10 Bots that need to reason over policies, data or code gpt-6-astra $10 $50 Hard, high-value queries where accuracy beats cost Our take: start every chatbot on Luna. Most customer-facing questions ("where's my order?", "can I change my plan?") don't need a frontier model, and Luna costs 1/20th of Sol. Upgrade only the conversations that need it. Because the model name lives in .env , switching is a one-line change. Step 3: Give the bot a personality and rules with a system prompt Without instructions, the model answers anything about anything. A support bot needs a job description. In the Responses API, that goes in the instructions parameter: 1 2 3 4 5 6 7 8 9 10 11 SYSTEM_PROMPT = """You are Ava, the support assistant for Brewly, an online coffee subscription. - Only answer questions about orders, subscriptions, shipping and coffee. - Keep replies under 80 words unless the user asks for more detail. - If you are not sure, say so and point the user to help@brewly.example. - Never invent order numbers, prices or delivery dates.""" response = client . responses . create ( model = "gpt-6-luna" , instructions = SYSTEM_PROMPT , input = "Can I pause my subscription for a month?" , ) print ( response . output_text ) A few things we've learned writing system prompts for client bots: Rules beat adjectives. "Keep replies under 80 words" works. "Be concise" gets interpreted loosely. Tell it what to do when it doesn't know. The fallback line (email support) is what stops a bot from making up a refund policy. Ban the specific lies. Order numbers and delivery dates are what support bots most love to invent, so the prompt names them. If you want to go deeper on wording, our guide to prompt engineering covers structure and testing. Step 4: Add conversation memory (three ways) Ask the bot from Step 3 "Can I pause my plan?", then follow up with "What about two months?" It has no idea what you mean. Every API call starts with a blank slate, because the model is stateless. You have three ways to fix that. Approach How it works Where history lives Best for A. Manual history Append every message and reply to a list, send the whole list each time Your app Full control, store=False for sensitive data B. previous_response_id Pass the last reply's ID; OpenAI fills in the earlier turns OpenAI (responses kept 30 days by default) Terminal bots, short sessions C. Conversations API Create a conversation object once, attach every response to it OpenAI (no 30-day expiry) Web apps where users come back later One thing none of the three changes: you pay for the history every turn. OpenAI's docs say it directly: with previous_response_id , "all previous input tokens for responses in the chain are billed as input tokens." Server-side memory saves you code, not tokens. Option A: manual history (stateless) 1 2 3 4 5 6 7 8 9 10 11 history = [ ] def ask ( user_text ) : history . append ( { "role" : "user" , "content" : user_text } ) response = client . responses . create ( model = "gpt-6-luna" , instructions = SYSTEM_PROMPT , input = history , store = False , # nothing kept on OpenAI's side ) history . extend ( response . output ) # keep ALL output items, not just the text return response . output_text Note history.extend(response.output) . OpenAI's conversation-state guide says to keep every output item so reasoning items stay intact. Appending only output_text as a string works on simple models and quietly degrades answers on reasoning ones. Option B: previous_response_id (the terminal chatbot) This is the full, runnable terminal bot. Save it as chat.py : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import os from dotenv import load_dotenv from openai import OpenAI load_dotenv ( ) client = OpenAI ( ) MODEL = os . getenv ( "OPENAI_MODEL" , "gpt-6-luna" ) SYSTEM_PROMPT = """You are Ava, the support assistant for Brewly, an online coffee subscription. - Only answer questions about orders, subscriptions, shipping and coffee. - Keep replies under 80 words unless the user asks for more detail. - If you are not sure, say so and point the user to help@brewly.example. - Never invent order numbers, prices or delivery dates.""" def main ( ) : previous_id = None print ( "Ava is ready. Type 'quit' to exit.\n" ) while True : user_text = input ( "You: " ) . strip ( ) if user_text . lower ( ) in { "quit" , "exit" } : break if not user_text : continue stream = client . responses . create ( model = MODEL , instructions = SYSTEM_PROMPT , # resend every turn input = user_text , previous_response_id = previous_id , reasoning = { "effort" : "low" } , stream = True , ) print ( "Ava: " , end = "" , flush = True ) for event in stream : if event . type == "response.output_text.delta" : print ( event . delta , end = "" , flush = True ) elif event . type == "response.completed" : previous_id = event . response . id print ( "\n" ) if __name__ == "__main__" : main ( ) Run python chat.py , ask about pausing, then ask "what about two months?" Ava now knows what "that" refers to. See the comment # resend every turn ? That's the bug that catches almost everyone. The SDK documentation says that with previous_response_id , "the instructions from a previous response will not be carried over to the next response." Send instructions only on the first call and by turn three Ava has forgotten she's a coffee bot and will happily write you a cover letter. Option C: the Conversations API For a web app, a user might close the tab and come back tomorrow. A conversation object survives that, and unlike stored responses it isn't subject to the 30-day TTL: 1 2 3 4 5 6 7 conversation = client . conversations . create ( ) response = client . responses . create ( model = "gpt-6-luna" , instructions = SYSTEM_PROMPT , input = "Can I pause my plan?" , conversation = conversation . id , ) Save conversation.id against your user record and pass it back on every message. We'll use this in the web version. Step 5: Stream replies instead of waiting You already saw streaming in chat.py , but it deserves its own section because it changes how the bot feels more than any model upgrade. Without streaming, the user types a question and stares at nothing for two, three, four seconds while the whole answer generates. Then a wall of text appears. With streaming, the first words show up almost immediately and the reply types itself out. Same total time. Completely different experience. The mechanics are two lines: pass stream=True , then loop over events and print the ones of type response.output_text.delta . Other events you'll see include response.created , response.completed (which carries the final response object and its ID) and error . Step 6: Put the chatbot on a web page with FastAPI Here's where a lot of tutorials go wrong. They call the OpenAI API straight from browser JavaScript, which means shipping your API key to every visitor. Anyone who opens dev tools can copy it and run up your bill. The fix is a thin backend. The browser talks to your server; only your server talks to OpenAI. Create app.py : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import os from dotenv import load_dotenv from fastapi import FastAPI from fastapi . responses import FileResponse , StreamingResponse from openai import OpenAI from pydantic import BaseModel , Field load_dotenv ( ) client = OpenAI ( timeout = 30 , max_retries = 3 ) MODEL = os . getenv ( "OPENAI_MODEL" , "gpt-6-luna" ) SYSTEM_PROMPT = """You are Ava, the support assistant for Brewly, an online coffee subscription. - Only answer questions about orders, subscriptions, shipping and coffee. - Keep replies under 80 words unless the user asks for more detail. - If you are not sure, say so and point the user to help@brewly.example. - Never invent order numbers, prices or delivery dates.""" app = FastAPI ( ) class ChatIn ( BaseModel ) : conversation_id : str message : str = Field ( min_length = 1 , max_length = 2000 ) @app . get ( "/" ) def home ( ) : return FileResponse ( "index.html" ) @app . post ( "/conversations" ) def new_conversation ( ) : conversation = client . conversations . create ( ) return { "conversation_id" : conversation . id } @app . post ( "/chat" ) def chat ( body : ChatIn ) : def generate ( ) : stream = client . responses . create ( model = MODEL , instructions = SYSTEM_PROMPT , input = body . message , conversation = body . conversation_id , reasoning = { "effort" : "low" } , max_output_tokens = 800 , stream = True , ) for event in stream : if event . type == "response.output_text.delta" : yield event . delta return StreamingResponse ( generate ( ) , media_type = "text/plain" ) Three small details doing real work here: max_length=2000 on the message stops someone pasting a novel into your bot and charging you for it. FastAPI rejects it with a 422 before OpenAI is ever called. max_output_tokens=800 caps the reply cost. Reasoning tokens count toward this limit, so don't set it too tight or replies get cut off. OpenAI(timeout=30, max_retries=3) raises the SDK's default of 2 automatic retries, which covers most brief rate-limit and network blips. Now the front end. Create index.html in the same folder: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <! doctype html > < html lang = " en " > < head > < meta charset = " utf-8 " > < title > Brewly Assistant </ title > </ head > < body > < div id = " log " > </ div > < form id = " chat-form " > < input id = " msg " autocomplete = " off " placeholder = " Ask about your order... " > < button > Send </ button > </ form > < script > const log = document.getElementById("log"); let conversationId = null; function addLine(who, text) { const p = document.createElement("p"); p.textContent = `${who}: ${text}`; // textContent, never innerHTML log.appendChild(p); return p; } document.getElementById("chat-form").addEventListener("submit", async (e) => { e.preventDefault(); const input = document.getElementById("msg"); const text = input.value.trim(); if (!text) return; input.value = ""; if (!conversationId) { const res = await fetch("/conversations", { method: "POST" }); conversationId = (await res.json()).conversation_id; } addLine("You", text); const botLine = addLine("Ava", ""); const res = await fetch("/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ conversation_id: conversationId, message: text }), }); const reader = res.body.getReader(); const decoder = new TextDecoder(); let reply = ""; while (true) { const { done, value } = await reader.read(); if (done) break; reply += decoder.decode(value, { stream: true }); botLine.textContent = `Ava: ${reply}`; } }); </ script > </ body > </ html > Start the server: 1 uvicorn app : app -- reload Open http://127.0.0.1:8000 and chat. That's a working, streaming, memory-aware chatbot in roughly 130 lines across two files. (The textContent comment isn't decoration. Model output rendered with innerHTML is an XSS hole: a user can talk the bot into returning a <script> tag. If you later want Markdown formatting, run it through a sanitizer such as DOMPurify first.) For a production widget with a launcher bubble, mobile layout and analytics, see our walkthrough on how to add AI chat to your website . How much does an OpenAI chatbot cost to run? Less than most people expect, if you pick the model deliberately. Take a typical support chat: a 300-token system prompt, 10 back-and-forth turns, user messages around 50 tokens and replies around 150. Because earlier turns are re-billed as input each time, the 10 turns add up to roughly 12,500 input tokens and 1,500 output tokens. Model Cost per 10-turn conversation Cost per 10,000 conversations gpt-6-luna ~$0.002 ~$20 gpt-6.1-sol ~$0.04 ~$400 gpt-6-astra ~$0.20 ~$2,000 These are estimates from list prices. They leave out reasoning tokens (billed as output) and cached-input discounts, which push in opposite directions. Measure your real numbers from response.usage after a week of traffic. The pattern that matters: cost grows with conversation length , not just count, because every turn re-sends the history. Two ways to keep long chats in check: Trim manual history. With Option A, keep the system prompt plus the last 10–20 items. Turn on server-side compaction. Pass context_management=[{"type": "compaction", "compact_threshold": 200000}] and OpenAI compresses older context once the conversation crosses that token count. Our full playbook on reducing LLM API costs covers caching, routing and batching in more depth. If you're budgeting a whole project rather than API calls, see what an AI chatbot costs to build in 2026 . Common mistakes (and the fix for each) AttributeError: module 'openai' has no attribute 'ChatCompletion' You copied pre-2023 code. Use client = OpenAI() and client.responses.create() . 400 Bad Request mentioning temperature GPT-6 models don't accept temperature , top_p or top_logprobs while reasoning is switched on. Remove them. Control tone through the system prompt instead. (On Luna you can set reasoning={"effort": "none"} if you really need sampling controls.) The bot forgets its role after a few messages You only sent instructions on the first call. With previous_response_id , resend them every turn. Replies stop mid-sentence max_output_tokens is too low. Reasoning tokens eat into it before visible text does. Raise the cap or lower the reasoning effort. RateLimitError (429) under load The SDK retries automatically, but sustained traffic needs a queue or per-user rate limit on your side. Check your usage tier in the OpenAI dashboard. Before you go live: a short production checklist The tutorial bot works. A public bot needs a bit more: Authenticate users and rate-limit per user. An open /chat endpoint is a free LLM for the whole internet. Send a safety_identifier . It's a stable per-user ID (OpenAI recommends hashing the email or username) that helps OpenAI detect abuse coming through your app without flagging your whole account. Decide on data retention. Responses are stored for 30 days by default. For health, finance or other sensitive data, use manual history with store=False . Log every conversation on your side. You can't improve a prompt you can't read. Review 20 real chats a week and you'll find the gaps fast. Write a test set. Twenty questions with expected answers, run after every prompt change, catches regressions before customers do. Give the bot real knowledge. A system prompt can't hold your entire help center. That's what retrieval is for. Where to go next: from chatbot to something useful Right now Ava is polite but knows nothing about Brewly's actual policies or orders. The next two upgrades fix that: Retrieval-augmented generation (RAG) lets the bot answer from your own documents. Upload them to a vector store and give the model the file_search tool, or build your own pipeline. Start with how RAG works , and if you're weighing it against training a custom model, read RAG vs fine-tuning . Function calling lets the bot do things: look up an order, pause a subscription, book a call. At that point it stops being a chatbot and starts being an agent. Here's the difference in practice: AI agents vs chatbots . Need a chatbot built for your business? This tutorial gets you a working prototype in an afternoon. Getting from prototype to a bot that knows your products, connects to your CRM or order system, and holds up under real traffic is where most of the work lives. That's the work our AI development team does: RAG chatbots, support agents and LLM integrations built in Python and FastAPI and shipped to production. Tell us what you're building and we'll tell you honestly whether you need us or this tutorial is enough.

Posted 13.10.2026
How to Add Stripe Payments to a Next.js App
Stripe

How to Add Stripe Payments to a Next.js App

How to Add Stripe Payments to a Next.js App Adding Stripe payments to a Next.js app takes about 30 minutes with the modern approach, and it is simpler than most tutorials make it look. The trick is to use Stripe Checkout , Stripe’s own hosted payment page, rather than building your own card form. You create a checkout session on the server, redirect the user to Stripe to pay, and Stripe sends you back. You never handle raw card details, which makes it faster to build and far more secure. This guide walks through the whole thing, step by step, for the Next.js App Router. Here is the one principle that matters more than any line of code, and the thing beginners get wrong: never trust the browser to confirm a payment. A user can close the tab, lose connection, or tamper with the page after paying. The only reliable way to know a payment succeeded is a webhook, a message Stripe sends directly to your server. Getting the webhook right is what separates a real integration from one that silently loses orders, so this guide treats it as essential, not optional. This guide covers setup, the checkout flow, the all-important webhook, testing, and going live. The quick answer: the whole flow If you want the shape of it first, here is what you are building. Install Stripe and add your API keys to environment variables. Create a server route that makes a Stripe Checkout session and returns its URL. Add a button that calls that route and redirects the user to Stripe to pay. Create a webhook route that listens for Stripe’s confirmation and fulfills the order (this is the critical part). Test locally with the Stripe CLI, then add your live keys and production webhook to go live. The golden rule throughout: Stripe Checkout handles the payment UI and card security for you, and the webhook, not the browser, is the source of truth for whether a payment actually happened. Prerequisites Get these in place first. A Next.js app using the App Router (Next.js 13 or newer), and Node.js 18 or newer. A Stripe account (free to create), with access to your API keys in the Stripe Dashboard. You will use test keys while building. Basic familiarity with Next.js API routes (route handlers) and environment variables. Step 1: Install Stripe and set up your keys Install the Stripe packages: npm install stripe @stripe/stripe-js Then add your keys to a .env.local file in your project root (use your test keys while building): STRIPE_SECRET_KEY=sk_test_your_key NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your_key STRIPE_WEBHOOK_SECRET=whsec_your_secret NEXT_PUBLIC_APP_URL= http://localhost:3000 Add .env.local to your .gitignore so your secret keys never reach your repository. The secret key must stay on the server only; the publishable key (prefixed NEXT_PUBLIC_) is safe in the browser. It also helps to create one shared Stripe client. Make a file at lib/stripe.ts: import Stripe from “stripe”; export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); Now every route can import the same configured client. Step 2: Create the checkout session route This server route creates a Stripe Checkout session and returns its URL. Create app/api/checkout/route.ts: import { NextResponse } from “next/server”; import { stripe } from “@/lib/stripe”; export async function POST() { const session = await stripe.checkout.sessions.create({ mode: “payment”, line_items: [ { price_data: { currency: “usd”, product_data: { name: “Your Product” }, unit_amount: 2000, // $20.00, in cents }, quantity: 1, }, ], success_url: ${process.env.NEXT_PUBLIC_APP_URL}/success , cancel_url: ${process.env.NEXT_PUBLIC_APP_URL}/cancel , }); return NextResponse.json({ url: session.url }); } A few notes: amounts are in the smallest currency unit (cents), mode is “payment” for a one-time purchase (use “subscription” for recurring), and success_url and cancel_url are where Stripe sends the user after. The route returns session.url, the hosted Stripe page you will redirect to next. Step 3: Add the checkout button On your page, add a button that calls the route and redirects the user to Stripe. In a client component: “use client”; export default function CheckoutButton() { async function handleClick() { const res = await fetch(“/api/checkout”, { method: “POST” }); const data = await res.json(); window.location.href = data.url; // redirect to Stripe } return <button onClick={handleClick}>Buy now</button>; } That is the entire customer-facing flow: the user clicks, your route makes a session, and the browser sends them to Stripe’s secure page to enter card details. You never touch the card data. Step 4: Create the webhook (the critical part) This is the step that makes the integration trustworthy. After a payment, Stripe sends a message to your server confirming it. You listen for that message and fulfill the order, send the product, mark it paid, email the receipt. Create app/api/webhooks/stripe/route.ts: import { NextResponse } from “next/server”; import { headers } from “next/headers”; import { stripe } from “@/lib/stripe”; import Stripe from “stripe”; export async function POST(req: Request) { const body = await req.text(); // CRITICAL: raw text, not JSON const signature = (await headers()).get(“stripe-signature”)!; let event: Stripe.Event; try { event = stripe.webhooks.constructEvent( body, signature, process.env.STRIPE_WEBHOOK_SECRET! ); } catch (err) { return new NextResponse(“Invalid signature”, { status: 400 }); } if (event.type === “checkout.session.completed”) { const session = event.data.object as Stripe.Checkout.Session; // Fulfill the order here: mark paid, send the product, email receipt console.log(“Payment confirmed:”, session.id); } return NextResponse.json({ received: true }); } Two things catch almost everyone here. First, you must read the raw request body with req.text(), not parsed JSON, because Stripe verifies a signature against the exact raw bytes. Second, always return a 200 response, even for events you do not handle, otherwise Stripe retries for up to 72 hours. Verifying the signature is what proves the message genuinely came from Stripe and not an attacker. Step 5: Test locally with the Stripe CLI You cannot test webhooks just by clicking around, you need Stripe to reach your local server. The Stripe CLI does this. Install it, log in, then forward events to your app: stripe listen --forward-to localhost:3000/api/webhooks/stripe This prints a webhook signing secret (starting whsec_), put that in your STRIPE_WEBHOOK_SECRET. Now run a test payment through your checkout button using Stripe’s test card number, 4242 4242 4242 4242 with any future expiry and any CVC, and watch the CLI forward the checkout.session.completed event to your webhook. When your webhook logs the confirmation, the full loop works. Step 6: Go live Switching to real payments is a short checklist. Replace your test API keys with your live keys (from the Stripe Dashboard, after activating your account). In the Stripe Dashboard, under Developers then Webhooks, add your production webhook endpoint, https://your-domain.com/api/webhooks/stripe , and copy its signing secret into your production environment variables. Deploy, and run one real, small transaction to confirm the live flow end to end. On Vercel or similar, add all your environment variables in the project settings. That is it: real payments flowing, confirmed by webhook, with card security handled entirely by Stripe. The mistakes that break Stripe integrations A few errors catch almost everyone. Avoid these. Trusting the browser instead of the webhook. If you mark an order paid based on the user landing on your success page, you will lose orders and let people fake payments. The webhook is the only source of truth. This is the single most important rule. Parsing the webhook body as JSON. Reading the body any way other than raw text breaks signature verification. Use req.text(). Exposing the secret key. The secret key must stay server-side only. Never prefix it with NEXT_PUBLIC_ or use it in a client component. Not returning 200 from the webhook. If your handler errors or returns a non-200, Stripe retries for up to 72 hours. Handle events, then return 200. Forgetting the production webhook. Local testing uses the CLI; production needs a webhook endpoint registered in the Stripe Dashboard with its own signing secret. Forgetting this means payments work but orders never get fulfilled live. Ready to add payments to your app? Adding Stripe to a Next.js app is genuinely approachable: hosted Checkout handles the hard, sensitive parts, and a correct webhook makes it reliable. Get those two right, and you have production-grade payments without ever touching raw card data. When you want payments built properly, including subscriptions , multiple gateways, or complex billing, as part of a real product, the Craxinno team integrates Stripe and other payment gateways into Next.js and mobile apps. See recent work in the Craxinno portfolio, explore our custom software development service , or email sales@craxinno.com .

Posted 12.10.2026
How to Add AI Chat to Your Website (Step-by-Step)
AI Chatbot

How to Add AI Chat to Your Website (Step-by-Step)

How to Add AI Chat to Your Website (Step-by-Step) Adding AI chat to your website is far easier in 2026 than most business owners expect, and there are three ways to do it, from a no-code tool you can set up in an afternoon to a fully custom assistant trained on your own content. The right path depends on one thing: how much you need the chat to actually know about your business. This guide walks through all three, step by step, so you can pick the one that fits and get it live. Here is the key decision to make first, because it saves you time and money. A generic chat widget that answers from a script is cheap and fast but limited. An AI chat that answers accurately from your own content, your products, policies, and FAQs, is more powerful and is what most businesses actually want, and it is now very achievable. Knowing which you need before you start is the difference between a quick win and a frustrating one. This guide covers the three ways to add AI chat, a step-by-step for each, what it costs, and how to choose. The quick answer: three ways to add AI chat If you want the options at a glance, here they are, from easiest to most powerful. A no-code chat tool (easiest). Sign up for a service like a hosted AI chat widget, paste a snippet of code into your site, and you have AI chat live in an afternoon. Best for simple needs and non-technical teams. A platform with your own content (middle). Use an AI chat platform that lets you upload your documents, website pages, or FAQs, so the chat answers accurately from your actual business content. Still mostly no-code, more useful, modest monthly cost. A custom AI chat (most powerful). Have a developer build a chat assistant trained on your content, connected to your systems (orders, bookings, accounts), that matches your brand and can actually take actions. Best when chat is important to your business and you want full control. The honest rule: start with the simplest option that meets your need. Most businesses are well served by option two, a platform that answers from their own content, without the cost of a custom build. First, decide what you actually need Before choosing a method, answer two quick questions, because they determine which path fits. Does the chat need to know your business? If you just want to greet visitors and answer a few generic questions, a basic widget is fine. If you want it to answer accurately about your products, pricing, policies, or services, you need a method that uses your own content, which rules out the most basic widgets. This is the single most important question. Does the chat need to do things, or just answer? If answering questions is enough, a platform works. If you need the chat to take actions, look up an order, book an appointment, update an account, you are moving toward a custom build, because that requires connecting to your systems. Most businesses start with answering and add actions later. Your answers point you straight to one of the three methods below. Method 1: Add a no-code AI chat widget (the fast path) This is the quickest way to get AI chat live, and it needs no developer. Here is the process. Step 1: Choose a tool. Pick a hosted AI chat service. Many exist, offering a chat widget you can add to any website, often with a free tier to start. Step 2: Sign up and configure. Create an account, set the chat's name, greeting, and tone, and choose basic settings like colors to match your brand. Step 3: Add your knowledge (if supported). Better no-code tools let you paste in FAQs or point the tool at your website so it can answer from your content. Do this if available, it is what makes the chat actually useful. Step 4: Copy the code snippet. The tool gives you a small snippet of code (usually one line). Step 5: Paste it into your website. Add the snippet to your site, in your site builder's "custom code" or "footer" section, or, on WordPress, via a plugin or the theme footer. Save, and the chat appears on your site. Step 6: Test it. Open your site, ask the chat a few real questions, and refine its settings or knowledge based on how it answers. That is it, AI chat live, often within an afternoon. The limit is that no-code tools offer less control and cannot deeply connect to your systems. Method 2: Use a platform trained on your content (the sweet spot) This is where most businesses should land. These platforms are still largely no-code, but they let the chat answer accurately from your own business content, which is what makes AI chat genuinely useful. Step 1: Choose a content-aware AI chat platform. Pick a service built to ingest your documents and website and answer from them (this uses a technique called RAG , which grounds answers in your real content so the chat does not make things up). Step 2: Upload your content. Add your FAQs, product and service pages, policies, and any help documents. The platform processes them so the chat can retrieve the right answer. Step 3: Configure behavior and brand. Set the tone, the greeting, what to do when it does not know (for example, offer to connect to a human), and match your brand. Step 4: Add it to your site. As with method one, paste the provided snippet into your website. Step 5: Test with real questions, then refine. Ask the questions your customers actually ask, see where answers fall short, and improve the content you fed it. The quality of your uploaded content largely determines how good the chat is. This path gives you a chat that answers accurately about your business, usually for a modest monthly fee, without a custom build. For most businesses, it is the best balance of power and effort. Method 3: Build a custom AI chat (the powerful path) When AI chat is important to your business, when you need it to match your brand exactly, answer from your full knowledge, and take real actions, a custom build is the answer. This needs a developer, but it gives you complete control. A custom AI chat typically uses an LLM (like Claude or GPT) connected to your content through RAG, so it answers accurately from your data, and connected to your systems so it can act, look up an order, check availability, update a record. It lives on your site exactly as you design it, with no third-party branding, and you own it. The build process, at a high level: define what the chat should do and answer, connect it to your content and systems, engineer and test its behavior against real questions, and deploy it to your site with a clean handoff to a human when needed. This is real development, and it is what our guide on the cost to build an AI chatbot breaks down in detail, and if the chat needs to complete tasks rather than just answer, that moves into agent territory, which our guide on AI agents vs chatbots explains. Choose this when chat is a core part of your customer experience and the simpler options have hit their limits. What it costs A quick, honest picture so you can budget. No-code widget: often free to start, then roughly $20 to $100 a month for a small business as you add features and volume. Content-aware platform: typically $50 to a few hundred dollars a month, depending on volume and features. The sweet spot for most businesses. Custom build: a one-time development cost (plus ongoing model usage and maintenance), which starts in the low five figures and scales with complexity. Right when chat is important enough to justify owning it. The pattern: the more the chat needs to know and do, and the more control you want, the more it costs, so match the spend to how much the chat matters to your business. How to choose your method A simple way to decide. If you want AI chat live fast, for simple questions, and have no developer, use a no-code widget (method one). If you want the chat to answer accurately about your business without a custom build, which is most businesses, use a content-aware platform (method two). If chat is core to your business and you need full control, brand match, and the ability to take actions, build custom (method three). And the smart sequence: start simple, prove chat helps your visitors, learn what they actually ask, then upgrade to a more powerful method if the value is there. There is no need to build custom on day one, and often no need to build custom at all. Ready to add AI chat to your website? Adding AI chat is one of the highest-return, lowest-friction ways to improve your website in 2026, it answers visitors instantly, around the clock, and captures leads you would otherwise lose. Start with the simplest method that meets your need, ground it in your real content, and upgrade only when the value justifies it. When you need AI chat that truly fits your business, trained on your content, connected to your systems, matched to your brand, the Craxinno team builds custom AI chat and assistants that work in production. See recent AI work in the Craxinno portfolio , explore our AI development service , or email sales@craxinno.com .

Posted 07.10.2026
Connect With Us

Have something in mind?

We take on a handful of new custom-software engagements every quarter. If your problem is interesting and your timeline is real — let’s talk.

Let’s ConnectAvg. response · under 4 hours
01
Ideate · 1 weekWorkshops, scoping, success metrics agreed.
02
Design + Build · 8–14 weeksBi-weekly demos. Production code from week one.
03
Ship + Support · ongoingDeployment, observability, and a long-tail retainer.