Support chatbots get all the attention, but a sales chatbot is a different beast and in many ways a harder one. A support bot succeeds by closing tickets. A sales bot succeeds by making a human feel understood enough to hand over their phone number. That is a social problem, not a retrieval problem, and the architecture reflects it.
This post documents the sales-oriented chatbot I built for a real-estate platform in Barcelona — the one now sitting on the homepage of kirweb.site, actually, and on a handful of client sites. The specific results I am writing from: organic leads tripled in four months, the bot qualifies roughly 65% of first-touch inquiries without human intervention, and it averages around 1.2 qualified leads per active hour across the sites where it runs. The code is Django, OpenAI, PostgreSQL, and surprisingly little magic.
What a sales chatbot actually needs to do
Before architecture, a clear definition. A sales chatbot is not a search box. Its job is a specific five-part conversation:
- Understand what the visitor wants. "I need a website" is not a usable intent; "I need a WordPress site for my restaurant with online ordering" is.
- Qualify them. Budget, timeline, decision-making authority, technical fit. If you can qualify out clearly misfit leads, your sales team's time is worth 3x more.
- Educate them. Most B2B buyers want to feel like they understand what they are buying before they agree to a call. The bot should explain the tiers, the trade-offs, the typical timelines.
- Capture contact details. Email, phone, preferred channel, best time to reach. This is the actual conversion event.
- Hand over to a human with context. The human should arrive with the transcript, the qualifying data, and the visitor's stated intent pre-loaded — not start from scratch.
Every decision below supports one of those five jobs. If a feature does not, I leave it out.
Why I built it on Django instead of a dedicated platform
You can buy a sales chatbot from Drift, Intercom, Tidio, or dozens of newer AI-first platforms. For the real estate project, the client had already paid for Intercom for two years and had a shelf of abandoned flows to prove it did not fit. The specific problems with off-the-shelf for sales conversations:
- They assume you sell one thing. The real estate platform sells buyer services, seller services, rental services, and valuations — four distinct qualification funnels. Drift's logic engine melts down at four nested branches.
- They do not know your pipeline. Integration with the client's CRM was copy-paste-via-Zapier, not native. A lead dropping into the CRM with no context was, in practice, a dead lead.
- Monthly cost scales with traffic. At 20,000 conversations/month, Intercom's AI add-on was quoted at EUR 650/month. A custom Django build pays for that in eight months and then runs for free (well, EUR 60/month of OpenAI).
For clients with generic needs, Intercom is fine. For anyone with a sales process that is even a little custom — which is most B2B and much of B2C — building on Django + OpenAI gives you control over every step of the conversation and of what gets written where.
The architecture in one paragraph
Django handles sessions, persistence, and the web layer. A view endpoint /chat/ receives user messages, loads the conversation history from Postgres, assembles a prompt, calls OpenAI with a tool schema that defines "qualify_lead", "schedule_call", "create_contact", and "hand_over_to_human", and returns the streamed response. Tool calls execute against the Django ORM. Redis handles rate-limiting per session. The whole thing fits in about 400 lines of Python for the core flow, plus Django templates and models.
Prompts are the product
This is the part nobody writes about honestly. In a sales chatbot, 70% of the perceived quality is the system prompt. The rest — the code, the tool calls, the UI — is table stakes.
The system prompt for the real estate bot is 600 words long. It defines:
- Persona. Not "I am an AI assistant" but "You are Maria, a sales advisor at [agency name]. You are warm, direct, and genuinely curious about helping the person find the right property. You are not pushy."
- Scope. What the bot can help with (property search, valuations, viewing scheduling), and what it cannot (legal advice, price negotiation on specific offers). Out-of-scope requests trigger a human hand-off.
- Qualification checklist. What information Maria should try to learn over the course of the conversation — without turning it into an interrogation. "Naturally introduce questions about budget and timeline after the visitor has described what they are looking for."
- Tone controls. "Use short sentences. Do not use bullet lists in responses unless the user asks for a comparison. Never start a message with 'Great question!' or 'As an AI...'."
- Tool usage rules. "Only call
schedule_callafter the user has confirmed they want to speak with a human and has provided a phone number."
I iterate on this prompt every week based on real transcript reviews. A sales prompt is never finished; it is tuned against outcomes (lead-to-call conversion rate, qualification completeness) the same way ad copy is.
The Django models that back the flow
Simple, boring, and durable. Over-engineering this layer is how you end up with a "framework" that slows down every future change.
class ChatSession(models.Model):
session_id = models.CharField(max_length=64, unique=True, db_index=True)
visitor_utm_source = models.CharField(max_length=100, blank=True)
visitor_utm_campaign = models.CharField(max_length=100, blank=True)
qualification_data = models.JSONField(default=dict)
is_lead = models.BooleanField(default=False)
contact_email = models.EmailField(blank=True)
contact_phone = models.CharField(max_length=30, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
last_active = models.DateTimeField(auto_now=True)
class ChatMessage(models.Model):
session = models.ForeignKey(ChatSession, on_delete=models.CASCADE, related_name='messages')
role = models.CharField(max_length=10) # user, assistant, tool
content = models.TextField()
tool_name = models.CharField(max_length=50, blank=True)
tool_args = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['created_at']
The qualification_data JSON field is intentionally schemaless. The bot fills in what it learns — budget range, property type, location preference, timeline — and nothing forces the order. If a visitor volunteers "I have EUR 400,000 and I need to move by September", the bot extracts both facts in one turn. A relational schema would force either a complicated wizard UI or a lot of null columns.
Tool calls, not free-text extraction
Early versions of the bot tried to pull structured data out of assistant messages with regex and post-processing. That broke constantly — the model would say "I'll note your budget is around 400k" in one turn and "let me just confirm, you mentioned 400 thousand, right?" in the next, and the extractor would either double-count or miss the update.
The fix: give the model a tool called update_qualification and require it to be called any time the visitor confirms a new piece of qualifying information. The tool just writes to the JSON field. The model's assistant text is purely conversational; the data capture is structured.
tools = [{
"type": "function",
"function": {
"name": "update_qualification",
"description": "Record or update a piece of qualifying information the visitor has confirmed.",
"parameters": {
"type": "object",
"properties": {
"field": {
"type": "string",
"enum": ["budget_eur", "timeline", "property_type", "location", "intent"],
},
"value": {"type": "string"},
},
"required": ["field", "value"],
},
},
}]
This one change was the biggest quality jump in the whole build. Conversion to qualified lead went from ~30% to ~65% in the first week after rollout, because the sales team now received leads with a complete picture instead of a transcript they had to read.
The conversion moment
The single highest-leverage UI element is the transition from chat to contact form. Most bots blow this by dumping a formal "Please fill in your details" form, which feels like a shop clerk shoving a clipboard in your face. The pattern that works for sales:
- Bot infers the visitor is warming up ("so what's the next step?", "how do I get started?").
- Bot responds conversationally: "I can have our team reach out — what's the best way to contact you? Email or WhatsApp?"
- User answers, and the bot captures the contact via a
create_contacttool call, not by presenting a form. - Bot confirms: "Got it. Someone from the team will follow up within 24 hours. Anything else you wanted to ask before they do?"
Contact capture rate on "warm" conversations went from 18% with a traditional form to 58% with the conversational capture. The difference is entirely UX — the bot uses the same channel the visitor is already engaged in, and does not break the rhythm.
Hand-over to a human sales rep
When the bot hits scope boundaries (complex pricing negotiation, specific legal questions, a visitor explicitly asking for a person), it calls hand_over_to_human. That tool does three things:
- Posts a summary into the team's Slack, including the visitor's stated intent, the qualification data collected, and a link to the full transcript.
- Creates a deal in the CRM (HubSpot for one client, a custom Django model for another), pre-populated with the captured fields.
- Sends the visitor a message acknowledging the hand-over and telling them what to expect next.
The key is that the human sales rep never starts from zero. They walk into a conversation already in progress, with enough context to open with "Hi Maria, I saw you are looking at a three-bedroom in Poblenou around 450k — let me show you what we have available." Compare that to the cold-open "How can I help you?", and you understand why the lead-to-meeting rate doubles.
Three design mistakes I made and corrected
1. Too many qualification questions up front. The first version interrogated visitors in the first four messages. Drop rate was 45% by message five. I restructured the prompt to make qualification emerge from the conversation — the bot asks what it needs to know only when it needs to know it, and always in context. Drop rate fell to 18%.
2. Letting the bot do price negotiation. A visitor asked "can you do 350 instead of 400?", and the bot, trying to be helpful, responded "Let me see what we can do!". Setting that precedent is disastrous — it invites everyone to haggle with a bot that has no authority. Now the system prompt includes an explicit instruction: "If the visitor asks to negotiate price, respond with 'Price is something our sales team handles personally — I'll connect you' and trigger hand-over."
3. Storing conversations but not analyzing them. I had 3,000 transcripts and no review process for three months. Once I built a weekly digest — 10 randomly sampled conversations, with flags for any that ended without a lead — prompt improvement accelerated dramatically. You cannot tune what you do not read.
What it cost to build and what it costs to run
Fixed-price build for the real-estate deployment: EUR 4,800, delivered in 5 weeks. That covered the Django app, OpenAI integration, CRM sync, Slack notifications, transcript review tooling, and the widget itself.
Monthly running cost at ~15,000 visitor conversations per month:
- OpenAI (mix of gpt-4o and gpt-4o-mini): EUR 75-110
- Hosting (shared Django infrastructure): EUR 15 incremental
- Total: under EUR 130/month
Against roughly 40 qualified leads per week at a sales-team cost of EUR 12/lead-hour previously spent qualifying, the payback was about three months.
FAQ
How long does it take to build a sales chatbot like this?
For a single-funnel bot with CRM integration and prompt tuning: 4-6 weeks fixed price, EUR 4,000-6,500. Add 1-2 weeks for each additional qualification funnel (e.g. buyer + seller + rental).
Can I build this on top of Django's admin instead of a custom dashboard?
Yes, and I recommend it for the first version. The default ChangeList view on ChatSession with filters on is_lead and created_at is enough for a sales team of three. Graduate to a custom dashboard when you outgrow it.
Is OpenAI the right model or should I use Claude or open-source?
For sales conversations in English, Spanish, and most European languages, GPT-4o is slightly ahead on natural tone, Claude 3.5 Sonnet is slightly ahead on following complex system prompts. I build client projects on OpenAI by default because the tool-calling ergonomics are a little cleaner, but I would switch without hesitation if the cost-quality curve shifted.
What about privacy for EU visitors?
Conversations are stored in your own Postgres, not OpenAI's servers, with OpenAI's API data-retention disabled. The privacy policy discloses AI processing. Contact data captured by the bot follows whatever retention rules you apply to your CRM — usually 12-24 months for unresolved leads, longer for customers.
How do I measure whether the bot is working?
Four metrics, weekly: qualified-lead rate (leads with complete qualification data / total conversations), contact capture rate (leads / conversations), hand-over rate (human hand-overs / total leads), and time-to-first-reply from the sales team. A healthy bot moves all four in the right direction; a sick one moves them against each other.
What about hallucinations? Can the bot invent a property that does not exist?
Tool calls prevent this. The bot cannot claim a property is available without calling search_inventory. For sales bots where the inventory is small enough to include in the prompt directly, embedding the full catalog is simpler; for larger inventories, RAG over the listings database.
If you want one like this
The fastest way to evaluate whether a sales chatbot makes sense for your business is a 15-minute call where we walk through your current sales funnel, where leads are getting stuck, and what a bot could plausibly fix. I return a fixed-price proposal within 24 hours if there is a fit.
The architecture here is not tied to real estate or to my particular stack. The pattern — Django (or any web framework), OpenAI API, structured tool calls for data capture, transcript review as the main quality loop — is the reliable shape of a sales chatbot in 2026. Anything simpler is a toy; anything more complex is usually someone selling you a platform you do not need.