diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index ee9040be2fb508c78b0fece2e4c865d0a098a47f..1811fe30e8ef76e31df9cb666b847bc9d1c2545e 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -89,6 +89,7 @@ set(SRCS
   detectors/much/ReadoutConfig.cxx
   detectors/much/Unpack.cxx
   detectors/tof/HitFinder.cxx
+  detectors/tof/Calibrate.cxx
   detectors/tof/Clusterizer.cxx
   detectors/tof/ReadoutConfig.cxx
   detectors/tof/Unpack.cxx
diff --git a/algo/detectors/tof/Calibrate.cxx b/algo/detectors/tof/Calibrate.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..be30ee715b515676a64f56c9eb788f1dc0ac1766
--- /dev/null
+++ b/algo/detectors/tof/Calibrate.cxx
@@ -0,0 +1,106 @@
+/* Copyright (C) 2023 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer] */
+
+#include "Calibrate.h"
+
+#include <chrono>
+
+#include "log.hpp"
+
+using namespace std;
+using fles::Subsystem;
+
+namespace cbm::algo::tof
+{
+  // -----   Execution   -------------------------------------------------------
+  Calibrate::resultType Calibrate::operator()(gsl::span<CbmTofDigi> digiIn)
+  {
+    xpu::push_timer("TofCalibrate");
+    xpu::t_add_bytes(digiIn.size_bytes());
+
+    // --- Output data
+    resultType result = {};
+
+    auto& calDigiOut = result.first;
+    auto& monitor    = result.second;
+
+    // channel deadtime map
+    std::map<int32_t, double> mChannelDeadTime;
+
+    for (size_t iDigi = 0; iDigi < digiIn.size(); iDigi++) {
+
+      CbmTofDigi pDigi    = digiIn[iDigi];
+      const double SmType = pDigi.GetType();
+      const double Sm     = pDigi.GetSm();
+      const double Rpc    = pDigi.GetRpc();
+      const double Chan   = pDigi.GetChannel();
+      const double Side   = pDigi.GetSide();
+      const int NbRpc     = fTofConfig.NbRpc[SmType];
+
+      auto& rpcs = fTofConfig.rpcs;
+      if (SmType >= rpcs.size() || Sm * NbRpc + Rpc >= rpcs.at(SmType).size()) {
+        monitor.fDigiCalibUnknownRPC++;
+        continue;
+      }
+
+      HitfindSetup::Rpc& rpcPar      = fTofConfig.rpcs.at(SmType).at(Sm * NbRpc + Rpc);
+      HitfindSetup::Channel& chanPar = rpcPar.chanPar[Chan];
+
+      if (rpcPar.swapChannelSides && 5 != SmType && 8 != SmType) {
+        pDigi.SetAddress(Sm, Rpc, Chan, (0 == Side) ? 1 : 0, SmType);
+      }
+
+      // Check dead time
+      const int32_t iAddr = pDigi.GetAddress();
+      auto it             = mChannelDeadTime.find(iAddr);
+      if (it != mChannelDeadTime.end() && pDigi.GetTime() <= it->second) {
+        it->second = pDigi.GetTime() + rpcPar.channelDeadtime;
+        continue;
+      }
+      mChannelDeadTime[iAddr] = pDigi.GetTime() + rpcPar.channelDeadtime;
+
+      // Create calibrated digi
+      CbmTofDigi pCalDigi(pDigi);
+
+      // calibrate Digi Time
+      pCalDigi.SetTime(pCalDigi.GetTime() - chanPar.vCPTOff[Side]);
+
+      // subtract Offset
+      double dTot = pCalDigi.GetTot() - chanPar.vCPTotOff[Side];
+      if (dTot < 0.001) dTot = 0.001;
+      // calibrate Digi ToT
+      pCalDigi.SetTot(dTot * chanPar.vCPTotGain[Side]);
+
+      // walk correction
+      std::vector<double>& walk = chanPar.vCPWalk[Side];
+      const double dTotBinSize  = (rpcPar.TOTMax - rpcPar.TOTMin) / rpcPar.numClWalkBinX;
+      int32_t iWx               = (int32_t)((pCalDigi.GetTot() - rpcPar.TOTMin) / dTotBinSize);
+
+      if (0 > iWx) { iWx = 0; }
+      if (iWx >= rpcPar.numClWalkBinX) { iWx = rpcPar.numClWalkBinX - 1; }
+
+      const double dDTot = (pCalDigi.GetTot() - rpcPar.TOTMin) / dTotBinSize - (double) iWx - 0.5;
+      double dWT         = walk[iWx];
+
+      // linear interpolation to next bin
+      if (dDTot > 0) {
+        if (iWx < rpcPar.numClWalkBinX - 1) { dWT += dDTot * (walk[iWx + 1] - walk[iWx]); }
+      }
+      else {
+        if (0 < iWx) { dWT -= dDTot * (walk[iWx - 1] - walk[iWx]); }
+      }
+      pCalDigi.SetTime(pCalDigi.GetTime() - dWT);  // calibrate Digi Time
+      calDigiOut.push_back(pCalDigi);
+    }
+
+    /// Sort the buffers of hits due to the time offsets applied
+    std::sort(calDigiOut.begin(), calDigiOut.end(),
+              [](const CbmTofDigi& a, const CbmTofDigi& b) -> bool { return a.GetTime() < b.GetTime(); });
+
+    monitor.fTime = xpu::pop_timer();
+
+    return result;
+  }
+
+}  // namespace cbm::algo::tof
diff --git a/algo/detectors/tof/Calibrate.h b/algo/detectors/tof/Calibrate.h
new file mode 100644
index 0000000000000000000000000000000000000000..9d8a60b0dd6b333236937cfabb22bdb084baf037
--- /dev/null
+++ b/algo/detectors/tof/Calibrate.h
@@ -0,0 +1,76 @@
+/* Copyright (C) 2023 Facility for Antiproton and Ion Research in Europe, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer] */
+
+#ifndef TOFCALIBRATE_H
+#define TOFCALIBRATE_H 1
+
+#include "CbmTofDigi.h"
+
+#include "tof/Clusterizer.h"
+#include "tof/HitfindSetup.h"
+
+#include <gsl/span>
+#include <optional>
+#include <sstream>
+#include <vector>
+
+#include <xpu/host.h>
+
+#include "PartitionedVector.h"
+
+namespace cbm::algo::tof
+{
+
+  /** @struct CalibrateMonitorData
+   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @since 16 Oct 2023
+   ** @brief Monitoring data for calibration
+   **/
+  struct CalibrateMonitorData {
+    xpu::timings fTime;
+    size_t fDigiCalibUnknownRPC = 0;
+
+    std::string print() const
+    {
+      std::stringstream ss;
+      ss << "Calibrate stats: num unknown RPC " << fDigiCalibUnknownRPC << ", time " << fTime.wall() << std::endl;
+      return ss.str();
+    }
+  };
+
+  /** @class Calibrate
+ ** @brief Algo class for calibration
+ ** @author Dominik Smith <d.smith@gsi.de>
+ ** @since 16.10.2023
+ **
+ **/
+  class Calibrate {
+
+  public:
+    typedef std::pair<std::vector<CbmTofDigi>, CalibrateMonitorData> resultType;
+
+    /** @brief Algorithm execution
+     ** @param digis to calibrate
+     ** @return pair: digi timeslice, monitoring data
+     **
+     ** @note Modifies input digis for time calibration
+     **/
+    resultType operator()(gsl::span<CbmTofDigi> digiIn);
+
+    /** @brief Default constructor **/
+    Calibrate() {};
+
+    /** @brief Destructor **/
+    ~Calibrate() {};
+
+    /** @brief Parameters for TOF hitfinders **/
+    tof::HitfindSetup fTofConfig {};
+
+  private:  // members
+    /** @brief Applies calibration to input digis **/
+    std::vector<CbmTofDigi> CalibRawDigis(gsl::span<CbmTofDigi> digiVec, CalibrateMonitorData& monitor);
+  };
+}  // namespace cbm::algo::tof
+
+#endif /* TOFCALIBRATE_H */
diff --git a/algo/detectors/tof/Hitfind.cxx b/algo/detectors/tof/Hitfind.cxx
index 2d74178106d92f6036e5b3f5d80dec4e61fc02e1..d789c8367d79416dfc949daec56b968e1075cc96 100644
--- a/algo/detectors/tof/Hitfind.cxx
+++ b/algo/detectors/tof/Hitfind.cxx
@@ -24,10 +24,12 @@ namespace cbm::algo::tof
 
     auto& clusterTs = std::get<0>(result);
     auto& monitor   = std::get<1>(result);
-    auto& calDigi   = std::get<2>(result);
+    //auto& calDigi   = std::get<2>(result);
 
     // Do calibration globally (optional, should not be used together with RPC-wise calibration)
-    calDigi = CalibRawDigis(digiIn, monitor);
+    //calDigi = CalibRawDigis(digiIn, monitor);
+
+    auto& calDigi = digiIn;
 
     // Loop over the digis array and store the Digis in separate vectors for
     // each RPC modules
diff --git a/reco/tasks/CbmTaskTofClusterizer.cxx b/reco/tasks/CbmTaskTofClusterizer.cxx
index c9020271df490cd0cf639d5feb636662aef66bef..d3dba4ee11cdffc999ca2b5e9707de0146e9b05c 100644
--- a/reco/tasks/CbmTaskTofClusterizer.cxx
+++ b/reco/tasks/CbmTaskTofClusterizer.cxx
@@ -313,6 +313,7 @@ bool CbmTaskTofClusterizer::InitAlgos()
   cbm::algo::fs::path paramsPath = "TofHitfinderPar.yaml";
   YAML::Node yaml                = YAML::LoadFile(paramsPath.string());
   fAlgo.fTofConfig               = cbm::algo::config::Read<cbm::algo::tof::HitfindSetup>(yaml);
+  fCalibrate.fTofConfig          = cbm::algo::config::Read<cbm::algo::tof::HitfindSetup>(yaml);
   fAlgo.Init();
   return true;
 }
@@ -347,13 +348,17 @@ bool CbmTaskTofClusterizer::BuildClusters()
     }
   }
 
+  // Calibrated digis (calibration done in algo)
+  // Can be left out if calibrated digis not stored separately!
+  *fTofCalDigiVec = fCalibrate(fTofDigiVec).first;
+
   //call cluster finder
-  auto clusterOut = fAlgo(fTofDigiVec);
+  auto clusterOut = fAlgo(*fTofCalDigiVec);
   auto clusters   = std::get<0>(clusterOut);
 
   // Calibrated digis (calibration done in algo)
   // Can be left out if calibrated digis not stored separately!
-  *fTofCalDigiVec = std::get<2>(clusterOut);
+  //*fTofCalDigiVec = std::get<2>(clusterOut);
 
   //Store hits and match
   for (auto const& cluster : clusters.Data()) {
diff --git a/reco/tasks/CbmTaskTofClusterizer.h b/reco/tasks/CbmTaskTofClusterizer.h
index 04a26e1ca3ed14ffc317948630174cd8e21a4803..b4996cc2c33e7b74a971282c4b65778c968a45ec 100644
--- a/reco/tasks/CbmTaskTofClusterizer.h
+++ b/reco/tasks/CbmTaskTofClusterizer.h
@@ -14,6 +14,7 @@ class CbmTsEventHeader;
 #include "CbmMatch.h"
 #include "CbmTofDigi.h"
 
+#include "tof/Calibrate.h"
 #include "tof/Clusterizer.h"
 #include "tof/Hitfind.h"
 
@@ -97,7 +98,6 @@ private:
 
   std::vector<CbmTofDigi>* fT0DigiVec = nullptr;  //! T0 Digis
 
-
   /**
        ** @brief Copy constructor.
        **/
@@ -130,6 +130,9 @@ private:
   // Hit finder algo
   cbm::algo::tof::Hitfind fAlgo;
 
+  // Calibrator algo
+  cbm::algo::tof::Calibrate fCalibrate;
+
   const CbmTsEventHeader* fTsHeader;
 
   // Input variables