diff --git a/algo/detectors/sts/UnpackSts.cxx b/algo/detectors/sts/UnpackSts.cxx
index 00c66eaddde4bb059c51a5588124a768b547383e..ec8389ce1775c78f37cd47be61525468831c0c2c 100644
--- a/algo/detectors/sts/UnpackSts.cxx
+++ b/algo/detectors/sts/UnpackSts.cxx
@@ -5,62 +5,70 @@
 #include "UnpackSts.h"
 
 #include <cassert>
+#include <utility>
 #include <vector>
 
 #include <cmath>
 
 #include "StsXyterMessage.h"
 
+using std::unique_ptr;
 using std::vector;
 
 namespace cbm::algo
 {
 
   // ----   Algorithm execution   ---------------------------------------------
-  vector<CbmStsDigi> UnpackSts::operator()(const uint8_t* msContent, const fles::MicrosliceDescriptor& msDescr,
-                                           const uint64_t tTimeslice)
+  UnpackSts::resultType UnpackSts::operator()(const uint8_t* msContent, const fles::MicrosliceDescriptor& msDescr,
+                                              const uint64_t tTimeslice)
   {
 
     // --- Assert that parameters are set
     assert(fParams);
-    fCurrentTsTime = tTimeslice;
 
-    // --- Output vector
-    vector<CbmStsDigi> digiVec;
+    // --- Output data
+    vector<CbmStsDigi> digiVec = {};
+    UnpackStsMonitorData moni  = {};
+
+    // --- Current Timeslice time and TS_MSB epoch cycle
+    fCurrentTsTime = tTimeslice;
+    auto msTime    = msDescr.idx;  // Unix time of MS in ns
+    fCurrentCycle  = std::ldiv(msTime, fkCycleLength).quot;
 
-    // --- Current TS_MSB epoch cycle
-    auto msTime   = msDescr.idx;  // Unix time of MS in ns
-    fCurrentCycle = std::ldiv(msTime, fCycleLength).quot;
+    // --- Interpret MS content as sequence of SMX messages
+    auto message = reinterpret_cast<const stsxyter::Message*>(msContent);
 
     // --- Get first TS_MSB (first message in microslice must be of type ts_msb)
-    const stsxyter::Message firstMessage(msContent[0]);
-    assert(firstMessage.GetMessType() == stsxyter::MessType::TsMsb);
-    ProcessTsmsbMessage(firstMessage);
+    if (message[0].GetMessType() != stsxyter::MessType::TsMsb) {
+      moni.fNumErrInvalidFirstMessage++;
+      return std::make_pair(digiVec, moni);
+    }
+    ProcessTsmsbMessage(message[0]);
 
     // --- Number of messages in microslice
     auto msSize = msDescr.size;
-    assert(msSize % sizeof(stsxyter::Message) == 0);
+    if (msSize % sizeof(stsxyter::Message) != 0) {
+      moni.fNumErrInvalidMsSize++;
+      return std::make_pair(digiVec, moni);
+    }
     const uint32_t numMessages = msSize / sizeof(stsxyter::Message);
 
     // --- Message loop
     for (uint32_t messageNr = 1; messageNr < numMessages; messageNr++) {
 
-      // --- Cast MS content to STSXYTER message
-      const stsxyter::Message message(msContent[messageNr]);
-      const stsxyter::MessType type = message.GetMessType();
-
       // --- Action depending on message type
-      switch (type) {
+      switch (message[messageNr].GetMessType()) {
 
         case stsxyter::MessType::Hit: {
-          ProcessHitMessage(message, digiVec);
+          ProcessHitMessage(message[messageNr], digiVec, moni);
           break;
         }
         case stsxyter::MessType::TsMsb: {
-          ProcessTsmsbMessage(message);
+          ProcessTsmsbMessage(message[messageNr]);
           break;
         }
         default: {
+          moni.fNumNonHitOrTsbMessage++;
           break;
         }
 
@@ -68,23 +76,29 @@ namespace cbm::algo
 
     }  //# Messages
 
-    return digiVec;
+    return std::make_pair(digiVec, moni);
   }
   // --------------------------------------------------------------------------
 
 
   // -----   Process hit message   --------------------------------------------
-  void UnpackSts::ProcessHitMessage(const stsxyter::Message& message, vector<CbmStsDigi>& digiVec) const
+  void UnpackSts::ProcessHitMessage(const stsxyter::Message& message, vector<CbmStsDigi>& digiVec,
+                                    UnpackStsMonitorData& moni) const
   {
 
-    uint16_t elink                    = message.GetLinkIndexHitBinning();
-    const UnpackStsElinkPar& elinkPar = fParams->GetElinkPar(elink);
+    // --- Check eLink and get parameters
+    uint16_t elink = message.GetLinkIndexHitBinning();
+    if (elink >= fParams->fElinkParams.size()) {
+      moni.fNumErrElinkOutOfRange++;
+      return;
+    }
+    const UnpackStsElinkPar& elinkPar = fParams->fElinkParams.at(elink);
     uint32_t asicNr                   = elinkPar.fAsicNr;
-    uint32_t numChansPerModule        = fParams->fNumAsicsPerModule * fParams->fNumChansPerAsic;
 
     // --- Hardware-to-software address
-    uint32_t address = elinkPar.fAddress;
-    uint32_t channel = 0;
+    uint32_t numChansPerModule = fParams->fNumAsicsPerModule * fParams->fNumChansPerAsic;
+    uint32_t address           = elinkPar.fAddress;
+    uint32_t channel           = 0;
     if (asicNr < fParams->fNumAsicsPerModule / 2) {  // front side (n side)
       channel = message.GetHitChannel() + fParams->fNumChansPerAsic * asicNr;
     }
@@ -94,10 +108,13 @@ namespace cbm::algo
 
     // --- Time stamp
     // --- Expand to full Unix time in clock cycles
-    uint64_t timeCC = message.GetHitTimeBinning() + fCurrentEpochTime;
+    uint64_t timeCc = message.GetHitTimeBinning() + fCurrentEpochTime;
+    if (timeCc >> 59 != 0) {  // avoid overflow in 64 bit // TODO: Hard-coded number!
+      moni.fNumErrTimestampOverflow++;
+      return;
+    }
     // --- Convert time into ns
-    assert(timeCC >> 59 == 0);  // avoid overflow in 64 bit
-    uint64_t timeNs = (timeCC * fParams->fClockCycleNom) / fParams->fClockCycleDen;
+    uint64_t timeNs = (timeCc * fkClockCycleNom + fkClockCycleDen / 2) / fkClockCycleDen;
     // --- Correct ASIC-wise offsets
     timeNs -= elinkPar.fTimeOffset;
     // --- Calculate time relative to timeslice
@@ -124,7 +141,7 @@ namespace cbm::algo
 
     // --- Update current epoch
     fCurrentEpoch     = epoch;
-    fCurrentEpochTime = (fCurrentCycle * fParams->fEpochsPerCycle + epoch) * fParams->fEpochLength;
+    fCurrentEpochTime = (fCurrentCycle * fkEpochsPerCycle + epoch) * fkEpochLength;
   }
   // --------------------------------------------------------------------------
 
diff --git a/algo/detectors/sts/UnpackSts.h b/algo/detectors/sts/UnpackSts.h
index 90ccc1392c23e2bc6b85d66756ffb2fdba30511e..855844104db53b5633952b5245323a66d70f6939 100644
--- a/algo/detectors/sts/UnpackSts.h
+++ b/algo/detectors/sts/UnpackSts.h
@@ -23,10 +23,10 @@ namespace cbm::algo
 {
 
 
-  /** @struct UnpackStsAsicPar
+  /** @struct UnpackStsElinkPar
    ** @author Volker Friese <v.friese@gsi.de>
    ** @since 25 November 2021
-   ** @brief Unpacking parameters for one eLink / ASIC
+   ** @brief STS Unpacking parameters for one eLink / ASIC
    **/
   struct UnpackStsElinkPar {
     uint32_t fAddress    = 0;   ///< CbmStsAddress for the connected module
@@ -43,22 +43,23 @@ namespace cbm::algo
    ** @brief Parameters required for the STS unpacking (specific to one component)
    **/
   struct UnpackStsPar {
+    uint32_t fNumChansPerAsic                   = 0;   ///< Number of channels per ASIC
+    uint32_t fNumAsicsPerModule                 = 0;   ///< Number of ASICS per module
+    std::vector<UnpackStsElinkPar> fElinkParams = {};  ///< Parameters for each eLink
+  };
+
 
-    uint32_t fNumChansPerAsic   = 0;              ///< Number of channels per ASIC
-    uint32_t fNumAsicsPerModule = 0;              ///< Number of ASICS per module
-    uint64_t fEpochsPerCycle    = 0;              ///< TS_MSB epochs per epoch cycle
-    uint64_t fEpochLength       = 0;              ///< Length of TS_MSB epoch in clock cycles
-    uint32_t fClockCycleNom     = 0;              ///< Clock cycle nominator [ns]
-    uint32_t fClockCycleDen     = 0.;             ///< Clock cycle denominator
-    std::vector<UnpackStsElinkPar> fElinkParams;  ///< Parameters for each eLink
-
-    size_t GetNumElinks() const { return fElinkParams.size(); }
-
-    const UnpackStsElinkPar& GetElinkPar(size_t eLink) const
-    {
-      assert(eLink < GetNumElinks());
-      return fElinkParams[eLink];
-    }
+  /** @struct UnpackStsMoni
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 2 December 2021
+   ** @brief Monitoring data for STS unpacking
+   **/
+  struct UnpackStsMonitorData {
+    uint32_t fNumNonHitOrTsbMessage     = 0;
+    uint32_t fNumErrElinkOutOfRange     = 0;  ///< Elink not contained in parameters
+    uint32_t fNumErrInvalidFirstMessage = 0;  ///< First message is not TS_MSB
+    uint32_t fNumErrInvalidMsSize       = 0;  ///< Microslice size is not multiple of message size
+    uint32_t fNumErrTimestampOverflow   = 0;  ///< Overflow in 64 bit time stamp
   };
 
 
@@ -71,6 +72,9 @@ namespace cbm::algo
   class UnpackSts {
 
   public:
+    typedef std::pair<std::vector<CbmStsDigi>, UnpackStsMonitorData> resultType;
+
+
     /** @brief Default constructor **/
     UnpackSts() {};
 
@@ -85,39 +89,43 @@ namespace cbm::algo
      ** @param  tTimeslice Unix start time of timeslice [ns]
      ** @return STS digi data
      **/
-    std::vector<CbmStsDigi> operator()(const uint8_t* msContent, const fles::MicrosliceDescriptor& msDescr,
-                                       const uint64_t tTimeslice);
+    resultType operator()(const uint8_t* msContent, const fles::MicrosliceDescriptor& msDescr,
+                          const uint64_t tTimeslice);
 
     /** @brief Set the parameter container
      ** @param params Pointer to parameter container
      **/
-    void SetParams(std::unique_ptr<UnpackStsPar> params)
-    {
-      fParams      = std::move(params);
-      fCycleLength = (fParams->fEpochsPerCycle * fParams->fEpochLength * fParams->fClockCycleNom);
-      fCycleLength /= fParams->fClockCycleDen;
-    }
+    void SetParams(std::unique_ptr<UnpackStsPar> params) { fParams = std::move(params); }
 
 
-  private:
-    /** @brief Process an epoch message (TS_MSB)
+  private:  // methods
+    /** @brief Process a hit message
      ** @param message SMX message (32-bit word)
      ** @param digiVec Vector to append the created digi to
      **/
-    void ProcessTsmsbMessage(const stsxyter::Message& message);
+    void ProcessHitMessage(const stsxyter::Message& message, std::vector<CbmStsDigi>& digiVec,
+                           UnpackStsMonitorData& moni) const;
 
-    /** @brief Process a hit message
+    /** @brief Process an epoch message (TS_MSB)
      ** @param message SMX message (32-bit word)
      ** @param digiVec Vector to append the created digi to
      **/
-    void ProcessHitMessage(const stsxyter::Message& message, std::vector<CbmStsDigi>& digiVec) const;
+    void ProcessTsmsbMessage(const stsxyter::Message& message);
+
 
-  private:
+  private:                           // members
     uint64_t fCurrentTsTime    = 0;  ///< Unix time of timeslice in ns
     uint64_t fCurrentCycle     = 0;  ///< Current epoch cycle
     uint32_t fCurrentEpoch     = 0;  ///< Current epoch number within epoch cycle
     uint64_t fCurrentEpochTime = 0;  ///< Unix time of current epoch in clock cycles
-    uint64_t fCycleLength      = 0;  ///< Epoch cycle length in ns
+
+    static constexpr uint64_t fkEpochsPerCycle = stsxyter::kuTsMsbNbTsBinsBinning;  ///< TS_MSB epochs per epoch cycle
+    static constexpr uint64_t fkEpochLength =
+      stsxyter::kuHitNbTsBinsBinning;                                        ///< Length of TS_MSB epoch in clock cycles
+    static constexpr uint32_t fkClockCycleNom = stsxyter::kulClockCycleNom;  ///< Clock cycle nominator [ns]
+    static constexpr uint32_t fkClockCycleDen = stsxyter::kulClockCycleDen;  ///< Clock cycle denominator
+    static constexpr uint64_t fkCycleLength =
+      (fkEpochsPerCycle * fkEpochLength * fkClockCycleNom) / fkClockCycleDen;  ///< Epoch cycle length in ns
 
     std::unique_ptr<UnpackStsPar> fParams = nullptr;  ///< Parameter container
   };
diff --git a/core/data/raw/StsXyterMessage.h b/core/data/raw/StsXyterMessage.h
index 408401595826ecd4283c7ea82979c7a12b078087..903e0436e68a1c70c7cddf9b0914a3c5c03a6d7a 100644
--- a/core/data/raw/StsXyterMessage.h
+++ b/core/data/raw/StsXyterMessage.h
@@ -155,10 +155,11 @@ namespace stsxyter
   static const uint32_t kuTsMsbNbTsBins = (0 < kusLenTsMsbVal ? 1 << kusLenTsMsbVal : 0);
   static const uint64_t kulTsCycleNbBins =
     static_cast<uint64_t>(kuTsMsbNbTsBins) * static_cast<uint64_t>(kuHitNbTsBins);
-  static const uint16_t kusMaskTsMsbOver = (1 << kusLenHitTsOver) - 1;
-  static const double kdClockCycleNs     = 3.125;  // ns, equivalent to 2*160 MHz clock
-                                                   /// Binning FW adds 1 bit to TS in HIT message
-                                                   /// => Quick and dirty hack is a factor 2!!!
+  static const uint16_t kusMaskTsMsbOver     = (1 << kusLenHitTsOver) - 1;
+  static constexpr uint32_t kulClockCycleNom = 25;  ///< Clock cycle nominator [ns], equivalent to 2*160 MHz clock
+  static constexpr uint32_t kulClockCycleDen = 8;   ///< Clock cycle denominator, equivalent to 2*160 MHz clock
+  static constexpr double kdClockCycleNs = static_cast<double>(kulClockCycleNom) / kulClockCycleDen;  // ns, not rounded
+
   static const uint32_t kuHitNbTsBinsBinning = 1 << 10;
   static const uint32_t kuTsMsbNbTsBinsBinning = 1 << kusLenTsMsbValBinning;
   static const uint64_t kulTsCycleNbBinsBinning =