Columns
As for an Excel import: “Unit” or “№”, “Status”, “Price”, “Area” — in English or Ukrainian. Column order and extra columns don’t matter.
Integrations
Already keep your sales chart in a Google Sheet or a CRM? There is no second one to maintain. Budvid takes statuses and prices from there and sends buyers’ requests to your CRM.
Publish the sheet — Budvid updates statuses and prices from it every hour. No developer needed.
2Your CRM sends statuses and prices in one request — the presentation shows them within ~15 seconds.
3Every request from the presentation arrives as JSON at your address — signed, retried and logged.
Way 1 · no developer needed
Your sales team keeps working in its own sheet. Once an hour Budvid reads the published sheet and updates the units’ statuses and prices in the presentation.
As for an Excel import: “Unit” or “№”, “Status”, “Price”, “Area” — in English or Ukrainian. Column order and extra columns don’t matter.
A unit is “12”, “Apt 12” or “Flat 12”. Statuses are available, reserved, sold. Prices are “€ 81,000”, “81000.00”, “81000”.
The cabinet says what exactly: not a publication link, the sheet no longer published, columns or unit numbers not found.
Anyone with the link can open a published sheet. Publish only the sheet with statuses and prices — no buyers’ names or phone numbers.
Way 2 · for your developer
A unit is sold or reserved in the CRM — the CRM sends the chart to Budvid in one request, and buyers see the change in the presentation within ~15 seconds.
curl -X PUT "<API_URL>/v1/projects/<slug>/sales" \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{"sales": [
{"id": "flat-12", "status": "sold", "price": 81000},
{"id": "flat-13", "status": "reserved", "price": 79500, "area": 57.6},
{"id": "flat-14", "status": "available", "price": 84000}
]}'
Answer: {"saved": 3}. A GET to the same address returns the current chart.
<API_KEY>API keyCabinet → “Plan” → “API keys” → “Create a key”. The key is shown once. Keys come with the Complex, Developer, Agency 100 and Agency 300 plans.<API_URL>Service addressCabinet → sign-in form → “Or with an API key” → the “Service address” field.<slug>The presentation’s name in its addressbudvid.com/p/<slug> — the published presentation’s link.idA flat or a houseThe id column of the CSV from the cabinet: the “Sales chart” tab → “Download CSV”. For example, flat-12.statusavailable · reserved · soldAvailable, reserved, sold.priceA whole number or nullIn the project’s currency, chosen in its profile. null — no price of yours.areaArea, m² — optional5 to 5000, to a tenth. Without it — the area from the plan.The list replaces the whole chart: send all units together, up to 3000 in one request. A unit missing from the list shows as available.
Way 3 · requests
A buyer leaves a request in the 3D presentation — Budvid sends it to your address at once. For example: an incoming webhook in your CRM — Bitrix24, KeyCRM, Binotel or your own system.
webhook.test event, and the “Deliveries” tab shows your server’s response code.POST https://crm.example.com/hooks/budvid
Content-Type: application/json
X-Archviz-Event: lead.created
X-Archviz-Delivery: dlv_…
X-Archviz-Signature: t=1761400000,v1=5f2c…
{
"id": "evt_…",
"event": "lead.created",
"created": 1761400000,
"data": {
"id": "lead_…",
"project": "<slug>",
"unit": "flat-12",
"name": "Olena",
"phone": "+380 67 123 45 67",
"note": null,
"lang": "en",
"created": 1761400000
}
}
v1 is the hex HMAC-SHA256 of <t>.<request body> with your secret. Reject requests whose t is older than 5 minutes.
// Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody, header, secret) {
const p = Object.fromEntries(
header.split(',').map((s) => s.split('=')));
const age = Math.abs(Date.now() / 1000 - Number(p.t));
if (!p.t || !p.v1 || age > 300) return false;
const expected = createHmac('sha256', secret)
.update(`${p.t}.${rawBody}`).digest('hex');
return expected.length === p.v1.length &&
timingSafeEqual(Buffer.from(expected), Buffer.from(p.v1));
}
// PHP
$body = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_ARCHVIZ_SIGNATURE'] ?? '';
parse_str(str_replace(',', '&', $header), $p);
$ok = isset($p['t'], $p['v1'])
&& abs(time() - (int) $p['t']) <= 300
&& hash_equals(
hash_hmac('sha256', $p['t'] . '.' . $body, $secret),
$p['v1']);
unit — the id of the unit the buyer was looking at, or null; created — Unix time in seconds; lang — the presentation’s language.
Delivered means your server answered 2xx within 10 seconds. Otherwise we try again after 1 min, 5 min, 30 min, 2 h and 12 h.
The request goes only to the address you gave: a redirect (3xx) is not followed and counts as a failed attempt.
If your CRM expects its own fields, put a small handler between Budvid and the CRM: it checks the signature and maps the fields to the CRM’s format.
There is no ready-made module. Requests reach the CRM through an incoming webhook or your own handler, and the CRM sends statuses and prices over the API — as described above.
There is one chart: every update — from the sheet, over the API or by hand in the cabinet — is written over the previous one. Choose one source so they don’t overwrite each other.
About 15 seconds after they are written. From a Google Sheet we write every hour or on “Update now”; over the API — right after your request.
No: the presentation shows only the units’ statuses, prices and areas. But anyone with the link can open a sheet published in Google, so keep no buyers’ data on that sheet.
Write what you keep your sales chart and requests in — we’ll tell you how to connect it to Budvid.