Skip to content
megapromotingLet's talk

Solutions · Dental clinics

A service list with a marker for coverage under compulsory insurance, editable by reception — not a PDF with prices sent by email.

A dental clinic website is judged by one thing: whether the person finds the service they need, with its price and its unit — per tooth, per jaw, per session — and whether they know whether it is covered by insurance or not. The rest is décor. We build the structure that holds this and the panel from which the staff changes it themselves.

Already builtLucrarea se poate verifica din exterior chiar acum: datele publice ale unui centru stomatologic raional pe care îl administrăm sunt servite ca fișier structurat — 89 de servicii, fiecare cu categorie, subcategorie, denumire, preț, unitate de măsură și un indicator de eligibilitate pentru asigurarea obligatorie, dintre care 27 marcate ca eligibile; 7 categorii și 10 subcategorii, verificate de noi pe 06.09.2026. A doua parte a dovezii e securizarea, cu diff în istoric: la preluare, parola de administrator era comparată în browser și ajungea în pachetul JavaScript public, iar scrierile pe interfața de programare se puteau face de oricine de pe internet. Precizarea corectă, ca să nu ne atribuim mai mult decât e al nostru: prima variantă a site-ului nu e a noastră. Contribuția care se poate arăta cu diff e preluarea, securizarea, tipurile, integrarea continuă și igiena dependințelor.

A dental clinic does not sell “services”. It sells a very long list of tariff items with units that do not resemble anything from another field: per tooth, per jaw, per extraction, per treated case, per session, per visit, per procedure. The person searching online is not looking for the clinic, but for their item: how much does an extraction cost, is it paid per tooth or per visit, is it covered by insurance. A website that answers this is useful; one that has a “Prices” page with an attached PDF is not.

The second thing that reality of a clinic requires is for the list to be changeable without a developer. Tariffs change, doctors change, the emergency schedule is announced differently in winter. If every change goes through a developer, the list stays old and, in a few months, patients come with last year’s price. The structure we build keeps the public data — services and team — in structured files read both by the API and directly by the visible part of the website, and the staff edit them from a panel.

The third thing, which no one asks for in the brief and which matters most, is who can write to that list. When we took over the project we write about here, anyone on the internet could send a write to the API, and the panel password ended up in the JavaScript bundle served to the browser — because its verification was done in the browser. We moved verification to the server: the password no longer leaves the server, a signed token valid for twelve hours is returned, with a limit of eight attempts per fifteen minutes per IP address and constant-time comparisons; the API listens only on the local address, so the web server is the only public path.

The marker for coverage under compulsory insurance deserves a separate sentence, because it is the field with the highest value for the patient and the highest risk for the clinic: it is an assertion with consequences. That is why it lives as institution-edited data, not as text written by us on a page; we build the field, the display and the ability to correct it in a minute.

What it covers

What changes concretely in dental clinics

Every service is an item with its own unit, not a row in a price table

The data structure of each service has category, subcategory, name, price, unit of measure and an eligibility indicator for compulsory insurance. The units are the real ones of the field — tooth, jaw, extraction, treated case, session, visit, consultation, anaesthesia, procedure, investigation, intervention — because a price without a unit produces exactly the conversation reception wants to avoid.

Two-level taxonomy, so the long list remains navigable

Category and subcategory, not a flat list. In the work we write about: 7 categories and 10 subcategories across 89 services. Scale matters — below one hundred items, a two-level taxonomy is enough; above a few hundred, the structure becomes a different discussion and we say so before, not after.

The insurance coverage marker, as institution data

The eligibility indicator is attached to each service, in public data, and is editable by the clinic. In the cited work, 27 out of 89 services are marked as eligible. We do not determine what is covered by insurance, and we do not infer it: it is the institution’s statement, with consequences for the patient, and it must be correctable by it at any time.

Separate screens for emergencies, orthodontics, implants, paedodontics

Not a single “Services” page. The site has 22 screens, from services and prices to emergencies, orthodontics, implants, paedodontics, transparency and feedback. The reason is practical: someone searching for “dental emergency” on Sunday evening does not want a general page, and a section page has something to say about opening hours and what is done in it.

Staff edit, the developer is not in the loop

Services and the team are edited from a panel, and writes require a valid token; reads remain public, so the site works without authentication. The consequence for the clinic: a price change goes live the same day, not in the next development iteration.

The password does not leave the server, and attempts are limited

Authentication is done on the server and returns a token signed with HMAC-SHA256, valid for 12 hours, with a maximum of 8 attempts per 15 minutes per IP address and constant-time comparisons. The signing secret is generated automatically, in a file with restricted permissions, kept outside the repository. These are numbers from the code, not general principles — they can be read and challenged.

The application programming interface is not exposed directly to the internet

It listens only on the local address, so the front web server is the only public route; the allowed domain list is explicit, and the request body is capped at 1 MB. Writes require a token, reads do not. It is a standard configuration — the problem is that, when it is missing, it cannot be seen from the outside until the day someone finds it.

Continuous integration refuses the package if it finds a secret in it

The check that compiles and verifies types also does something that few projects do: it searches for secrets in the built package and fails if it finds one. It is directly tied to the bug fixed in intake — a password had ended up in the public package precisely because nothing checked for this. On top of that, dependency updates on a weekly cadence.

What a dental clinic site does not do

It does not confirm an appointment on its own: it collects the request, and confirmation requires either a person or a connection to the clinic’s appointment system — a separate task, with its own conditions. It does not publish clinical information written by us: content about treatments is the responsibility of the specialists. And it does not decide what is covered by insurance.

Traseul

How a request passes through the system.

01

We inventory the nomenclature before design

The first delivery is the list of tariff items with the unit for each and the coverage marking, in the form kept by the clinic. If the list does not exist in a structured form — and usually it exists as a spreadsheet or as a document — we structure it, and the clinic confirms it. Design comes afterwards, because it depends on how many items there are.

02

We build the public structure and the panel in the same stage

The public screens and the panel from which they are edited are not made separately, because otherwise displayed fields appear that cannot be edited. We deliver the site with the real data loaded, not with filler content — a clinic site full of placeholder text cannot be assessed by anyone.

03

We close the writes and check from the outside

Server-side authentication, signed token, attempt limit, API only on the local address, allowed domain list, request body cap. We deliver the result of an external verification: what can be read without authentication and what cannot be written.

04

We hand over with training for the person who edits, not just with passwords

We hand over the accesses, the commissioning document and a short training for the person who will change prices and the team. Delivery is not considered complete until someone from the clinic has, on their own, made a change that appears on the site.

1Server web în față2pachet static construit3interfață de programare doar pe adresa locală4fișiere structurate cu serviciile și echipaCitirile trec public; scrierile cer un jeton semnat, valabil 12 ore.
4 straturi

The data

What we touch, where it lives, and how long it stays

The rules differ from one industry to another. These are the ones that apply in dental clinics.

What is public and what is not, on a clinic site
Public: the list of services with unit and coverage marker, the team, the institution’s schedule, contacts. Not public: anything that comes from a form. The distinction seems obvious until someone puts a message with their name and issue on the feedback page — which is why forms and public display never share the same source.
The appointment request contains health data
“My lower left wisdom tooth hurts” is information about the health of an identifiable person, from a category with a stricter regime. The practical consequences are established at implementation: who has access to the received messages, how long they are kept, which channel they arrive on and what is never requested in the form.
Prices and the coverage marker belong to the institution
We build them as structure and as a panel; their content belongs to the clinic and changes by its decision. We do not publish any tariff figure as an example in our materials, not even for illustration — a tariff taken out of context becomes a statement about the clinic.
Who can write to public data
Only someone with a valid token, obtained through server-side authentication, with an attempt limit. Reads are public because they have to be. The difference between these two sentences is exactly the fault we fixed when taking over the cited project.
What remains in the repository history
A project history almost always contains data that nobody put there intentionally: a phone number, an e-mail address, the content administrator’s name. We treat them as such — they are not taken over into public materials and are removed from the content at the client’s request.

A case

An administrator password that was reaching the package served to the browser

The situation

When taking over a dental centre site, the admin panel had two problems that could be seen only from the inside. First: the password was compared in the browser, so it reached the public JavaScript package. Second: write requests to the API could be made by anyone on the internet, and the port was exposed directly.

What we built

We moved the check to the server: authentication returns a token signed with HMAC-SHA256, valid for 12 hours, with a limit of 8 attempts per 15 minutes per IP address and constant-time comparisons; the signing secret is generated independently, with restricted rights, outside the repository. The API was kept listening only on the local address, with the web server in front, allowed domain list and request body capped at 1 MB. Writes to services and the team require a token; reads remained public. On top of all that, continuous integration that compiles, checks types and rejects the package if it finds a secret in it.

What came out

The clinic’s public data can be checked from the outside today: 89 services, each with category, subcategory, price, unit and insurance coverage indicator, of which 27 are marked as eligible. What has changed is who can write to them.

What the case does not say

We did not build the first version of the site. From the repository history, some of the commits belong to external developers. The contribution we can show with diff is the handover: security, continuous integration, types, dependencies.

Questions

What someone from dental clinics asks

Can we change the prices ourselves, without calling you?

Yes — that is half of the work. The services and the team are edited from a panel; writes require authentication, reads remain public. Our recommendation: at handover, the person who will handle it makes the change in front of us, so we know it works from their hand, not from ours.

How do we show what is included in compulsory insurance?

As an indicator on each service, in the same data you edit. In the work we cite, 27 of 89 services are marked as eligible. We build the field and the display; what is eligible is the institution’s statement, because it has consequences for the patient and for the clinic.

Why does the unit of measure matter for a price?

Because in dentistry the price is not per visit. It is per tooth, per jaw, per extraction, per treated case, per session. A price displayed without a unit produces exactly the conversation reception has ten times a day and, worse, the patient’s feeling that they were told something else on the phone.

Can the site make appointments?

It can collect the request. Confirming a time slot requires either a person who checks the schedule, or a connection to the clinic’s appointment system — it is a separate job, with compatibility checking beforehand. We do not call a form that sends an email “online booking”.

Who writes the texts about treatments?

The clinic’s specialists. We do not write clinical information and do not “optimise” it for search, because that would mean formulating medical statements on behalf of a medical institution. We structure it, ask the questions the text must answer, and publish it after it is confirmed.

I already have a site made by someone else. Can you take it over?

Yes, and the work we write about here is exactly that. What we do first in a takeover: we look at what can be written from the outside, where the passwords are, and what ends up in the package served to the browser. In the cited case, the administrator password ended up in the public package, and writes could be made by anyone. We moved verification to the server and closed the writes.

How quickly is a tariff change visible?

As soon as it is saved, because the public data is read from the same source you edit. What we do not promise is the speed at which results are updated in search engines — that does not depend on us and we cannot guarantee it.

What happens with the data from the contact form?

It is set at implementation: where they go, who sees them, how long they are kept and what is never requested in the form. In a clinic, the message often contains a description of a health issue, so it is not “a lead”, it is a category of data with a strict regime — and its handling is set out in the clinic’s published policy.

Will the site say it was made by you?

Only if you want it to. Some of our work carries attribution in the footer, others do not — it depends on the client. We mention this because it is a question clinics ask and because, when the site carries no attribution, we do not present ourselves as authors without their consent.

What would you want to work better?

Tell us about your process. Together we decide what is worth building, what we can connect, and how we check the result.

Let's talk