Beacon C++ SDK

The Beacon C++ SDK provides native integration for Windows desktop applications, server processes, and cross-platform C++ projects.

Installation

Add Beacon to your CMakeLists.txt:

include(FetchContent)
FetchContent_Declare(
  beacon_sdk
  GIT_REPOSITORY https://github.com/softagility/beacon-sdk-cpp.git
  GIT_TAG        v3.0.0
)
FetchContent_MakeAvailable(beacon_sdk)
 
target_link_libraries(your_app PRIVATE beacon_sdk)

Build from Source

git clone https://github.com/softagility/beacon-sdk-cpp.git
cd beacon-sdk-cpp
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release

Dependencies (libcurl, nlohmann_json, SQLite3) are fetched automatically via CMake FetchContent if not found on the system.

vcpkg

A vcpkg port is planned for a future release. For now, use CMake FetchContent or build from source.

System Requirements

  • C++17 or later
  • Windows 10+, Linux (glibc 2.28+), or macOS 12+
  • CMake 3.25+
  • libcurl, nlohmann_json, SQLite3 — resolved automatically by CMake FetchContent if not present
  • OpenSSL development headers — required on Linux and macOS via find_package(OpenSSL). On Windows, TLS uses Schannel and no extra dependency is needed.

The SDK selects the TLS backend based on platform: Schannel on Windows, OpenSSL on Linux and macOS.

v3.0.0 (breaking change): The config fields were renamed — app_nameproduct (v2.0.0) and app_versionproduct_version (v3.0.0) — and the wire fields source_appproduct and source_versionproduct_version. Upgrading from 1.x: rename opts.app_nameopts.product and opts.app_versionopts.product_version. Values are unchanged. See the CHANGELOG.

Configuration

#include <beacon/beacon.hpp>
 
int main()
{
    auto tracker = beacon::Tracker::configure([](beacon::Options& opts) {
        opts.api_key = "your-api-key";
        opts.api_base_url = "https://api.beacon.softagility.com";
        opts.product = "MyApp";
        opts.product_version = "1.2.0";
    });
 
    tracker->identify("user-123");
    tracker->startSession();
 
    // Your application code here...
 
    tracker->endSession();
    return 0;
}

You can also pass an Options struct directly:

beacon::Options opts;
opts.api_key = "your-api-key";
opts.api_base_url = "https://api.beacon.softagility.com";
opts.product = "MyApp";
opts.product_version = "1.2.0";
 
auto tracker = beacon::Tracker::configure(opts);

Configuration Options

OptionTypeDefaultDescription
api_keystd::stringYour Beacon API key (required)
api_base_urlstd::stringBeacon API base URL (required)
productstd::stringYour product’s slug as registered in the Beacon dashboard (e.g., "inventory-manager"). Sent as the product field on every event. Must match exactly or events are rejected with UNRECOGNIZED_PRODUCT. (required)
product_versionstd::stringYour application’s version string. (required)
enabledbooltrueEnable or disable tracking globally
flush_interval_secondsint60Batch send interval in seconds (1-3600)
max_batch_sizeint25Events per batch, clamped to 1-1000
max_queue_size_mbint10Maximum offline queue size in MB (1-1000)
max_breadcrumbsint25Breadcrumb ring buffer size (0-200)
loggerstd::shared_ptr<beacon::ILogger>nullptrOptional logger for SDK diagnostics
eventsEventDefinitionBuilderBuilder for registering known event categories and names. Optional; populated via tracker->exportEventManifest().

Identifying Users

Before tracking events or sessions, identify the current actor:

tracker->identify("user-123");

Once identified, all subsequent calls use this actor. You can also pass an explicit actor ID to tracking methods.

To read the currently identified actor:

std::string current = tracker->actorId();   // returns "" before identify() is called

Account & License Context

setAccount / setLicense (added in 1.1.0) attach a customer-account ID and / or license ID to every subsequent event, session start, and exception report. Use them for B2B analytics when your application services multiple customer organisations.

tracker->identify("user-123");
tracker->setAccount("acct_acme");                  // customer organisation
tracker->setLicense("sub_acme_team_annual");       // contract / entitlement
tracker->startSession();
tracker->track("reports", "report_exported", {});  // event carries both IDs

Behaviour:

  • Persistence. Values are stored in process memory and applied to every subsequent payload until cleared or reset() is called.
  • Validation. 1-256 chars after trim, no whitespace-only, no control characters (including U+2028 / U+2029). Invalid input is silently ignored at Warning log level and does NOT overwrite a previously valid value.
  • Clearing. clearAccount() and clearLicense() each clear only their own field. reset() clears actor, session, account, license, queue, and breadcrumbs.
  • Opt-out. setAccount / setLicense are no-ops while opted out; clearAccount / clearLicense are always safe to call.
  • Per-call payload. When set, account_id / license_id are included as top-level JSON fields. When unset, the fields are omitted entirely.

Modeling licenses

Prefer per-contract license IDs (one ID shared across every user of a subscription / site / bundle):

tracker->setLicense("sub_acme_team_annual");      // recommended

Per-user / per-seat IDs are accepted but make the dashboard’s License Detail page thin. See the Licenses section of the user manual.

Account- and license-level analytics in the Beacon dashboard are gated to Business and Enterprise plans (Trial also during evaluation). Ingestion itself is tier-blind.

Tracking Events

Events are organized by category and name, with optional properties.

Basic Event

Properties are passed as std::unordered_map<std::string, std::string>. The C++ API doesn’t accept braced initializer lists directly — wrap explicitly:

tracker->track("ui", "button_clicked",
    std::unordered_map<std::string, std::string>{
        {"button_name", "export"},
        {"screen", "dashboard"}
    });

With Explicit Actor

tracker->track("reports", "report_exported", "user-456",
    std::unordered_map<std::string, std::string>{
        {"format", "pdf"},
        {"row_count", "1500"}
    });

Session Management

// Start a session (uses the identified actor)
tracker->startSession();
 
// Or start with an explicit actor ID
tracker->startSession("user-123");
 
// End the current session
tracker->endSession();

Exception Tracking

Track exceptions with severity levels. Breadcrumbs from recent track() calls are attached automatically.

try
{
    process_order(order);
}
catch (const std::exception& ex)
{
    tracker->trackException(ex, beacon::ExceptionSeverity::NonFatal);
    throw;
}

Severity options: beacon::ExceptionSeverity::Fatal and beacon::ExceptionSeverity::NonFatal.

Flushing Events

Events are batched and sent on a timer or when the batch size is reached. To flush manually:

bool success = tracker->flush(); // Blocks up to 30 seconds

Check the last flush result:

beacon::FlushStatus status = tracker->last_flush_status();

Offline Persistence

Events are persisted to a local SQLite database when the network is unavailable. The queue syncs automatically when connectivity returns.

  • Persists across application restarts
  • Thread-safe queue with a configurable size limit (max_queue_size_mb)
  • Best-effort durable retry: events are retained through transient network failures, but if the queue reaches the configured cap, the oldest events are evicted. Permanent HTTP errors (non-retriable 4xx other than 401/402/429) discard the affected events.
  • Call tracker->flush() at shutdown to deliver pending events before the process exits.

Privacy Controls

The SDK supports opt-out and opt-in for user consent:

tracker->optOut();    // stops tracking, clears queue
tracker->optIn();     // resumes tracking

Opt-out state persists across application restarts via the offline-persistence database.

Breaking change in 1.2.0 (unreleased): the methods are renamed from opt_out() / opt_in() to optOut() / optIn() to match the camelCase style of every other multi-word public method on Tracker (startSession, endSession, setAccount, setLicense, trackException, exportEventManifest). The README has always documented the camelCase form. No compatibility alias is provided — call sites pinning the SDK at 1.2.0 or later must update.

Reset

Clear all in-memory state (session, queue, breadcrumbs, actor ID) and generate a new anonymous device ID:

tracker->reset();

Use this on logout to separate user sessions.

Event Manifest

Register known events for import into the Beacon portal’s allowlist:

tracker->exportEventManifest("events.json");

Singleton Access

After calling configure(), retrieve the tracker instance anywhere with:

auto tracker = beacon::Tracker::instance();

Call beacon::Tracker::reset_for_testing() in test teardown to clear the singleton.

Thread Safety

beacon::Tracker is fully thread-safe. Share a single instance across threads.

Lifetime Management

configure() stores the tracker in an internal static shared_ptr, so a local auto tracker going out of scope does not destroy the tracker — it only releases your local reference. The destructor (when it eventually runs) persists in-memory events to disk and joins worker threads, but does not perform a network flush, end the active session, or send any final HTTP requests.

To shut down cleanly, call endSession() and flush() explicitly before your application exits:

auto tracker = beacon::Tracker::configure(configurator);
tracker->identify("user-123");
tracker->startSession();
 
// Track events...
 
// Explicit shutdown
tracker->endSession();
tracker->flush();   // blocks up to 30s waiting for the network round-trip

Without an explicit flush(), in-memory events are persisted to disk and delivered on the next session.

Next Steps