diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index b7d83336ca0b9830ac92c5ebc693e474bbc23521..4a93e43c2060cba0a9f7b206dc2bb6bd287b83f5 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -6,6 +6,7 @@ add_subdirectory (test)
 set(SRCS
   evbuild/EventBuilder.cxx
   trigger/TimeClusterTrigger.cxx
+  detectors/sts/UnpackSts.cxx
  )
 
 add_library(Algo SHARED ${SRCS})
diff --git a/algo/detectors/sts/UnpackSts.cxx b/algo/detectors/sts/UnpackSts.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..00c66eaddde4bb059c51a5588124a768b547383e
--- /dev/null
+++ b/algo/detectors/sts/UnpackSts.cxx
@@ -0,0 +1,132 @@
+/* Copyright (C) 2021 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau, Volker Friese [committer] */
+
+#include "UnpackSts.h"
+
+#include <cassert>
+#include <vector>
+
+#include <cmath>
+
+#include "StsXyterMessage.h"
+
+using std::vector;
+
+namespace cbm::algo
+{
+
+  // ----   Algorithm execution   ---------------------------------------------
+  vector<CbmStsDigi> 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;
+
+    // --- Current TS_MSB epoch cycle
+    auto msTime   = msDescr.idx;  // Unix time of MS in ns
+    fCurrentCycle = std::ldiv(msTime, fCycleLength).quot;
+
+    // --- 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);
+
+    // --- Number of messages in microslice
+    auto msSize = msDescr.size;
+    assert(msSize % sizeof(stsxyter::Message) == 0);
+    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) {
+
+        case stsxyter::MessType::Hit: {
+          ProcessHitMessage(message, digiVec);
+          break;
+        }
+        case stsxyter::MessType::TsMsb: {
+          ProcessTsmsbMessage(message);
+          break;
+        }
+        default: {
+          break;
+        }
+
+      }  //? Message type
+
+    }  //# Messages
+
+    return digiVec;
+  }
+  // --------------------------------------------------------------------------
+
+
+  // -----   Process hit message   --------------------------------------------
+  void UnpackSts::ProcessHitMessage(const stsxyter::Message& message, vector<CbmStsDigi>& digiVec) const
+  {
+
+    uint16_t elink                    = message.GetLinkIndexHitBinning();
+    const UnpackStsElinkPar& elinkPar = fParams->GetElinkPar(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;
+    if (asicNr < fParams->fNumAsicsPerModule / 2) {  // front side (n side)
+      channel = message.GetHitChannel() + fParams->fNumChansPerAsic * asicNr;
+    }
+    else {  // back side (p side)
+      channel = numChansPerModule - message.GetHitChannel() + 1;
+    }
+
+    // --- Time stamp
+    // --- Expand to full Unix time in clock cycles
+    uint64_t timeCC = message.GetHitTimeBinning() + fCurrentEpochTime;
+    // --- Convert time into ns
+    assert(timeCC >> 59 == 0);  // avoid overflow in 64 bit
+    uint64_t timeNs = (timeCC * fParams->fClockCycleNom) / fParams->fClockCycleDen;
+    // --- Correct ASIC-wise offsets
+    timeNs -= elinkPar.fTimeOffset;
+    // --- Calculate time relative to timeslice
+    timeNs -= fCurrentTsTime;
+    // --- TODO: Add walk correction (depends on ADC)
+
+    // --- Charge
+    double charge = elinkPar.fAdcOffset + (message.GetHitAdc() - 1) * elinkPar.fAdcGain;
+
+    // --- Create output digi
+    digiVec.emplace_back(address, channel, timeNs, charge);
+  }
+  // --------------------------------------------------------------------------
+
+
+  // -----   Process an epoch (TS_MSB) message   ------------------------------
+  void UnpackSts::ProcessTsmsbMessage(const stsxyter::Message& message)
+  {
+
+    auto epoch = message.GetTsMsbValBinning();
+
+    // --- Cycle wrap
+    if (epoch < fCurrentEpoch) fCurrentCycle++;
+
+    // --- Update current epoch
+    fCurrentEpoch     = epoch;
+    fCurrentEpochTime = (fCurrentCycle * fParams->fEpochsPerCycle + epoch) * fParams->fEpochLength;
+  }
+  // --------------------------------------------------------------------------
+
+
+} /* namespace cbm::algo */
diff --git a/algo/detectors/sts/UnpackSts.h b/algo/detectors/sts/UnpackSts.h
new file mode 100644
index 0000000000000000000000000000000000000000..90ccc1392c23e2bc6b85d66756ffb2fdba30511e
--- /dev/null
+++ b/algo/detectors/sts/UnpackSts.h
@@ -0,0 +1,128 @@
+/* Copyright (C) 2021 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pierre-Alain Loizeau, Volker Friese [committer] */
+
+#ifndef CBM_ALGO_UNPACKSTS_H
+#define CBM_ALGO_UNPACKSTS_H 1
+
+
+#include "CbmStsDigi.h"
+
+#include "MicrosliceDescriptor.hpp"
+#include "Timeslice.hpp"
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "StsXyterMessage.h"
+
+namespace cbm::algo
+{
+
+
+  /** @struct UnpackStsAsicPar
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 25 November 2021
+   ** @brief Unpacking parameters for one eLink / ASIC
+   **/
+  struct UnpackStsElinkPar {
+    uint32_t fAddress    = 0;   ///< CbmStsAddress for the connected module
+    uint32_t fAsicNr     = 0;   ///< Number of connected ASIC within the module
+    uint64_t fTimeOffset = 0.;  ///< Time calibration parameter
+    double fAdcOffset    = 0.;  ///< Charge calibration parameter
+    double fAdcGain      = 0.;  ///< Charge calibration parameter
+  };
+
+
+  /** @struct UnpackStsPar
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 25 November 2021
+   ** @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
+    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];
+    }
+  };
+
+
+  /** @class UnpackSts
+   ** @author Pierre-Alain Loizeau <p.-a.loizeau@gsi.de>
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 25 November 2021
+   ** @brief Unpack algorithm for STS
+   **/
+  class UnpackSts {
+
+  public:
+    /** @brief Default constructor **/
+    UnpackSts() {};
+
+
+    /** @brief Destructor **/
+    ~UnpackSts() {};
+
+
+    /** @brief Algorithm execution
+     ** @param  msContent  Microslice payload
+     ** @param  msDescr    Microslice descriptor
+     ** @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);
+
+    /** @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;
+    }
+
+
+  private:
+    /** @brief Process an epoch message (TS_MSB)
+     ** @param message SMX message (32-bit word)
+     ** @param digiVec Vector to append the created digi to
+     **/
+    void ProcessTsmsbMessage(const stsxyter::Message& message);
+
+    /** @brief Process a hit message
+     ** @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;
+
+  private:
+    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
+
+    std::unique_ptr<UnpackStsPar> fParams = nullptr;  ///< Parameter container
+  };
+
+
+} /* namespace cbm::algo */
+
+#endif /* CBM_ALGO_UNPACKSTS_H */