diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index e7591a0bc18983727527dc6072beb76de5bc11c3..809483cc879147c3cfc0cca9fb0f407d1780a56a 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -111,6 +111,9 @@ set(SRCS
   global/Reco.cxx
   qa/DigiEventQa.cxx
   qa/Histo1D.cxx
+  qa/CanvasConfig.cxx 
+  qa/PadConfig.cxx
+  qa/QaData.cxx
   ca/TrackingChain.cxx
   ca/qa/CaQaBuilder.cxx
   ca/qa/CaQaConfig.cxx
diff --git a/algo/ca/TrackingChain.cxx b/algo/ca/TrackingChain.cxx
index e115b6316cb7e3e28c35aad582534cc6bc96465f..e5533656fdbecb74698764942bb6441e194c270b 100644
--- a/algo/ca/TrackingChain.cxx
+++ b/algo/ca/TrackingChain.cxx
@@ -34,24 +34,7 @@ using cbm::algo::ca::constants::clrs::GNb;  // grin bald text
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-TrackingChain::TrackingChain(std::shared_ptr<HistogramSender> histoSender) : fpSender(histoSender)
-{
-  // ------ Initialize the histogram sender
-  if (fpSender) {
-    auto histCfgs = fQaBuilder.GetConfig().GetHistogramConfigs();
-    auto canvCfgs = fQaBuilder.GetConfig().GetCanvasConfigs();
-    fpSender->PrepareAndSendMsg(std::pair<uint32_t, uint32_t>(histCfgs.size(), canvCfgs.size()),
-                                zmq::send_flags::sndmore);
-    for (const auto& cfg : histCfgs) {
-      fpSender->PrepareAndSendMsg(cfg, zmq::send_flags::sndmore);
-    }
-    for (const auto& cfg : canvCfgs) {
-      fpSender->PrepareAndSendMsg(cfg, zmq::send_flags::sndmore);
-    }
-    // Histograms serialization and emission to close multi-part message
-    fpSender->PrepareAndSendMsg(std::vector<Histo1D>{}, zmq::send_flags::none);
-  }
-}
+TrackingChain::TrackingChain(std::shared_ptr<HistogramSender> histoSender) : fQaBuilder(ca::qa::Builder(histoSender)) {}
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
@@ -73,7 +56,10 @@ void TrackingChain::Init()
   fCaFramework.ReceiveParameters(std::move(parameters));
 
   // ------ Initialize QA builder
-  fQaBuilder.RegisterParameters(&fCaFramework.GetParameters());
+  if (fQaBuilder.IsSenderDefined()) {
+    fQaBuilder.RegisterParameters(&fCaFramework.GetParameters());
+    fQaBuilder.Init();
+  }
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -158,14 +144,11 @@ TrackingChain::Output_t TrackingChain::PrepareOutput()
   output.monitorData = fCaMonitorData;
 
   // QA
-  if (fpSender) {
+  if (fQaBuilder.IsSenderDefined()) {
     fQaBuilder.RegisterInputData(&fCaFramework.GetInputData());
     fQaBuilder.RegisterTracks(&output.tracks);
     fQaBuilder.RegisterRecoHitIndices(&fCaFramework.fRecoHits);
-    auto qaData = fQaBuilder.Build();
-    L_(info) << "TrackingChain: " << qaData.ToString();
-    fpSender->PrepareAndSendMsg(qaData.VectorOfH1(), zmq::send_flags::none);
-    L_(info) << "TrackingChain: Published " << qaData.VectorOfH1().size() << " 1D-histograms";
+    fQaBuilder.Build();
   }
 
 
diff --git a/algo/ca/TrackingChain.h b/algo/ca/TrackingChain.h
index 03d5cc430aa396cb86b78ef50ebdf04735102f6d..782a6c67a88617c255858be345f92baf6a24acf9 100644
--- a/algo/ca/TrackingChain.h
+++ b/algo/ca/TrackingChain.h
@@ -95,9 +95,6 @@ namespace cbm::algo
     template<ca::EDetectorID DetID>
     void ReadHits(PartitionedSpan<const ca::HitTypes_t::at<DetID>> hits);
 
-    /// \brief
-
-
     // *************************
     // **  Framework variables
 
@@ -105,8 +102,7 @@ namespace cbm::algo
     ca::TrackingMonitorData fCaMonitorData{};      ///< CA monitor data object
     ca::Framework fCaFramework{};                  ///< CA framework instance
     ca::DataManager fCaDataManager{};              ///< CA data manager
-    ca::qa::Builder fQaBuilder{ca::qa::Config()};  ///< CA QA builder
-    std::shared_ptr<HistogramSender> fpSender;     ///< Histogram sender
+    ca::qa::Builder fQaBuilder;                    ///< CA QA builder
 
     // ************************
     // **  Auxilary variables
diff --git a/algo/ca/qa/CaQaBuilder.cxx b/algo/ca/qa/CaQaBuilder.cxx
index 19e210fa8159886b6e43d62abca7325b6d0a9078..a319fd510f9e7eee27296542b8a1c0ee7d3d1196 100644
--- a/algo/ca/qa/CaQaBuilder.cxx
+++ b/algo/ca/qa/CaQaBuilder.cxx
@@ -14,33 +14,114 @@
 #include "CaParameters.h"
 #include "CaTrack.h"
 
+#include <fmt/format.h>
+
+using cbm::algo::CanvasConfig;
+using cbm::algo::Histo1D;
+using cbm::algo::PadConfig;
+using cbm::algo::QaData;
 using cbm::algo::ca::constants::math::Pi;
 using cbm::algo::ca::qa::Builder;
-using cbm::algo::ca::qa::Config;
-using cbm::algo::ca::qa::Data;
-using cbm::algo::ca::qa::H1Key;
+
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-Data Builder::Build()
+void Builder::Init()
 {
-  Data res(fConfig);
+  if (!fpSender.get()) {
+    return;
+  }
+
+  // ---- Init histograms
+  // TODO: Provide definition from config
+  constexpr int nParPads                              = 4;
+  std::array<std::string, knTrkParPoints> vsPointName = {"first", "last"};
+  for (int i = 0; i < knTrkParPoints; ++i) {
+    {
+      auto sName      = fmt::format("track_{}_theta", vsPointName[i]);
+      auto sTitl      = fmt::format("#theta at {} hit; #theta", vsPointName[i]);
+      fvphTrkTheta[i] = fQaData.MakeObj<Histo1D>(62, 0., 90., sName, sTitl);
+    }
+    {
+      auto sName    = fmt::format("track_{}_phi", vsPointName[i]);
+      auto sTitl    = fmt::format("#phi at {} hit; #phi", vsPointName[i]);
+      fvphTrkPhi[i] = fQaData.MakeObj<Histo1D>(62, -180., 180., sName, sTitl);
+    }
+    {
+      auto sName        = fmt::format("track_{}_chi2_ndf", vsPointName[i]);
+      auto sTitl        = fmt::format("#chi^{{2}}/NDF at {} hit; #chi^{{2}}/NDF", vsPointName[i]);
+      fvphTrkChi2Ndf[i] = fQaData.MakeObj<Histo1D>(100, 0., 20., sName, sTitl);
+    }
+    {
+      auto sName    = fmt::format("track_{}_hit_station", vsPointName[i]);
+      auto sTitl    = fmt::format("Station of {} hit;ID_{{sta}}", vsPointName[i]);
+      fvphHitSta[i] = fQaData.MakeObj<Histo1D>(100, -.5, 10.5, sName, sTitl);
+    }
+  }
+  fphTrkNofHits = fQaData.MakeObj<Histo1D>(11, -0.5, 10.5, "n_hits", "Number of hits;N_{hit}");
+
+  // ---- Init canvases
+  {
+    // Track parameters at first/last track hit
+    for (int i = 0; i < knTrkParPoints; ++i) {
+      auto sCanvName = fmt::format("ca_trk_{}_hit", vsPointName[i]);
+      auto sCanvTitl = fmt::format("Track parameters at {} hit", vsPointName[i]);
+      auto canv      = CanvasConfig(sCanvName, sCanvTitl);
+      std::vector<PadConfig> pads;
+      pads.reserve(nParPads);
+      for (int iPad = 0; iPad < nParPads; ++iPad) {
+        auto& pad = pads.emplace_back();
+        pad.SetLog(false, true);
+        pad.SetGrid(true, true);
+      }
+      pads[0].RegisterHistogram(fvphTrkTheta[i], "hist");
+      pads[1].RegisterHistogram(fvphTrkPhi[i], "hist");
+      pads[2].RegisterHistogram(fvphTrkChi2Ndf[i], "hist");
+      pads[3].RegisterHistogram(fvphHitSta[i], "hist");
+      for (auto& pad : pads) {
+        canv.AddPadConfig(pad);
+      }
+      fQaData.AddCanvasConfig(canv);
+    }
+
+    // Number of hits in track
+    {
+      auto canv = CanvasConfig("ca_nhits_in_trk", "Number of hit in track");
+      auto pad  = PadConfig();
+      pad.SetGrid(true, true);
+      pad.SetLog(false, true);
+      pad.RegisterHistogram(fphTrkNofHits, "hist");
+      canv.AddPadConfig(pad);
+      fQaData.AddCanvasConfig(canv);
+    }
+  }
+
+  fQaData.Init(fpSender);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void Builder::Build()
+{
+  if (!fpSender.get()) {
+    return;
+  }
 
   if (!fpParameters) {
     LOG(error) << "cbm::algo::ca::qa::Builder::Build(): parameters object is undefined";
-    return res;
+    return;
   }
   if (!fpInputData) {
     LOG(error) << "cbm::algo::ca::qa::Builder::Build(): input data object is undefined";
-    return res;
+    return;
   }
   if (!fpvTracks) {
     LOG(error) << "cbm::algo::ca::qa::Builder::Build(): tracks vector is undefined";
-    return res;
+    return;
   }
   if (!fpvRecoHits) {
     LOG(error) << "cbm::algo::ca::qa::Builder::Build(): reco hit indices vector is undefined";
-    return res;
+    return;
   }
 
   // Fill track histograms
@@ -51,16 +132,20 @@ Data Builder::Build()
     int iFstHit = (*fpvRecoHits)[trkFirstHit];
     int iLstHit = (*fpvRecoHits)[trkFirstHit + nHits - 1];
 
-    //res.H1(H1Key::TrackFirstTx).Add(track.fParFirst.GetTx());
-    //res.H1(H1Key::TrackFirstTy).Add(track.fParFirst.GetTy());
-    res.H1(H1Key::FstTrkTheta).Add(track.fParFirst.GetTheta() * 180. / Pi);
-    res.H1(H1Key::FstTrkPhi).Add(track.fParFirst.GetPhi() * 180. / Pi);
-    res.H1(H1Key::FstChi2Ndf).Add(track.fParFirst.GetChiSq() / track.fParFirst.GetNdf());
-    res.H1(H1Key::FstHitSta).Add(fpInputData->GetHit(iFstHit).Station());
-    res.H1(H1Key::LstHitSta).Add(fpInputData->GetHit(iLstHit).Station());
-    res.H1(H1Key::TrkNofHits).Add(nHits);
+    // Distributions in different track points
+    for (int ip = 0; ip < knTrkParPoints; ++ip) {
+      int iHit           = (ip == 0 ? iFstHit : iLstHit);
+      const auto& trkPar = (ip == 0 ? track.fParFirst : track.fParLast);
+      const auto& hit    = fpInputData->GetHit(iHit);
+      fvphTrkTheta[ip]->Add(trkPar.GetTheta() * 180. / Pi);
+      fvphTrkPhi[ip]->Add(trkPar.GetPhi() * 180. / Pi);
+      fvphTrkChi2Ndf[ip]->Add(trkPar.GetChiSq() / trkPar.GetNdf());
+      fvphHitSta[ip]->Add(hit.Station());
+    }
+    // Other distributions
+    fphTrkNofHits->Add(nHits);
     trkFirstHit += nHits;
   }
 
-  return res;
+  fQaData.Send(fpSender);
 }
diff --git a/algo/ca/qa/CaQaBuilder.h b/algo/ca/qa/CaQaBuilder.h
index c2fb40ebe40c2a818518f40318434138015f3257..f9a6748f953c37066da641ef9a6dd5b031de7cb1 100644
--- a/algo/ca/qa/CaQaBuilder.h
+++ b/algo/ca/qa/CaQaBuilder.h
@@ -11,15 +11,19 @@
 
 #include "CaHit.h"  // for HitIndex_t
 #include "CaQaBuilder.h"
-#include "CaQaData.h"
 #include "CaVector.h"
+#include "QaData.h"
 
-namespace cbm::algo::ca
+namespace cbm::algo
 {
-  template<typename DataT>
-  class Parameters;
-  class InputData;
-  class Track;
+  class Histo1D;
+  namespace ca
+  {
+    template<typename DataT>
+    class Parameters;
+    class InputData;
+    class Track;
+  }  // namespace ca
 }  // namespace cbm::algo::ca
 
 namespace cbm::algo::ca::qa
@@ -33,11 +37,12 @@ namespace cbm::algo::ca::qa
     using HitIndexV_t = ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>>;
 
     /// \brief Default destructor
-    Builder() = delete;
+    /// \param pSender  Pointer to the histogram sender
+    Builder(std::shared_ptr<HistogramSender> pSender) : fpSender(pSender){};
 
     /// \brief Constructor from the configuration object
     /// \param config  QA configuration object
-    Builder(const Config& config) : fConfig(config) {}
+    Builder() = default;
 
     /// \brief Copy constructor
     Builder(const Builder&) = delete;
@@ -55,10 +60,13 @@ namespace cbm::algo::ca::qa
     Builder& operator=(Builder&&) = delete;
 
     /// \brief QA execution function
-    Data Build();
+    void Build();
 
-    /// \brief Gets QA config
-    const Config& GetConfig() const { return fConfig; }
+    /// \brief Initializes the QA
+    void Init();
+
+    /// \brief Checks, if the histogram sender is defined
+    bool IsSenderDefined() const { return static_cast<bool>(fpSender.get()); }
 
     /// \brief Registers tracking input data
     /// \note  Call per TS
@@ -76,11 +84,23 @@ namespace cbm::algo::ca::qa
     /// \note  Call per run
     void RegisterParameters(const Parameters<fvec>* pParameters) { fpParameters = pParameters; }
 
+
    private:
-    Config fConfig;                                   ///< QA configuration
-    const Parameters<fvec>* fpParameters  = nullptr;  ///< Pointer to tracking parameters
-    const Vector<Track>* fpvTracks        = nullptr;  ///< Pointer to tracks vector
-    const InputData* fpInputData          = nullptr;  ///< Pointer to input data
-    const Vector<HitIndex_t>* fpvRecoHits = nullptr;  ///< Pointer to reco hit indices
+    QaData fQaData{"CaQa"};  ///< QA data
+
+    std::shared_ptr<HistogramSender> fpSender = nullptr;  ///< Histogram sender
+    const Parameters<fvec>* fpParameters      = nullptr;  ///< Pointer to tracking parameters
+    const Vector<Track>* fpvTracks            = nullptr;  ///< Pointer to tracks vector
+    const InputData* fpInputData              = nullptr;  ///< Pointer to input data
+    const Vector<HitIndex_t>* fpvRecoHits     = nullptr;  ///< Pointer to reco hit indices
+
+    // ----- List of the histograms ------------------------------------------------------------------------------------
+    static constexpr int knTrkParPoints = 2;  ///< Number of track points to build par distributions
+
+    std::array<Histo1D*, knTrkParPoints> fvphTrkTheta   = {{0}};    ///< hist: theta at first/last hit
+    std::array<Histo1D*, knTrkParPoints> fvphTrkPhi     = {{0}};    ///< hist: phi at first/last hit
+    std::array<Histo1D*, knTrkParPoints> fvphTrkChi2Ndf = {{0}};    ///< hist: chi2/NDF at first/last hit
+    std::array<Histo1D*, knTrkParPoints> fvphHitSta     = {{0}};    ///< hist: station of first/last hit
+    Histo1D* fphTrkNofHits                              = nullptr;  ///< hist: number of hits in track
   };
 }  // namespace cbm::algo::ca::qa
diff --git a/algo/qa/CanvasConfig.cxx b/algo/qa/CanvasConfig.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..449eba144c4b56f87d38740ed3151748f9f71a0b
--- /dev/null
+++ b/algo/qa/CanvasConfig.cxx
@@ -0,0 +1,56 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CanvasConfig.cxx
+/// \date   12.02.2024
+/// \brief  A class representing a canvas in the message for the Histogram server (implementation)
+/// \author S.Zharko <s.zharko@gsi.de>
+
+#include "CanvasConfig.h"
+
+#include <sstream>
+
+using cbm::algo::CanvasConfig;
+using cbm::algo::PadConfig;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+CanvasConfig::CanvasConfig(std::string_view name, std::string_view title, int nPadsX, int nPadsY)
+  : fsName(name)
+  , fsTitle(title)
+  , fNofPadsX(nPadsX)
+  , fNofPadsY(nPadsY)
+{
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void CanvasConfig::AddPadConfig(const PadConfig& pad)
+{
+  fvsPadConfigs.push_back(pad.ToString());
+
+  // Re-calculate number of pads
+  if (fNofPadsX * fNofPadsY < static_cast<int>(fvsPadConfigs.size())) {
+    if (fNofPadsX > fNofPadsY) {
+      ++fNofPadsY;
+    }
+    else {
+      ++fNofPadsX;
+    }
+  }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string CanvasConfig::ToString() const
+{
+  // TODO: What to return (throw), if 0 pads defined?
+
+  std::stringstream cfg;
+  cfg << fsName << ';' << fsTitle << ';' << fNofPadsX << ';' << fNofPadsY;
+  for (const auto& padCfg : fvsPadConfigs) {
+    cfg << ';' << padCfg;
+  }
+  return cfg.str();
+}
diff --git a/algo/qa/CanvasConfig.h b/algo/qa/CanvasConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..0efe885c1c57a8192520dafa3790c338130c7286
--- /dev/null
+++ b/algo/qa/CanvasConfig.h
@@ -0,0 +1,63 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CanvasConfig.h
+/// \date   12.02.2024
+/// \brief  A class representing a canvas in the message for the Histogram server
+/// \author S.Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "PadConfig.h"
+
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace cbm::algo
+{
+  /// \class CanvasConfig
+  /// \brief A canvas configuration for the histogram server
+  ///
+  /// The class represents a configuration of the canvas, which can be converted to the initialization
+  /// message for the histogram server.
+  class CanvasConfig {
+   public:
+    /// \brief Constructor
+    /// \param name    Name of the canvas
+    /// \param title   Title of the canvas
+    /// \param nPadsX  Number of pads along x-axis
+    /// \param nPadsY  Number of pads along y-axis
+    CanvasConfig(std::string_view name, std::string_view title, int nPadsX = 1, int nPadsY = 1);
+
+    /// \brief Copy constructor
+    CanvasConfig(const CanvasConfig&) = default;
+
+    /// \brief Move constructor
+    CanvasConfig(CanvasConfig&&) = default;
+
+    /// \brief Copy assignment operator
+    CanvasConfig& operator=(const CanvasConfig&) = default;
+
+    /// \brief Move assignment operator
+    CanvasConfig& operator=(CanvasConfig&&) = default;
+
+    /// \brief Destructor
+    ~CanvasConfig() = default;
+
+    /// \brief Adds a pad to the canvas
+    void AddPadConfig(const PadConfig& pad);
+
+    /// \brief Returns message config
+    std::string ToString() const;
+
+   private:
+    std::string fsName;                      ///< Name of the canvas
+    std::string fsTitle;                     ///< Name of the pad
+    std::vector<std::string> fvsPadConfigs;  ///< Vector of pad config messages
+    int fNofPadsX = 1;                       ///< Number of pads along the x-axis
+    int fNofPadsY = 1;                       ///< Number of pads along the y-axis
+  };
+}  // namespace cbm::algo
diff --git a/algo/qa/PadConfig.cxx b/algo/qa/PadConfig.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..5743b665a6a6c63e741944642b61a2c1dbb2425b
--- /dev/null
+++ b/algo/qa/PadConfig.cxx
@@ -0,0 +1,48 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   Pad.cxx
+/// \date   12.02.2024
+/// \brief  A class representing a Pad in the message for the Histogram server (implementation)
+/// \author S.Zharko <s.zharko@gsi.de>
+
+#include "PadConfig.h"
+
+#include <sstream>
+
+using cbm::algo::PadConfig;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void PadConfig::SetGrid(bool gridX, bool gridY)
+{
+  fbGridX = gridX;
+  fbGridY = gridY;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void PadConfig::SetLog(bool logX, bool logY, bool logZ)
+{
+  fbLogX = logX;
+  fbLogY = logY;
+  fbLogZ = logZ;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::string PadConfig::ToString() const
+{
+  std::stringstream cfg;
+  cfg << fbGridX << ',' << fbGridY << ',' << fbLogX << ',' << fbLogY << ',' << fbLogZ;
+  if (fvObjectList.empty()) {
+    cfg << ',';
+  }
+  else {
+    for (const auto& [name, opt] : fvObjectList) {
+      cfg << ",(" << name << ',' << opt << ')';
+    }
+  }
+  return cfg.str();
+}
diff --git a/algo/qa/PadConfig.h b/algo/qa/PadConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..d95c1cdc1d7387d2aa0097616494ab052ef88bef
--- /dev/null
+++ b/algo/qa/PadConfig.h
@@ -0,0 +1,84 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   PadConfig.h
+/// \date   12.02.2024
+/// \brief  A class representing a pad config in the message for the Histogram server
+/// \author S.Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "Histo1D.h"
+
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace cbm::algo
+{
+  /// \class PadConfig
+  /// \brief A pad configuration for the histogram server
+  ///
+  /// The class represents a configuration of the pad, which can be converted in the part of the
+  /// canvas initialization message for the histogram server.
+  class PadConfig {
+   public:
+    /// \brief Constructor
+    PadConfig() = default;
+
+    /// \brief Copy constructor
+    PadConfig(const PadConfig&) = default;
+
+    /// \brief Move constructor
+    PadConfig(PadConfig&&) = default;
+
+    /// \brief Copy assignment operator
+    PadConfig& operator=(const PadConfig&) = default;
+
+    /// \brief Move assignment operator
+    PadConfig& operator=(PadConfig&&) = default;
+
+    /// \brief Destructor
+    ~PadConfig() = default;
+
+    /// \brief  Set grid flags
+    /// \param  gridX  Flag for x-axis
+    /// \param  gridY  Flag for y-axis
+    void SetGrid(bool gridX, bool gridY = false);
+
+    /// \brief  Sets logarithm axis
+    /// \param  logX  Logarithm flag for x-axis
+    /// \param  logY  Logarithm flag for y-axis
+    /// \param  logZ  Logarithm flag for z-axis
+    void SetLog(bool logX, bool logY = false, bool logZ = false);
+
+    /// \brief  Registers an object in the pad
+    /// \param  name  Name of the object
+    /// \param  opt   Draw options for the object
+    void RegisterObject(std::string_view name, std::string_view opt)
+    {
+      fvObjectList.emplace_back(std::make_pair(name, opt));
+    }
+
+    /// \brief  Registers a histogram in the pad
+    /// \param  hist  Histogram object
+    /// \param  opt   Draw options for the histogram
+    void RegisterHistogram(const Histo1D* hist, std::string_view opt) { RegisterObject(hist->Name(), opt); }
+
+    /// \brief  Returns message config
+    std::string ToString() const;
+
+   private:
+    bool fbGridX = false;  ///< Grid flag for x-axis
+    bool fbGridY = false;  ///< Grid flag for y-axis
+    bool fbLogX  = false;  ///< Log flag for x-axis
+    bool fbLogY  = false;  ///< Log flag for y-axis
+    bool fbLogZ  = false;  ///< Log flag for z-axis
+
+    std::vector<std::pair<std::string, std::string>> fvObjectList;  ///< List of objects on the pad
+  };
+
+
+}  // namespace cbm::algo
diff --git a/algo/qa/QaData.cxx b/algo/qa/QaData.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..955c1ba27c67966dde4db0f439b8079325f45a38
--- /dev/null
+++ b/algo/qa/QaData.cxx
@@ -0,0 +1,64 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   QaData.cxx
+/// \date   12.02.2024
+/// \brief  A unified data-structure to handle QA objects for the online reconstruction (implementation)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#include "QaData.h"
+
+using cbm::algo::QaData;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void QaData::Init(std::shared_ptr<HistogramSender> histSender)
+{
+  if (histSender.get()) {
+    // Forming a histogram config message
+    std::vector<std::pair<std::string, std::string>> vHistCfgs;
+    vHistCfgs.reserve(std::distance(fvH1.begin(), fvH1.end()));
+    for (const auto& hist : fvH1) {
+      vHistCfgs.emplace_back(hist.Name(), fsName);
+    }
+
+    // Forming a canvas config message
+    std::vector<std::pair<std::string, std::string>> vCanvCfgs;
+    vCanvCfgs.reserve(fvsCanvCfgs.size());
+    for (const auto& canv : fvsCanvCfgs) {
+      vCanvCfgs.emplace_back(std::make_pair(canv.substr(0, canv.find_first_of(';')), canv));
+    }
+
+    histSender->PrepareAndSendMsg(std::pair<uint32_t, uint32_t>(vHistCfgs.size(), vCanvCfgs.size()),
+                                  zmq::send_flags::sndmore);
+    for (const auto& cfg : vHistCfgs) {
+      histSender->PrepareAndSendMsg(cfg, zmq::send_flags::sndmore);
+    }
+    for (const auto& cfg : vCanvCfgs) {
+      histSender->PrepareAndSendMsg(cfg, zmq::send_flags::sndmore);
+    }
+    // Histograms serialization and emission to close multi-part message
+    histSender->PrepareAndSendMsg(std::vector<Histo1D>{}, zmq::send_flags::none);
+  }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void QaData::Reset()
+{
+  // TODO: clear the vectors of other object types here as well
+  for (auto& hist : fvH1) {
+    hist.Clear();
+  }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void QaData::Send(std::shared_ptr<HistogramSender> histoSender)
+{
+  std::vector<Histo1D> vHisto1D{std::begin(fvH1), std::end(fvH1)};
+  histoSender->PrepareAndSendMsg(vHisto1D, zmq::send_flags::none);
+  L_(info) << fsName << ": Published " << vHisto1D.size() << " 1D-histograms";
+  this->Reset();
+}
diff --git a/algo/qa/QaData.h b/algo/qa/QaData.h
new file mode 100644
index 0000000000000000000000000000000000000000..263614458c37962cd41f83eef542f109b4945113
--- /dev/null
+++ b/algo/qa/QaData.h
@@ -0,0 +1,87 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   QaData.cxx
+/// \date   12.02.2024
+/// \brief  A unified data-structure to handle QA objects for the online reconstruction
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "CanvasConfig.h"
+#include "Histo1D.h"
+#include "HistogramSender.h"
+
+#include <forward_list>
+#include <log.hpp>
+#include <memory>
+#include <string_view>
+#include <type_traits>
+#include <vector>
+
+namespace cbm::algo
+{
+  /// \class QaData
+  /// \brief Class to handle QA-objects in the online reconstruction
+  class QaData {
+   public:
+    /// \brief Constructor
+    /// \param name  Name of the QA module (appears as the directory name in the output)
+    QaData(std::string_view name) : fsName(name) {}
+
+    /// \brief Copy constructor
+    QaData(const QaData&) = default;
+
+    /// \brief Move constructor
+    QaData(QaData&&) = default;
+
+    /// \brief Copy assignment operator
+    QaData& operator=(const QaData&) = default;
+
+    /// \brief Move assignment operator
+    QaData& operator=(QaData&&) = default;
+
+    /// \brief Destructor
+    ~QaData() = default;
+
+    /// \brief Adds a canvas to the canvas config list
+    /// \param canvas  A CanvasConfig object
+    void AddCanvasConfig(const CanvasConfig& canvas) { fvsCanvCfgs.push_back(canvas.ToString()); }
+
+    /// \brief Creates a QA-object and returns the pointer to it
+    template<class Obj, typename... Args>
+    Obj* MakeObj(Args... args);
+
+    /// \brief Gets module name
+    std::string_view GetName() const { return fsName; }
+
+    /// \brief Sends QA initialization information to the HistogramSender
+    /// \param histoSender  A pointer to the histogram sender
+    void Init(std::shared_ptr<HistogramSender> histoSender);
+
+    /// \brief Resets the histograms
+    void Reset();
+
+    /// \brief Sends QA data to the HistogramSender
+    /// \param histoSender  A pointer to the histogram sender
+    /// \note  Calls this->Reset() after sending the message to the histogram server
+    void Send(std::shared_ptr<HistogramSender> histoSender);
+
+   private:
+    std::string fsName;                         ///< Name of the QA module (used as a directory name)
+    std::forward_list<Histo1D> fvH1      = {};  ///< List of 1D-histograms
+    std::vector<std::string> fvsCanvCfgs = {};  ///< Vector of canvas configs
+  };
+
+  // -------------------------------------------------------------------------------------------------------------------
+  //
+  template<class Obj, typename... Args>
+  Obj* QaData::MakeObj(Args... args)
+  {
+    if constexpr (std::is_same_v<Obj, cbm::algo::Histo1D>) {
+      return &(fvH1.emplace_front(args...));
+    }
+    return nullptr;
+  }
+}  // namespace cbm::algo