01Project overview
Admister is a multi-vertical marketplace. Under one roof it runs classifieds, a car and parts board, real estate with sale and rent, a goods market with seller shops, digital goods, a coupon service and a price aggregator. Each of them has its own subdomain, its own front page and its own idea of what an item is.
From the outside they look like seven separate products. From the inside they are one application: the service is decided by the hostname of the request, and everything underneath — accounts, money, moderation, search, media, notifications — is shared. The project was ours end to end: the model, the engine, the interface and the launch of every vertical.
02Context and the problem
The usual way to launch several marketplaces is to launch several websites. It works until the second one: now there are two user tables, two moderation queues, two payment integrations and two places where the same bug has to be fixed twice — and fixed differently, because by then the code has drifted.
The opposite extreme is just as bad. One site with a category called «Cars» cannot ask for mileage, engine and plate the way a car board must, and a real-estate search that cannot filter by floor and rooms is not a real-estate search. The task was to keep one engine without pretending that seven very different catalogues are the same catalogue.
03Project goals
- one codebase and one account for all verticals, with the service chosen by hostname;
- per-vertical attributes and filters: dozens of fields for cars and real estate, none of them dragged into the other services;
- money the owner can trust — a balance, crypto invoices, idempotent crediting and reconciliation of stuck payments;
- reasons for buyers to come back: saved searches with push, seller badges, auctions and group buys;
- two languages and four display currencies without duplicating content;
- a mobile application that talks to a backend-for-frontend rather than scraping the site.
04What we did
We built the service registry into the boot sequence. A request arrives, the hostname is matched against the registry, and from that moment the application knows which vertical it is: which modules to load, which attribute set to use, which front page and which menu to render. The bare domain is the classifieds board, so the shortest address is also the most general one.
Everything that is not vertical-specific was written once: accounts and sessions, the balance and payments, media intake, moderation, saved searches, notifications, the sitemap and the API. Adding the seventh service was mostly writing its attributes and its front page — not another marketplace.
05Seven services, seven catalogues
- Classifieds — the general board: anything from a bicycle to a sofa, free to post.
- Cars — vehicles, plates and parts, with a dedicated attribute set of several dozen fields and filters built on it.
- Real estate — sale and rent, its own attribute set, maps and per-district search.
- Market — goods with seller shops: a storefront per seller, stock, orders.
- Digital — keys, gift cards and eSIM: goods that are delivered, not shipped.
- Deals — coupons and promo codes of partner stores.
- Price — a price aggregator: the same product across shops, compared, with counted outbound clicks.
They share the account, the balance, the moderation queue and the search index — and share nothing in their attributes, which is the point.
06Money: a balance, crypto invoices, reconciliation
Paid features — promotion, shop plans, digital goods — are paid from an internal balance, and the balance is topped up with crypto through Payora, our own gateway. The invoice body is built in exactly one function, because the same body is replayed by the reconciliation cron under the same idempotency key: a single field that differs between the two would come back as a conflict and leave the payment hanging.
Two details we would repeat on any gateway. The invoice carries the buyer’s own locale taken from their account row, not from the web request — the cron has no request and would otherwise send everyone English. And it carries a return address that leads back into the account on the bare domain, so a buyer who started on the car vertical returns logged in, not to a page that asks them to sign in again.
07Trust: moderation, badges, disputes
A marketplace is judged by its worst listing, so moderation is not a side screen here. Every decision — approve, reject, take down — goes through one handler, and «reject» is deliberately not the same thing as «take down»: the first is an answer to the author with a reason, the second removes something that was already live. Two verbs, two different letters, two different states in the record.
Sellers and shops carry badges that are earned, not bought: how long the account has existed, how many deals closed, whether the phone and the email are confirmed. The buyer sees the same badge everywhere the seller appears, because it is computed in one place.
08Search, saved searches and push
Search knows which vertical it is in. On the car board it offers the fields a car has; on real estate it offers rooms, floor and district; on the general board it stays deliberately simple. Suggestions come from an endpoint of their own, so the field answers while the page is still being read.
A search can be saved — and that is what turns a visitor into a returning one. New matches arrive as a web push, the subscription belongs to the account rather than to the browser tab, and the same mechanism carries the events people actually wait for: an auction about to close, a group buy that reached its threshold.
09A mobile app with its own backend
The application does not scrape the website. It talks to a backend-for-frontend of its own: a small set of endpoints that answer in the shapes the screens need, with tokens issued separately from web sessions. Two paths are open without a token — the reference data the app needs before anyone signs in, and a health endpoint.
That health endpoint is a separate address on purpose. The registry that watches the fleet used to ask the reference endpoint, and that one assembles every dictionary and every rate — a liveness check should not cost more than the work. It answers with the contract version too, so a client built for an older contract is noticed before its users notice it.
10Seven fronts of SEO
Seven services on one domain means seven sitemaps, seven sets of structured data and one very easy way to produce duplicates. Each vertical publishes its own sitemap from cache, the listing pages declare their own type of structured data, and search-result pages never claim to be canonical for a listing.
The content side is automated too: an editorial stream arrives from Astrina, the platform we built for exactly this, and the moment an article lands the sitemap cache for its vertical is dropped — otherwise the new page would wait for the next rebuild and nobody would know why it was missing.
11One outbound channel
Everything the application sends outwards — API calls, webhooks, partner feeds, images it fetches, mail — goes through a single module bound to a SOCKS5 egress proxy, with DNS resolved through the proxy as well. The module is fail-closed: if the proxy is unavailable the request is blocked rather than quietly sent from the server’s own address.
This is not decoration. A marketplace pulls partner feeds and remote images all day; each of those is a request that would otherwise carry the origin address of the site to a third party. Mail goes the same way, through YourTrend, so letters are signed and the server’s hostname never appears in a header.
12Technology stack
- PHP 8.3 without a framework, a thin PDO wrapper and prepared statements everywhere;
- MariaDB, schema kept as numbered migration files rather than as somebody’s memory;
- media stored outside the web root and served by a controller, so an upload is never an executable path;
- a build step that hashes assets and inlines nothing that should be cached, with a kill switch for when it misbehaves;
- SOCKS5 egress for every outbound call, mail through YourTrend SMTP;
- Payora for crypto invoices, Astrina for sign-in and editorial content, Adgora for house advertising.
13The result
Seven live services on one codebase, one account and one balance across all of them, with attributes and filters that differ as much as the catalogues do. A new vertical is a set of attributes and a front page — not a new project, not a second user table, not a second payment integration.
What the owner gets day to day is one place to look. Moderation, money, sellers and content are managed from the shared control panel of the whole group, and the marketplace exposes them through a signed admin API rather than through a second admin site of its own.
14Takeaways
The lesson we would carry into the next marketplace: share the machinery, not the meaning. Accounts, money, moderation and search are the same problem in every vertical and must be written once. Attributes, filters and the shape of a listing are the product itself, and forcing them into one universal table is how marketplaces become unusable for everyone at the same time.
The second lesson is about money. The place where a payment is described must be a single function, because something else will replay it — a cron, a retry, a reconciliation — and two descriptions of one payment is the cheapest way to lose money quietly.
If you are building a marketplace, a classifieds board or a multi-vertical platform on one engine, this is the kind of work we do end to end.



