Event website development using 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1173
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    745
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

Event Website Development on 1C-Bitrix

An event website is a transactional system, not a brochure. Registration, ticket sales, email notifications, QR-code generation for check-in, attendee dashboards — all of this runs on the sale, catalog, and subscribe modules plus a set of carefully structured info blocks. Get the data architecture wrong at the start, and six months later when the second conference launches, you discover that the "Events" info block holds speakers, schedule slots, and tickets in a single flat structure. Refactoring on production means migrating data, rewriting component templates, and rebuilding the cart logic.

Info Block Architecture

Four info blocks minimum. You can collapse schedule into events, but that falls apart the moment you need multi-day conferences with parallel tracks.

"Events" info block (type events) — the primary entity. Properties:

  • DATE_START, DATE_END — Date/time type. Used in filtering and sorting; an index on b_iblock_element_property by IBLOCK_PROPERTY_ID + VALUE_DATE is required
  • VENUE — element link to a "Venues" info block or Highload block containing address, coordinates, hall capacity
  • CAPACITY — integer, maximum attendees. Validated during checkout via the OnSaleOrderBeforeSaved handler
  • STATUS — list: "Registration Open", "Registration Closed", "Completed", "Cancelled"
  • STREAM_URL — string, broadcast link (YouTube/Vimeo). Displayed only to authenticated users with a paid ticket
  • SCHEMA_ORG — computed at render time in result_modifier.php, not stored in the database

"Speakers" info block (type speakers) — separate from events. One speaker presents at multiple events; binding via a multiple "Element link" (E) property. Properties: PHOTO (file), BIO (HTML/text), COMPANY, POSITION, SOCIAL_LINKS (multiple string).

"Schedule" info block (type schedule) — one element per time slot. Properties:

  • EVENT_ID — link to event
  • SPEAKER_ID — multiple link to speakers
  • TIME_START, TIME_END — time within the day
  • HALL — track/room for parallel sessions
  • SLOT_TYPE — list: "Talk", "Workshop", "Break", "Networking"

"Tickets" info block (type tickets) — connected to the catalog module. Each ticket type is an info block element attached as a product in the trade catalog. Properties: EVENT_ID, TICKET_TYPE (Standard, VIP, Online), AVAILABLE_SEATS, VALID_UNTIL.

Why not Highload blocks for everything? Speakers and schedule hold tens to hundreds of records. Standard info blocks give you news.list, news.detail components and the admin visual editor out of the box. Highload makes sense for venues (if there are 500+) and for the check-in log (thousands of QR scan records).

Ticket Sales — the Core Complexity

A ticket is a product in the Bitrix catalog, but with non-standard logic: date binding, quantity limits, and it's nominative (tied to a specific attendee).

Product = element in the "Tickets" info block, connected to the trade catalog via CCatalog::Add(). Each ticket type for each event is a separate product. A 500-person conference with three ticket types means three catalog products, each with its own stock quantity.

Inventory management enforces capacity limits. A warehouse is created in the catalog module; each ticket product's quantity equals the available seats. When an order is placed, the sale module decrements stock automatically. When stock reaches zero, the "Buy" button switches to "Sold Out" — checked via CCatalogProduct::GetByID() in the component template.

Order flow in detail:

  1. User selects an event and ticket type
  2. Add to cart via \Bitrix\Sale\Basket::addItem() — not the legacy CSaleBasket::Add()
  3. During checkout — a custom step in sale.order.ajax with fields: attendee name, email, phone, company. These values are stored as order properties (\Bitrix\Sale\Order::setField()) and basket properties
  4. Payment via a gateway: YooKassa, Stripe, or PayPal. Configured in the sale module → "Payment Systems" → handler yookassa or a custom handler at local/php_interface/include/sale_payment/
  5. On successful payment — the OnSalePaymentEntitySaved event fires. The handler generates a QR code, sends the ticket email, and creates a CRM lead

Real-time capacity enforcement. Standard inventory works at order level, but time passes between adding to cart and paying. To avoid selling ticket 501 for a 500-seat event:

  • The OnSaleOrderBeforeSaved handler checks current stock and rejects the order if no seats remain
  • Soft reservation via CCatalogProduct::Update() decrements QUANTITY on add-to-cart. An agent restores it after 30 minutes if the order is not completed

Promo codes — via catalog.discount. A discount rule with condition "Coupon code = X", discount type — percentage or fixed amount. In the sale.order.ajax template, add a coupon input field; application through \Bitrix\Sale\Discount::setBasketItemDiscount(). For early-bird pricing — a discount with a date condition: active until a specific date, deactivates automatically.

Registration and CRM Integration

Free event registration bypasses the sale module entirely. A custom component project:event.register with a form:

  • Frontend validation (JS) and backend validation (in component.php)
  • Record in a "Registrations" Highload block: EVENT_ID, USER_ID, STATUS, REGISTERED_AT
  • CRM lead creation via REST API: CRest::call('crm.lead.add', [...]) or directly through the crm module if Bitrix24 is deployed on the same server

For paid events, registration happens automatically on payment. The OnSalePaymentEntitySaved handler creates the Highload block record and updates the CRM lead status.

Attendee dashboard is built on sale.personal.section with a custom template:

  • List of registered events (Highload block query by USER_ID)
  • Order history and tickets (stock sale.personal.order)
  • Ticket download with QR code (PDF generated via TCPDF or Dompdf)
  • Broadcast access (STREAM_URL link appears 30 minutes before start)
  • Post-event attendance certificate (PDF with data from the Highload block)

QR Code and Check-In

On successful payment, a QR code is generated. Content — a unique order hash: md5($orderId . $userId . $salt). The hash is stored in the order property QR_HASH.

QR generation uses the endroid/qr-code Composer package, loaded in local/php_interface/. The QR is embedded in the PDF ticket and sent by email.

At the venue — a mobile app or a web page at /check-in/ with a camera scanner (the html5-qrcode library). The scanned hash is sent as a POST request to /api/checkin/. The controller checks:

  1. Does an order with this QR_HASH exist
  2. Is the order paid (\Bitrix\Sale\Order::isPaid())
  3. Has the ticket already been used (CHECKED_IN order property)

On success — the order property is updated, a record is written to the check-in Highload block, and a 200 response with attendee data is returned.

Event Calendar

Two approaches:

Custom component + FullCalendar.js. The project:event.calendar component returns a JSON array of events via AJAX. On the frontend — FullCalendar with eventSources pointing to /api/events/calendar/. Each event object contains title, start, end, url (link to the detail page), color (by event type). JSON response caching — 10 minutes with tagged cache on the events info block.

Stock iblock.element.list component with a custom template rendering a date grid. Easier to maintain but less interactive — no month/week/day switching without custom JS.

For event series (annual conference) — filtering by info block section. Each year = section, events inside = elements.

Countdown Timer and Live Streaming

The timer is a JS component that reads DATE_START from a data attribute. Server time is passed via <?= time() ?> for synchronization. No external API — pure setInterval calculating the difference.

Streaming — a YouTube/Vimeo iframe wrapped in an access-controlled component. project:event.stream checks in component.php:

  • Is the user authenticated
  • Does the user have a paid order with an "Online" or "VIP" ticket for this event
  • Has the event ended

If the check fails — redirect to the ticket purchase page.

Email Notifications

The subscribe module plus main module mail events:

  • Registration confirmation — mail event EVENT_REGISTER_CONFIRM, template in Settings → Mail Events
  • Ticket after paymentEVENT_TICKET_PAID, with PDF attachment
  • 24-hour reminder — a CAgent that selects events where DATE_START = tomorrow and sends emails to registered attendees via CEvent::Send()
  • Schedule change notificationOnAfterIBlockElementUpdate handler on the Schedule info block, sends updates to event attendees

SEO: Schema.org Event Markup

In the event detail page's result_modifier.php, a JSON-LD block is generated:

{
  "@context": "https://schema.org",
  "@type": "Event",
  "name": "...",
  "startDate": "2025-09-15T10:00:00+03:00",
  "endDate": "2025-09-15T18:00:00+03:00",
  "location": {
    "@type": "Place",
    "name": "...",
    "address": "..."
  },
  "offers": {
    "@type": "Offer",
    "price": "...",
    "priceCurrency": "USD",
    "availability": "https://schema.org/InStock",
    "validFrom": "..."
  },
  "performer": [...]
}

Output via $APPLICATION->AddHeadString() in the <head>. The availability field updates dynamically: InStock when tickets are available, SoldOut when stock is zero.

Development Stages

  1. Planning (1-2 weeks) — info block structure, data schema, page wireframes, registration and payment workflow documentation
  2. Design (1-2 weeks) — page layouts: event catalog, detail page, schedule, attendee dashboard, check-in screen
  3. Backend (2-4 weeks) — info blocks, trade catalog, registration and payment components, gateway integration, QR generation
  4. Frontend (2-3 weeks) — component templates, calendar, countdown timer, responsive layouts, check-in scanner page
  5. Integrations (1-2 weeks) — CRM, email notifications, streaming embed
  6. Testing (1-2 weeks) — functional, payment in sandbox mode, load testing (simulating 500 concurrent purchases)
  7. Launch (3-5 days) — deployment, monitoring, validation on a real event
Project Scope Estimated Timeline
Single event, registration without payment 3-5 weeks
Event with ticket sales and QR check-in 6-9 weeks
Multi-event platform with attendee dashboard 8-12 weeks

Timelines depend on the number of ticket types, external integrations, and attendee dashboard requirements.