diff --git a/macro/reco/reco_steer.C b/macro/reco/reco_steer.C
index 1fc55ceec3e590049c8579a74c54b14cea58c588..c3d4872b9998def99f52290984171c4e34f15141 100644
--- a/macro/reco/reco_steer.C
+++ b/macro/reco/reco_steer.C
@@ -24,6 +24,7 @@ using std::string;
  ** @param tsaFile    Name of input file (w/o extension .tsa)
  ** @param outFile    Name of output file (w/o extension .digi.root)
  ** @param numTs      Number of timeslices to process. If not specified, all available will be used.
+ ** @param port       Port of http server. If 0, server will not be activated.
  **
  ** Reconstruction from timeslice level, making use of the steering class CbmReco.
  ** Currently included stages:
@@ -36,7 +37,8 @@ using std::string;
  ** the extension .tsa by .digi.root
  **/
 
-void reco_steer(TString tsaFile = "", TString outFile = "", int32_t numTs = std::numeric_limits<int32_t>::max())
+void reco_steer(TString tsaFile = "", TString outFile = "", int32_t numTs = std::numeric_limits<int32_t>::max(),
+                uint32_t port = 8080)
 {
 
   // ========================================================================
@@ -98,7 +100,7 @@ void reco_steer(TString tsaFile = "", TString outFile = "", int32_t numTs = std:
   // -----   Run reconstruction   -------------------------------------------
   TStopwatch timer;
   timer.Start();
-  CbmReco run(inFile.Data(), outFile.Data(), numTs, config);
+  CbmReco run(inFile.Data(), outFile.Data(), numTs, config, port);
   run.Run();
   timer.Stop();
   // ------------------------------------------------------------------------
diff --git a/macro/reco/reco_ts.C b/macro/reco/reco_ts.C
index e520b169d94ecab63c2005abbd702c95d689118a..ea0cebc743deb64122323809161f4842fb639518 100644
--- a/macro/reco/reco_ts.C
+++ b/macro/reco/reco_ts.C
@@ -65,6 +65,7 @@ void reco_ts(TString tsaFile = "", TString outFile = "")
   // TODO: Would need a small up-to-date default input file; the one distributed with
   // the code is outdated.
 
+
   // ----- Default file names   ---------------------------------------------
   if (tsaFile.IsNull()) tsaFile = srcDir + "/input/mcbm_run399_first20Ts";
   TString inFile = tsaFile + ".tsa";
@@ -121,6 +122,13 @@ void reco_ts(TString tsaFile = "", TString outFile = "")
   // ------------------------------------------------------------------------
 
 
+  // -----   Event QA   -----------------------------------------------------
+  auto eventQa = std::make_unique<CbmTaskDigiEventQa>();
+  LOG(info) << myName << ": Added task " << eventQa->GetName();
+  run->AddTask(eventQa.release());
+  // ------------------------------------------------------------------------
+
+
   // -----   Logger settings   ----------------------------------------------
   FairLogger::GetLogger()->SetLogScreenLevel(logLevel.Data());
   FairLogger::GetLogger()->SetLogVerbosityLevel(logVerbosity.Data());
diff --git a/reco/tasks/CMakeLists.txt b/reco/tasks/CMakeLists.txt
index dd87b3a51de4bf5eee4813a1e30b2dc18cd07808..7d98de2467e7d82c137bfb90a86f20ceb3bf3e22 100644
--- a/reco/tasks/CMakeLists.txt
+++ b/reco/tasks/CMakeLists.txt
@@ -13,6 +13,7 @@ set(SRCS
 CbmReco.cxx
 CbmSourceTs.cxx
 CbmTaskBuildEvents.cxx
+CbmTaskDigiEventQa.cxx
 CbmTaskMakeRecoEvents.cxx
 CbmTaskTriggerDigi.cxx
 CbmTaskUnpack.cxx
diff --git a/reco/tasks/CbmReco.cxx b/reco/tasks/CbmReco.cxx
index ccfd00a3167782bea1354549e17e8d5f2de8b7db..373191c2b9e1233e406d4ddb8c95e7667775be81 100644
--- a/reco/tasks/CbmReco.cxx
+++ b/reco/tasks/CbmReco.cxx
@@ -6,6 +6,7 @@
 
 #include "CbmSourceTs.h"
 #include "CbmTaskBuildEvents.h"
+#include "CbmTaskDigiEventQa.h"
 #include "CbmTaskTriggerDigi.h"
 #include "CbmTaskUnpack.h"
 #include "CbmTsEventHeader.h"
@@ -14,6 +15,9 @@
 #include <FairRootFileSink.h>
 #include <FairRunOnline.h>
 
+#include <THttpServer.h>
+#include <TRootSniffer.h>
+
 #include <iostream>
 #include <memory>
 #include <string>
@@ -25,22 +29,25 @@ using std::string;
 
 
 // -----   Constructor from single source   -----------------------------------
-CbmReco::CbmReco(string source, TString outFile, int32_t numTs, const CbmRecoConfig& config)
+CbmReco::CbmReco(string source, TString outFile, int32_t numTs, const CbmRecoConfig& config, uint16_t port)
   : fSourceNames {source}
   , fOutputFileName(outFile)
   , fNumTs(numTs)
   , fConfig(config)
+  , fHttpServerPort(port)
 {
 }
 // ----------------------------------------------------------------------------
 
 
 // -----   Constructor from multiple sources   --------------------------------
-CbmReco::CbmReco(std::vector<string> sources, TString outFile, int32_t numTs, const CbmRecoConfig& config)
+CbmReco::CbmReco(std::vector<string> sources, TString outFile, int32_t numTs, const CbmRecoConfig& config,
+                 uint16_t port)
   : fSourceNames(sources)
   , fOutputFileName(outFile)
   , fNumTs(numTs)
   , fConfig(config)
+  , fHttpServerPort(port)
 {
 }
 // ----------------------------------------------------------------------------
@@ -102,6 +109,10 @@ int32_t CbmReco::Run()
     evtBuild->SetEventWindow(entry.first, entry.second.first, entry.second.second);
   evtBuild->SetOutputBranchPersistent("DigiEvent", fConfig.fStoreEvents);
 
+  // --- Event QA
+  auto evtQa = make_unique<CbmTaskDigiEventQa>();
+  evtQa->Config(fConfig);
+
   // --- Run configuration
   FairRunOnline run(source.release());
   run.SetSink(sink.release());
@@ -109,6 +120,14 @@ int32_t CbmReco::Run()
   run.AddTask(unpack.release());
   run.AddTask(trigger.release());
   run.AddTask(evtBuild.release());
+  run.AddTask(evtQa.release());
+
+  // ----- HttpServer for online monitoring
+  if (fHttpServerPort) {
+    Int_t serverRefreshRate = 100;  // timeslices
+    run.ActivateHttpServer(serverRefreshRate, fHttpServerPort);
+    run.GetHttpServer()->GetSniffer()->SetScanGlobalDir(kFALSE);
+  }
 
   // --- Initialise and start run
   timer.Stop();
diff --git a/reco/tasks/CbmReco.h b/reco/tasks/CbmReco.h
index 32dbdb004859a5bf490afc24718b816418c000e9..28aaf291efaecd9cab7d6fef7b65fbebbe922d9e 100644
--- a/reco/tasks/CbmReco.h
+++ b/reco/tasks/CbmReco.h
@@ -64,8 +64,9 @@ public:
    ** @param outFile Name of output file
    ** @param numTs   Number of timeslices to process. If negative, all available will be used.
    ** @param config  Configuration
+   ** @param port    Port number for the http server. If 0, server will not be activated.
    **/
-  CbmReco(std::string source, TString outFile, int32_t numTs, const CbmRecoConfig& config);
+  CbmReco(std::string source, TString outFile, int32_t numTs, const CbmRecoConfig& config, uint16_t port = 0);
 
 
   /** @brief Standard constructor for list of sources
@@ -73,8 +74,10 @@ public:
    ** @param outFile Name of output file
    ** @param numTs   Number of timeslices to process. If negative, all available will be used.
    ** @param config  Configuration
+   ** @param port    Port number for the http server
    **/
-  CbmReco(std::vector<std::string> sources, TString outFile, int32_t numTs, const CbmRecoConfig& config);
+  CbmReco(std::vector<std::string> sources, TString outFile, int32_t numTs, const CbmRecoConfig& config,
+          uint16_t port = 0);
 
 
   /** @brief Destructor **/
@@ -98,6 +101,7 @@ private:
   TString fOutputFileName               = "";  ///< Output file
   int32_t fNumTs                        = 0;   ///< Number of timeslices to process
   CbmRecoConfig fConfig                 = {};  ///< Configuration parameters
+  uint16_t fHttpServerPort              = 0;
 
   ClassDef(CbmReco, 1);
 };
diff --git a/reco/tasks/CbmRecoTasksLinkDef.h b/reco/tasks/CbmRecoTasksLinkDef.h
index 9d2dbacc5b1bc63f4f357949980a9a098d3508d1..ac0ef949083e0ac7e4329d9aca8f1a5c0ea99b88 100644
--- a/reco/tasks/CbmRecoTasksLinkDef.h
+++ b/reco/tasks/CbmRecoTasksLinkDef.h
@@ -15,6 +15,7 @@
 #pragma link C++ class CbmRecoConfig + ;
 #pragma link C++ class CbmSourceTs + ;
 #pragma link C++ class CbmTaskBuildEvents + ;
+#pragma link C++ class CbmTaskDigiEventQa + ;
 #pragma link C++ class CbmTaskMakeRecoEvents + ;
 #pragma link C++ class CbmTaskTriggerDigi + ;
 #pragma link C++ class CbmTaskUnpack + ;
diff --git a/reco/tasks/CbmTaskDigiEventQa.cxx b/reco/tasks/CbmTaskDigiEventQa.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..d45ff3c4e3cf0b5ecd2d2c6a9eb81171b91af78f
--- /dev/null
+++ b/reco/tasks/CbmTaskDigiEventQa.cxx
@@ -0,0 +1,164 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese [committer] */
+
+#include "CbmTaskDigiEventQa.h"
+
+#include "CbmDigiBranchBase.h"
+#include "CbmDigiManager.h"
+#include "CbmDigiTimeslice.h"
+#include "CbmModuleList.h"
+#include "CbmReco.h"  // for CbmRecoConfig
+
+#include <FairLogger.h>
+#include <FairRunOnline.h>
+
+#include <TH1F.h>
+#include <THttpServer.h>
+#include <TStopwatch.h>
+
+#include <cassert>
+#include <iomanip>
+#include <iostream>
+
+using namespace std;
+
+// -----   Constructor   -----------------------------------------------------
+CbmTaskDigiEventQa::CbmTaskDigiEventQa() : FairTask("DigiEventQa") {}
+// ---------------------------------------------------------------------------
+
+
+// -----   Destructor   ------------------------------------------------------
+CbmTaskDigiEventQa::~CbmTaskDigiEventQa() {}
+// ---------------------------------------------------------------------------
+
+
+// -----   Configuration   ---------------------------------------------------
+void CbmTaskDigiEventQa::Config(const CbmRecoConfig& config)
+{
+  auto limitIt = config.fEvtbuildWindows.find(ECbmModuleId::kSts);
+  if (limitIt != config.fEvtbuildWindows.end()) {
+    double lower     = limitIt->second.first - 10.;
+    double upper     = limitIt->second.second + 10.;
+    fHistDigiTimeSts = new TH1F("hDigiTimeSts", "STS digi time in event", 100, lower, upper);
+  }
+}
+// ---------------------------------------------------------------------------
+
+
+// -----   Execution   -------------------------------------------------------
+void CbmTaskDigiEventQa::Exec(Option_t*)
+{
+
+  // --- Timer and counters
+  TStopwatch timer;
+  timer.Start();
+  double sumT          = 0.;
+  double sumTsq        = 0.;
+  double sumNumDigis   = 0.;
+  double sumNumDigisSq = 0.;
+  size_t numEvents     = 0;
+  size_t numDigis      = 0;
+
+  // --- Event loop
+  for (const auto& event : *fEvents) {
+    numEvents++;
+    double numDigisEvt = double(event.fData.fSts.fDigis.size());
+    sumNumDigis += numDigisEvt;
+    sumNumDigisSq += numDigisEvt * numDigisEvt;
+    for (const auto& digi : event.fData.fSts.fDigis) {
+      numDigis++;
+      const double tDigi = digi.GetTime() - event.fTime;
+      if (fHistDigiTimeSts) fHistDigiTimeSts->Fill(tDigi);
+      sumT += tDigi;
+      sumTsq += tDigi * tDigi;
+    }
+  }
+
+  // --- First and second moments of digi times and digi numbers
+  double tMean          = sumT / double(numDigis);
+  double tSqMean        = sumTsq / double(numDigis);
+  double tRms           = sqrt(tSqMean - tMean * tMean);
+  double numDigisMean   = sumNumDigis / double(numEvents);
+  double numDigisSqMean = sumNumDigisSq / double(numEvents);
+  double numDigisRms    = sqrt(numDigisSqMean - numDigisMean * numDigisMean);
+
+  // --- Timeslice log
+  timer.Stop();
+  fExecTime += timer.RealTime();
+  stringstream logOut;
+  logOut << setw(15) << left << GetName() << " [";
+  logOut << fixed << setw(8) << setprecision(1) << right << timer.RealTime() * 1000. << " ms] ";
+  logOut << "TS " << fNumTs << ", events " << numEvents << ", digis " << numDigis << ", digis per event ("
+         << numDigisMean << " +- " << numDigisRms << ")"
+         << ",  digi time (" << tMean << " +- " << tRms << ") ns";
+  LOG(info) << logOut.str();
+
+  // --- Run statistics
+  fNumTs++;
+  fNumEvents += numEvents;
+  fNumDigis += numDigis;
+
+
+  // See: macro/run/run_unpack_online.C
+  // See : recp/detectors/sts/unpack/CbmStsUnpackMonitor
+}
+// ----------------------------------------------------------------------------
+
+
+// -----   End-of-timeslice action   ------------------------------------------
+void CbmTaskDigiEventQa::Finish()
+{
+  std::cout << std::endl;
+  LOG(info) << "=====================================";
+  LOG(info) << GetName() << ": Run summary";
+  LOG(info) << "Timeslices : " << fNumTs;
+  LOG(info) << "Events     : " << fNumEvents;
+  LOG(info) << "Digis      : " << fNumDigis;
+  LOG(info) << "Exec time  : " << fixed << setprecision(2) << 1000. * fExecTime / double(fNumTs) << " ms / TS";
+  if (fHistDigiTimeSts)
+    LOG(info) << "STS digi times : entries " << fHistDigiTimeSts->GetEntries() << ", mean "
+              << fHistDigiTimeSts->GetMean() << ", stddev " << fHistDigiTimeSts->GetStdDev();
+  LOG(info) << "=====================================";
+  if (fHistDigiTimeSts) fHistDigiTimeSts->Write();
+}
+// ----------------------------------------------------------------------------
+
+
+// -----   Initialisation   ---------------------------------------------------
+InitStatus CbmTaskDigiEventQa::Init()
+{
+  // --- Get FairRootManager instance
+  FairRootManager* ioman = FairRootManager::Instance();
+  assert(ioman);
+
+  std::cout << std::endl;
+  LOG(info) << "==================================================";
+  LOG(info) << GetName() << ": Initialising...";
+
+  // --- Input data
+  fEvents = ioman->InitObjectAs<const std::vector<CbmDigiEvent>*>("DigiEvent");
+  if (!fEvents) {
+    LOG(error) << GetName() << ": No input branch DigiEvent!";
+    return kFATAL;
+  }
+  LOG(info) << "--- Found branch DigiEvent";
+
+  // --- Register histograms
+  THttpServer* server = FairRunOnline::Instance()->GetHttpServer();
+  if (server) {
+    LOG(info) << "--- Http server present; registering histograms";
+    if (fHistDigiTimeSts) server->Register("DigiEvent", fHistDigiTimeSts);
+  }
+  else
+    LOG(info) << "--- No Http server present";
+
+  LOG(info) << "==================================================";
+  std::cout << std::endl;
+
+  return kSUCCESS;
+}
+// ----------------------------------------------------------------------------
+
+
+ClassImp(CbmTaskDigiEventQa)
diff --git a/reco/tasks/CbmTaskDigiEventQa.h b/reco/tasks/CbmTaskDigiEventQa.h
new file mode 100644
index 0000000000000000000000000000000000000000..32452a9133ed177375ee9d1a39584bf67ebc9277
--- /dev/null
+++ b/reco/tasks/CbmTaskDigiEventQa.h
@@ -0,0 +1,82 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese [committer] */
+
+#ifndef CBMTASKDIGIEVENTQA_H
+#define CBMTASKDIGIEVENTQA_H 1
+
+#include "CbmDefs.h"
+#include "CbmDigiEvent.h"
+
+#include <FairTask.h>
+
+#include <vector>
+
+//#include "EventBuilder.h"
+
+class CbmDigiManager;
+class CbmRecoConfig;
+class TH1F;
+
+/** @class CbmTaskDigiEventQa
+ ** @brief QA task class for digi events produced by the event builder
+ ** @author Volker Friese <v.friese@gsi.de>
+ ** @since 15.03.2022
+ **
+ ** Currently implemented functionality: histogram the STS digi time within each event.
+ ** To be expanded for other detector systems and probably more QA figures.
+ ** The histograms are published to the THttpServer.
+ **/
+class CbmTaskDigiEventQa : public FairTask {
+
+public:
+  /** @brief Constructor **/
+  CbmTaskDigiEventQa();
+
+
+  /** @brief Copy constructor (disabled) **/
+  CbmTaskDigiEventQa(const CbmTaskDigiEventQa&) = delete;
+
+
+  /** @brief Destructor **/
+  virtual ~CbmTaskDigiEventQa();
+
+
+  /** @brief Configuration
+   ** @param config  Reconstruction configuration
+   **
+   ** Histograms are created with limits adjusted to the windows use by the event builder.
+   **/
+  void Config(const CbmRecoConfig& config);
+
+
+  /** @brief Task execution **/
+  virtual void Exec(Option_t* opt);
+
+
+  /** @brief Finish timeslice **/
+  virtual void Finish();
+
+
+  /** @brief Assignment operator (disabled) **/
+  CbmTaskDigiEventQa& operator=(const CbmTaskDigiEventQa&) = delete;
+
+
+private:  // methods
+  /** @brief Task initialisation **/
+  virtual InitStatus Init();
+
+
+private:                                               // members
+  const std::vector<CbmDigiEvent>* fEvents = nullptr;  //! Input data (events)
+  size_t fNumTs                            = 0;        ///< Number of processed timeslices
+  size_t fNumEvents                        = 0;        ///< Number of analysed events
+  size_t fNumDigis                         = 0;        ///< Number of analysed digis
+  double fExecTime                         = 0.;       ///< Execution time [s]
+  TH1F* fHistDigiTimeSts                   = nullptr;  //!
+
+
+  ClassDef(CbmTaskDigiEventQa, 1);
+};
+
+#endif /* CBMTASKDIGIEVENTQA_H */