diff --git a/src/includes/core/actions.php b/src/includes/core/actions.php
index 734d00d..36cc78d 100644
--- a/src/includes/core/actions.php
+++ b/src/includes/core/actions.php
@@ -309,6 +309,18 @@ add_action( 'bbp_spam_reply',    'bbp_decrease_user_reply_count' );
 add_action( 'bbp_insert_topic', 'bbp_insert_topic_update_counts', 10, 2 );
 add_action( 'bbp_insert_reply', 'bbp_insert_reply_update_counts', 10, 3 );
 
+// Update topic voice counts.
+add_action( 'bbp_new_reply',        'bbp_maybe_increase_topic_voice_count' );
+add_action( 'bbp_trashed_reply',    'bbp_maybe_decrease_topic_voice_count' );
+add_action( 'bbp_untrashed_reply',  'bbp_maybe_increase_topic_voice_count' );
+add_action( 'bbp_spammed_reply',    'bbp_maybe_decrease_topic_voice_count' );
+add_action( 'bbp_unspammed_reply',  'bbp_maybe_increase_topic_voice_count' );
+add_action( 'bbp_approved_reply',   'bbp_maybe_increase_topic_voice_count' );
+add_action( 'bbp_unapproved_reply', 'bbp_maybe_decrease_topic_voice_count' );
+
+// Insert reply voice counts.
+add_action( 'bbp_insert_reply', 'bbp_maybe_increase_topic_voice_count' );
+
 // Topic status transition helpers for replies
 add_action( 'bbp_trash_topic',   'bbp_trash_topic_replies'   );
 add_action( 'bbp_untrash_topic', 'bbp_untrash_topic_replies' );
diff --git a/src/includes/replies/functions.php b/src/includes/replies/functions.php
index 2cd7875..8a1d7c7 100644
--- a/src/includes/replies/functions.php
+++ b/src/includes/replies/functions.php
@@ -1018,13 +1018,11 @@ function bbp_update_reply_walker( $reply_id, $last_active_time = '', $forum_id =
 				// See https://bbpress.trac.wordpress.org/ticket/2838
 				bbp_update_topic_last_active_time( $ancestor, $topic_last_active_time );
 
-				// Always update voice counts
-				bbp_update_topic_voice_count( $ancestor );
-
 				// Only update reply count if we're deleting a reply, or in the dashboard.
 				if ( in_array( current_filter(), array( 'bbp_deleted_reply', 'save_post' ), true ) ) {
 					bbp_update_topic_reply_count(        $ancestor );
 					bbp_update_topic_reply_count_hidden( $ancestor );
+					bbp_update_topic_voice_count(        $ancestor );
 				}
 
 			// Forum meta relating to most recent topic
diff --git a/src/includes/topics/functions.php b/src/includes/topics/functions.php
index 60889c5..c366ab0 100644
--- a/src/includes/topics/functions.php
+++ b/src/includes/topics/functions.php
@@ -2659,6 +2659,83 @@ function bbp_insert_topic_update_counts( $topic_id = 0, $forum_id = 0 ) {
 	}
 }
 
+/**
+ * Bump the voice count of a topic.
+ *
+ * @since x.x.x bbPress (rXXXX)
+ *
+ * @param int $topic_id   Optional. Topic id.
+ * @param int $difference Optional. Default 1.
+ *
+ * @return int Topic voice count.
+ */
+function bbp_bump_topic_voice_count( $topic_id = 0, $difference = 1 ) {
+
+	// Bail if no bump.
+	if ( empty( $difference ) ) {
+		return false;
+	}
+
+	// Get counts.
+	$topic_id    = bbp_get_topic_id( $topic_id );
+	$voice_count = bbp_get_topic_voice_count( $topic_id, true );
+	$difference  = (int) $difference;
+	$new_count   = (int) ( $voice_count + $difference );
+
+	// Update this topic id's voice count.
+	update_post_meta( $topic_id, '_bbp_voice_count', $new_count );
+
+	return (int) apply_filters( 'bbp_bump_topic_voice_count', $new_count, $topic_id, $difference );
+}
+
+/**
+ * Increase the voice count of a topic by one.
+ *
+ * @since x.x.x bbPress (rXXXX)
+ *
+ * @param int $topic_id The topic id.
+ *
+ * @return void
+ */
+function bbp_increase_topic_voice_count( $topic_id = 0 ) {
+
+	// Bail early if no id is passed.
+	if ( empty( $topic_id ) ) {
+		return;
+	}
+
+	// If it's a reply, get the topic id.
+	if ( bbp_is_reply( $topic_id ) ) {
+		$topic_id = bbp_get_reply_topic_id( $topic_id );
+	}
+
+	bbp_bump_topic_voice_count( $topic_id, 1 );
+}
+
+/**
+ * Decrease the voice count of a topic by one.
+ *
+ * @since x.x.x bbPress (rXXXX)
+ *
+ * @param int $topic_id The topic id.
+ *
+ * @return void
+ */
+function bbp_decrease_topic_voice_count( $topic_id = 0 ) {
+
+	// Bail early if no id is passed.
+	if ( empty( $topic_id ) ) {
+		return;
+	}
+
+	// If it's a reply, get the topic id.
+	if ( bbp_is_reply( $topic_id ) ) {
+		$topic_id = bbp_get_reply_topic_id( $topic_id );
+	}
+
+	bbp_bump_topic_voice_count( $topic_id, -1 );
+}
+
 /** Topic Updaters ************************************************************/
 
 /**
@@ -2993,6 +3070,109 @@ function bbp_update_topic_voice_count( $topic_id = 0 ) {
 }
 
 /**
+ * Maybe update the topic voice count.
+ *
+ * Runs additional checks over `bbp_update_topic_voice_count()` to only
+ * add/remove meta entries when absolutely necessary.
+ *
+ * @since x.x.x bbPress (rXXXX)
+ *
+ * @param int    $topic_id The topic id (or reply id).
+ * @param string $action   The action. Expects `increase` or `decrease`.
+ *
+ * @return void
+ */
+function bbp_maybe_update_topic_voice_count( $topic_id = 0, $action = '' ) {
+
+	// Make sure we have a valid action.
+	if ( ! in_array( $action, array( 'increase', 'decrease' ) ) ) {
+		return;
+	}
+
+	// If it's a reply, then get the parent (topic id).
+	if ( bbp_is_reply( $topic_id ) ) {
+		$author_id = bbp_get_reply_author_id( $topic_id );
+		$published = bbp_is_reply_published( $topic_id );
+		$topic_id  = bbp_get_reply_topic_id( $topic_id );
+	} elseif ( bbp_is_topic( $topic_id ) ) {
+		$author_id = bbp_get_topic_author_id( $topic_id );
+		$published = true;
+		$topic_id  = bbp_get_topic_id( $topic_id );
+	} else {
+		return;
+	}
+
+	// Get the current voice count.
+	$voice_count = bbp_get_topic_voice_count( $topic_id, true );
+
+	// Attempt to add an engagement.
+	if ( 'increase' === $action && $published ) {
+		bbp_add_user_engagement( $author_id, $topic_id );
+
+	// If we're decreasing, make sure the reply author isn't the topic author.
+	} elseif ( $author_id !== bbp_get_topic_author_id( $topic_id ) ) {
+
+		// Check for other replies by the user.
+		$replies = new WP_Query( array(
+			'author'        => $author_id,
+			'fields'        => 'ids',
+			'nopaging'      => true,
+			'no_found_rows' => true,
+			'post_parent'   => $topic_id,
+			'post_status'   => bbp_get_public_status_id(),
+			'post_type'     => bbp_get_reply_post_type(),
+		) );
+
+		// Remove the user engagement if you no more replies.
+		if ( 0 === ( count( $replies->posts ) ) ) {
+			bbp_remove_user_engagement( $author_id, $topic_id );
+		}
+	}
+
+	// Get the engagements count, and calculate the difference.
+	$engagements_count = count( bbp_get_topic_engagements( $topic_id ) );
+	$difference        = (int) ( $engagements_count - $voice_count );
+
+	bbp_bump_topic_voice_count( $topic_id, $difference );
+}
+
+/**
+ * Maybe increase the topic voice count.
+ *
+ * @since x.x.x bbPress (rXXXX)
+ *
+ * @param int $topic_id The topic id (or reply id).
+ *
+ * @return void
+ */
+function bbp_maybe_increase_topic_voice_count( $topic_id = 0 ) {
+
+	if ( empty( $topic_id ) ) {
+		return;
+	}
+
+	bbp_maybe_update_topic_voice_count( $topic_id, 'increase' );
+}
+
+/**
+ * Maybe decrease the topic voice count.
+ *
+ * @since x.x.x bbPress (rXXXX)
+ *
+ * @param int $topic_id The topic id (or reply id).
+ *
+ * @return void
+ */
+function bbp_maybe_decrease_topic_voice_count( $topic_id = 0 ) {
+
+	if ( empty( $topic_id ) ) {
+		return;
+	}
+
+	bbp_maybe_update_topic_voice_count( $topic_id, 'decrease' );
+}
+
+/**
  * Adjust the total anonymous reply count of a topic
  *
  * @since 2.0.0 bbPress (r2567)
