diff --git a/components/common/cpp/include/ftl/handle.hpp b/components/common/cpp/include/ftl/handle.hpp
index 0fe697e8435e5d33548cd6159025f6964c8b5fe4..7a2348d5bf671595bf97f8922873304737f14b4c 100644
--- a/components/common/cpp/include/ftl/handle.hpp
+++ b/components/common/cpp/include/ftl/handle.hpp
@@ -26,8 +26,7 @@ struct Handle {
 	friend struct BaseHandler;
 
 	/**
-	 * Cancel the timer job. If currently executing it will block and wait for
-	 * the job to complete.
+	 * Cancel the callback and invalidate the handle.
 	 */
 	inline void cancel() { if (handler_) handler_->remove(*this); handler_ = nullptr; }
 
@@ -62,8 +61,17 @@ struct Handle {
 	Handle(BaseHandler *h, int id) : handler_(h), id_(id) {}
 };
 
+/**
+ * This class is used to manage callbacks. The template parameters are the
+ * arguments to be passed to the callback when triggered. This class is already
+ * thread-safe.
+ */
 template <typename ...ARGS>
 struct Handler : BaseHandler {
+	/**
+	 * Add a new callback function. It returns a `Handle` object that must
+	 * remain in scope, the destructor of the `Handle` will remove the callback.
+	 */
 	Handle on(const std::function<bool(ARGS...)> &f) {
 		std::unique_lock<std::mutex> lk(mutex_);
 		int id = id_++;
@@ -71,6 +79,12 @@ struct Handler : BaseHandler {
 		return make_handle(this, id);
 	}
 
+	/**
+	 * Safely trigger all callbacks. Note that `Handler` is locked when
+	 * triggering so callbacks cannot make modifications to it or they will
+	 * lock up. To remove a callback, return false from the callback, else
+	 * return true.
+	 */
 	void trigger(ARGS ...args) {
 		std::unique_lock<std::mutex> lk(mutex_);
 		try {
@@ -82,6 +96,10 @@ struct Handler : BaseHandler {
 		}
 	}
 
+	/**
+	 * Remove a callback using its `Handle`. This is equivalent to allowing the
+	 * `Handle` to be destroyed or cancelled.
+	 */
 	void remove(const Handle &h) override {
 		std::unique_lock<std::mutex> lk(mutex_);
 		callbacks_.erase(h.id());