diff --git a/core/data/CMakeLists.txt b/core/data/CMakeLists.txt
index 60276a916df095743b896188d821dff5b442c86e..57cfea793442d17eadea422c85e72a681a2ca140 100644
--- a/core/data/CMakeLists.txt
+++ b/core/data/CMakeLists.txt
@@ -134,7 +134,7 @@ SET_SOURCE_FILES_PROPERTIES(tof/etof/star_rhicf.c PROPERTIES COMPILE_FLAGS -Wno-
 
 list(APPEND HEADERS base/CbmDigiData.h global/CbmDigiEvent.h global/CbmDigiTimeslice.h
 bmon/CbmBmonDigiData.h sts/CbmStsDigiData.h much/CbmMuchDigiData.h rich/CbmRichDigiData.h trd/CbmTrdDigiData.h
-tof/CbmTofDigiData.h psd/CbmPsdDigiData.h fsd/CbmFsdDigiData.h)
+tof/CbmTofDigiData.h psd/CbmPsdDigiData.h fsd/CbmFsdDigiData.h trd/CbmTrdFexMessageSpadic.h)
 
 set(LIBRARY_NAME CbmData)
 set(LINKDEF ${LIBRARY_NAME}LinkDef.h)
diff --git a/core/data/trd/CbmTrdFexMessageSpadic.h b/core/data/trd/CbmTrdFexMessageSpadic.h
new file mode 100644
index 0000000000000000000000000000000000000000..326813b814052150b1c10798e159447c1a377fdc
--- /dev/null
+++ b/core/data/trd/CbmTrdFexMessageSpadic.h
@@ -0,0 +1,116 @@
+/* Copyright (C) 2024 Goethe-University Frankfurt, Frankfurt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: David Schledt [committer] */
+
+#ifndef CbmTrdFexMessageSpadic_H
+#define CbmTrdFexMessageSpadic_H
+
+#include "fmt/format.h"
+
+#include <array>
+#include <stdexcept>
+
+
+namespace Spadic
+{
+
+  template<std::uint8_t sys_ver>
+  constexpr std::uint8_t BytesPerWord()
+  {
+    switch (sys_ver) {
+      case 0x01: return 8;
+      case 0x10: return 8;
+      default: return 0;
+    }
+  }
+
+  template<size_t bytes>
+  struct NByteContainer {
+    std::array<std::uint8_t, bytes> b;
+
+    operator std::uint64_t()
+    {
+      std::uint64_t r = 0;
+      for (std::uint8_t i = 0; i < bytes; ++i)
+        r |= (std::uint64_t) b[i] << (8 * i);
+      return r;
+    }
+
+    size_t range(std::uint8_t h, std::uint8_t l)
+    {
+
+      if (l > h) {
+        throw std::invalid_argument(fmt::format("Reverse ranges not supported : low={:#x} > high={:#x}", l, h));
+      }
+      // get high and low byte positions
+      std::uint16_t bh = h >> 3;
+      std::uint16_t bl = l >> 3;
+      // get number of bytes that need to be accessed
+      std::uint16_t rbytes = bh - bl;
+      if ((bh + 1) > bytes) {
+        throw std::invalid_argument(fmt::format("High value: out-of-range, got={:#x}, size={:#x}", h, (bytes * 8 - 1)));
+      }
+      if ((bl + 1) > bytes) {
+        throw std::invalid_argument(fmt::format("Low value: out-of-range, got={:#x}, size={:#x}", l, (bytes * 8 - 1)));
+      }
+      // calculate bit positions for high and low byte
+      std::uint16_t bits_in_h = h - (8 * bh - 1);
+      std::uint16_t bits_in_l = l - (8 * bl);
+      std::uint16_t mask_h    = (1 << bits_in_h) - 1;
+
+      // assemble return value
+      size_t r = b[bl] >> bits_in_l;
+      if (bh != bl) r |= (b[bh] & mask_h) << (rbytes * 8);
+      for (int i = 1; i < rbytes; ++i)
+        r |= b[bl + i] << (i * 8);
+      return r;
+    }
+  };
+
+  // Define template specialisations for each system version
+  template<std::uint8_t sys_ver>
+  struct FexWord {
+  };
+
+
+  // sys_ver 0x10 (c.f.git.cbm.gsi.de/trd/reports/technical-notes/integrated-data-format)
+  template<>
+  struct FexWord<0x10> {
+    std::uint8_t elink;
+    std::uint8_t channel;
+    std::uint64_t timestamp;
+    float prec_time;
+    std::uint16_t maxAdc;
+    std::uint16_t timesample;
+    std::uint8_t iMA;
+    std::uint8_t ht;
+    std::uint8_t mh;
+
+    FexWord(NByteContainer<BytesPerWord<0x10>()> word)
+    {
+      constexpr int hts  = 62;
+      constexpr int els  = hts - 8;
+      constexpr int chs  = els - 4;
+      constexpr int maxs = chs - 9;
+      constexpr int imas = maxs - 3;
+      constexpr int trs  = imas - 9;
+      constexpr int mhs  = trs - 3;
+      constexpr int pts  = mhs - 5;
+
+      ht = (word >> 62) & 0x3;  // TODO ext trigger
+      if (ht != 0) {
+        elink      = (word >> els) & 0xff;
+        channel    = (word >> chs) & 0xf;
+        maxAdc     = (word >> maxs) & 0x1ff;
+        iMA        = (word >> imas) & 0x3;
+        timesample = (word >> trs) & 0x1ff;
+        mh         = (word >> mhs) & 0x3;
+        prec_time  = (float) ((word >> pts) & 0x1f) / float(1 << 4);
+        timestamp  = word & 0x1fffff;
+      }
+    }
+  };
+
+}  // namespace Spadic
+
+#endif  // CbmTrdFexMessageSpadic_H
diff --git a/reco/detectors/trd/unpack/CbmTrdUnpackAlgoR.cxx b/reco/detectors/trd/unpack/CbmTrdUnpackAlgoR.cxx
index 888bd043d694b252660f9ab4e26df62faadbee8c..f12519401d3324001eb88d91491e35cb25fd691e 100644
--- a/reco/detectors/trd/unpack/CbmTrdUnpackAlgoR.cxx
+++ b/reco/detectors/trd/unpack/CbmTrdUnpackAlgoR.cxx
@@ -13,12 +13,12 @@
 #include <Logger.h>
 
 #include <RtypesCore.h>
-#include <typeinfo>
 
 #include <algorithm>
 #include <bitset>
 #include <cstdint>
 #include <memory>
+#include <typeinfo>
 
 CbmTrdUnpackAlgoR::CbmTrdUnpackAlgoR(/* args */) : CbmTrdUnpackAlgoBaseR("CbmTrdUnpackAlgoR") {}
 
@@ -35,7 +35,9 @@ CbmTrdUnpackAlgoR::GetParContainerRequest(std::string geoTag, std::uint32_t runI
 
   // Digest the runId information in case of runId = 0 we use the default fall back
   std::string runpath = "";
-  if (runId != 0) { runpath = ".run" + std::to_string(runId); }
+  if (runId != 0) {
+    runpath = ".run" + std::to_string(runId);
+  }
 
   // Get .asic parameter container
   temppath = basepath + runpath + ".asic" + ".par";
@@ -75,7 +77,9 @@ void CbmTrdUnpackAlgoR::digestInfoMsg(const std::uint32_t frame, std::uint16_t c
                                       std::uint16_t elinkId)
 {
   /// Save info message if needed.
-  if (fOptOutBVec) { fOptOutBVec->emplace_back(std::make_pair(fLastFulltime, frame)); }
+  if (fOptOutBVec) {
+    fOptOutBVec->emplace_back(std::make_pair(fLastFulltime, frame));
+  }
   fNrCreatedInfoMsgs++;
   Spadic::MsInfoType infotype = getInfoType(frame, criId, crobId, elinkId);
   // "Spadic_Info_Types";
@@ -95,19 +99,27 @@ void CbmTrdUnpackAlgoR::digestMsFlags(const std::uint16_t flags, std::uint16_t c
 
   if (flags & static_cast<std::uint16_t>(fles::MicrosliceFlags::CrcValid)) {
     fNrCrcValidFlags++;
-    if (fMonitor) { fMonitor->FillHisto(fles::MicrosliceFlags::CrcValid, moduleid); }
+    if (fMonitor) {
+      fMonitor->FillHisto(fles::MicrosliceFlags::CrcValid, moduleid);
+    }
   }
   if (flags & static_cast<std::uint16_t>(fles::MicrosliceFlags::OverflowFlim)) {
     fNrOverflowFlimFlags++;
-    if (fMonitor) { fMonitor->FillHisto(fles::MicrosliceFlags::OverflowFlim, moduleid); }
+    if (fMonitor) {
+      fMonitor->FillHisto(fles::MicrosliceFlags::OverflowFlim, moduleid);
+    }
   }
   if (flags & static_cast<std::uint16_t>(fles::MicrosliceFlags::OverflowUser)) {
     fNrOverflowUserFlags++;
-    if (fMonitor) { fMonitor->FillHisto(fles::MicrosliceFlags::OverflowUser, moduleid); }
+    if (fMonitor) {
+      fMonitor->FillHisto(fles::MicrosliceFlags::OverflowUser, moduleid);
+    }
   }
   if (flags & static_cast<std::uint16_t>(fles::MicrosliceFlags::DataError)) {
     fNrDataErrorFlags++;
-    if (fMonitor) { fMonitor->FillHisto(fles::MicrosliceFlags::DataError, moduleid); }
+    if (fMonitor) {
+      fMonitor->FillHisto(fles::MicrosliceFlags::DataError, moduleid);
+    }
   }
 }
 
@@ -304,6 +316,38 @@ void CbmTrdUnpackAlgoR::makeDigi(CbmTrdRawMessageSpadic raw)
   digestOutput(std::move(digi), raw);
 }
 
+// ---- makeDigi ----
+void CbmTrdUnpackAlgoR::makeDigi(Spadic::FexWord<0x10> fw, std::uint32_t criid)
+{
+  auto rawTriggerType = static_cast<Spadic::eTriggerType>(fw.ht);
+  auto triggerType    = CbmTrdRawToDigiBaseR::GetDigiTriggerType(rawTriggerType);
+
+  // Get the address of the originating spadic
+  auto asicAddress = getAsicAddress(criid, 0, fw.elink);
+  if (asicAddress) {
+    // Get the unique module id from the asic address
+    Int_t uniqueModuleId = asicAddress / 1000;
+
+    // Get the channel id on the module
+    auto padChNr = getChannelId(asicAddress, fw.elink, fw.channel);
+
+    // Store the full time information to last full-time member for error message handling
+    // Get the time information and apply the necessary correction
+    ULong64_t time = (fw.timestamp - fw.prec_time) * fSpadic->GetClockCycle() + fMsStartTimeRelCC;
+
+    auto energy = fSpadic->MaxAdcToEnergyCal(fw.maxAdc);
+
+    time -= fSystemTimeOffset;
+
+    auto digi = std::unique_ptr<CbmTrdDigi>(new CbmTrdDigi(padChNr, uniqueModuleId, energy, time, triggerType, 0));
+
+    // If the message was flagged as multi hit, forward this info to the digi
+    if (fw.mh != 0) digi->SetTriggerType(CbmTrdDigi::eTriggerType::kMulti);
+
+    fOutputVec.emplace_back(*std::move(digi));
+  }
+}
+
 // ---- makeRaw ----
 CbmTrdRawMessageSpadic CbmTrdUnpackAlgoR::makeRaw(const std::uint32_t frame, std::uint16_t criId, std::uint8_t crobId,
                                                   std::uint16_t elinkId, std::uint8_t istream)
@@ -339,32 +383,46 @@ bool CbmTrdUnpackAlgoR::unpack(const fles::Timeslice* ts, std::uint16_t icomp, U
   // The UTC is already to large for storing it CbmTrdRawMessageSpadic due to a cast, caused by the multiplication with a double used in the raw message
   fMsStartTimeRelCC = (msdesc.idx - fTsStartTime) / fSpadic->GetClockCycle();
 
-  // We only want to count on TS_MSB per Stream per TS_MSB package (each eLink sends its own TS_MSB frame) so we store the current TS_MSB and compare it to the incoming.
-  std::int8_t currTsMsb = 0;
+  // Get the hardware ids from which the current µSlice is coming
+  std::uint8_t crobId = 0;
+  auto criId          = msdesc.eq_id;
 
-  // Reset the TS_MSB counter for the new µSlice we unpack
-  fNrTsMsbVec.clear();
-  fNrTsMsbVec.resize(fStreamsPerWord);
+  // Digest the flags from the µSlice
+  digestMsFlags(msdesc.flags, criId, crobId);
+
+  const auto mspointer = ts->content(icomp, imslice);
+
+  const auto mscontent = reinterpret_cast<const size_t*>(mspointer);
+
+  if (msdesc.sys_ver == 0x10) {
+    return unpackFex<0x10>(msdesc, mscontent);
+  }
+  else {
+    return unpackRaw(msdesc, mscontent);
+  }
+  return unpackOk;
+}
 
+bool CbmTrdUnpackAlgoR::unpackRaw(const fles::MicrosliceDescriptor msdesc, const size_t* mscontent)
+{
 
   // Get the µslice size in bytes to calculate the number of completed words
   auto mssize = msdesc.size;
 
+  // We have 32 bit spadic frames in this readout version
+  std::uint32_t nwords = mssize / fBytesPerWord;
 
   // Get the hardware ids from which the current µSlice is coming
   std::uint8_t crobId = 0;
   auto criId          = msdesc.eq_id;
 
-  // Digest the flags from the µSlice
-  digestMsFlags(msdesc.flags, criId, crobId);
-
-  // Get the number of complete words in the input MS buffer.
-  std::uint32_t nwords = mssize / fBytesPerWord;
-
-  const auto mspointer = ts->content(icomp, imslice);
+  bool unpackOk = true;
+  // We only want to count on TS_MSB per Stream per TS_MSB package (each eLink sends its own TS_MSB frame) so we store the current TS_MSB and compare it to the incoming.
+  std::int8_t currTsMsb = 0;
 
-  // We have 32 bit spadic frames in this readout version
-  const auto mscontent = reinterpret_cast<const size_t*>(mspointer);
+  // Reset the TS_MSB counter for the new µSlice we unpack
+  fNrTsMsbVec.clear();
+  fNrTsMsbVec.resize(fStreamsPerWord);
 
   // Loop over all 64bit-Spadic-Words in the current µslice
   for (std::uint32_t istream = 0; istream < fStreamsPerWord; istream++) {
@@ -540,4 +598,35 @@ bool CbmTrdUnpackAlgoR::unpack(const fles::Timeslice* ts, std::uint16_t icomp, U
   return unpackOk;
 }
 
+template<std::uint8_t sys_ver>
+bool CbmTrdUnpackAlgoR::unpackFex(const fles::MicrosliceDescriptor msdesc, const size_t* mscontent)
+{
+  constexpr std::uint8_t bytes = Spadic::BytesPerWord<sys_ver>();
+  if constexpr (bytes == 0) {
+    // TODO log something
+    return false;
+  }
+  bool unpackOk = true;
+
+  // Get the µslice size in bytes to calculate the number of completed words
+  auto mssize          = msdesc.size;
+  std::uint32_t nwords = mssize / bytes;
+  // Get the hardware ids from which the current µSlice is coming
+  std::uint8_t crobId = 0;
+  auto criId          = msdesc.eq_id;
+
+  const Spadic::NByteContainer<bytes>* bp = reinterpret_cast<const Spadic::NByteContainer<bytes>*>(mscontent);
+
+  for (std::uint32_t iword = 0; iword < nwords; ++iword) {
+    Spadic::NByteContainer<bytes> bCont = bp[iword];
+    Spadic::FexWord<sys_ver> fw(bCont);
+    if (fw.ht != 0) {
+      makeDigi(fw, criId);
+    }
+  }
+  return unpackOk;
+}
+
+template bool CbmTrdUnpackAlgoR::unpackFex<0x10>(const fles::MicrosliceDescriptor msdesc, const size_t* mscontent);
+
 ClassImp(CbmTrdUnpackAlgoR)
diff --git a/reco/detectors/trd/unpack/CbmTrdUnpackAlgoR.h b/reco/detectors/trd/unpack/CbmTrdUnpackAlgoR.h
index 845bf5155e054905fff7d26cadfb20cb7061b0e9..1d16c3eefd9505a1c00b3642f48d9e94df9b491c 100644
--- a/reco/detectors/trd/unpack/CbmTrdUnpackAlgoR.h
+++ b/reco/detectors/trd/unpack/CbmTrdUnpackAlgoR.h
@@ -19,6 +19,7 @@
 #ifndef CbmTrdUnpackAlgoR_H
 #define CbmTrdUnpackAlgoR_H
 
+#include "CbmTrdFexMessageSpadic.h"
 #include "CbmTrdRawMessageSpadic.h"
 #include "CbmTrdUnpackAlgoBaseR.h"
 
@@ -29,17 +30,17 @@
 #include <Rtypes.h>  // for types
 #include <RtypesCore.h>
 
+#include <cmath>
 #include <cstdint>
 #include <vector>
 
-#include <cmath>
 
 class CbmTrdUnpackAlgoR : public CbmTrdUnpackAlgoBaseR {
 
-private:
+ private:
   /* data */
 
-public:
+ public:
   /** @brief Create the Cbm Trd Unpack AlgoBase object */
   CbmTrdUnpackAlgoR(/* args */);
 
@@ -65,7 +66,7 @@ public:
   GetParContainerRequest(std::string geoTag, std::uint32_t runId);
 
 
-protected:
+ protected:
   /**
    * @brief Digest the aditional flags stored in the 4 "cccc" bits of the EPO messages.
    * @param frame 
@@ -155,6 +156,15 @@ protected:
   */
   void makeDigi(CbmTrdRawMessageSpadic raw);
 
+  /**
+   * @brief Create an actual digi from the fex message
+   * 
+   * @param fw FexWord for a given sys_ver
+   * @param criid the eq_is of a FLIM channel
+  */
+
+  void makeDigi(Spadic::FexWord<0x10> fw, std::uint32_t criid);
+
   /** @brief Up to now we do not need this function for this algorithm */
   bool setDerivedTsParameters(size_t /*itimeslice*/) { return true; }
 
@@ -171,6 +181,11 @@ protected:
   */
   bool unpack(const fles::Timeslice* ts, std::uint16_t icomp, UInt_t imslice);
 
+  bool unpackRaw(const fles::MicrosliceDescriptor msdesc, const size_t* mscontent);
+
+  template<std::uint8_t sys_ver>
+  bool unpackFex(const fles::MicrosliceDescriptor msdesc, const size_t* mscontent);
+
   // Parameter storage members
 
   /** @brief Counter for the ts_msb used to reconstruct the time */
@@ -196,7 +211,7 @@ protected:
   static const std::uint8_t fStreamsPerWord = 2;
 
 
-private:
+ private:
   ClassDef(CbmTrdUnpackAlgoR, 2)
 };