Commit 5533117856b for woocommerce
commit 5533117856b8acb004573f8faf086d8b2b658772
Author: Sulaiman Dauda <abiodauda@gmail.com>
Date: Fri Oct 23 18:28:05 2026 +0100
Stop repeating the webhook ping after every delivery (#69446)
diff --git a/plugins/woocommerce/changelog/fix-webhook-stuck-ping b/plugins/woocommerce/changelog/fix-webhook-stuck-ping
new file mode 100644
index 00000000000..d2b3f403c93
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-webhook-stuck-ping
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Accept any 2xx response to a webhook's test ping, and stop repeating the ping after every successful delivery.
diff --git a/plugins/woocommerce/includes/class-wc-webhook.php b/plugins/woocommerce/includes/class-wc-webhook.php
index 99045664d95..49f34484386 100644
--- a/plugins/woocommerce/includes/class-wc-webhook.php
+++ b/plugins/woocommerce/includes/class-wc-webhook.php
@@ -607,6 +607,8 @@ class WC_Webhook extends WC_Legacy_Webhook {
// Check for a success, which is a 2xx, 301 or 302 Response Code.
if ( intval( $response_code ) >= 200 && intval( $response_code ) < 303 ) {
$this->set_failure_count( 0 );
+ // A successful delivery proves the delivery URL works, so it no longer needs a ping.
+ $this->set_pending_delivery( false );
if ( 0 !== $this->get_id() ) {
$this->save();
@@ -698,7 +700,7 @@ class WC_Webhook extends WC_Legacy_Webhook {
return new WP_Error( 'error', sprintf( __( 'Error: Delivery URL cannot be reached: %s', 'woocommerce' ), $test->get_error_message() ) );
}
- if ( 200 !== $response_code ) {
+ if ( $response_code < 200 || $response_code >= 300 ) {
/* translators: error message */
return new WP_Error( 'error', sprintf( __( 'Error: Delivery URL returned response code: %s', 'woocommerce' ), absint( $response_code ) ) );
}
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-webhook-tests.php b/plugins/woocommerce/tests/php/includes/class-wc-webhook-tests.php
index 18a7ba831fe..9bc340daacc 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-webhook-tests.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-webhook-tests.php
@@ -693,6 +693,86 @@ class WC_Webhook_Test extends WC_Unit_Test_Case {
$this->assertSame( 999, $webhook2->get_user_id() );
}
+ /**
+ * @testdox A successful delivery clears the pending ping, so no ping follows it.
+ */
+ public function test_successful_delivery_clears_pending_ping(): void {
+ $product = WC_Helper_Product::create_simple_product();
+ $webhook = $this->create_active_webhook( 'product.updated' );
+ $webhook->set_user_id( 1 );
+ $webhook->save();
+
+ $requests = array();
+ add_filter(
+ 'pre_http_request',
+ function ( $preempt, $args ) use ( &$requests ) {
+ $is_ping = 0 === strpos( (string) $args['body'], 'webhook_id=' );
+ $requests[] = $is_ping ? 'ping' : 'delivery';
+
+ // Like an endpoint that verifies signatures: the unsigned ping is rejected, the delivery accepted.
+ return $this->mock_http_response( $is_ping ? 401 : 200 );
+ },
+ 10,
+ 2
+ );
+
+ $webhook->deliver( $product->get_id() );
+
+ $this->assertSame( array( 'delivery' ), $requests, 'A successful delivery should not be followed by a ping.' );
+ $this->assertFalse( wc_get_webhook( $webhook->get_id() )->get_pending_delivery(), 'A successful delivery should clear the pending ping.' );
+ }
+
+ /**
+ * @testdox The ping counts any 2xx response as success, like deliveries do.
+ *
+ * @testWith [200, true]
+ * [202, true]
+ * [204, true]
+ * [302, false]
+ * [401, false]
+ * [500, false]
+ *
+ * @param int $response_code HTTP status code the delivery URL answers with.
+ * @param bool $expect_success Whether the ping should succeed.
+ */
+ public function test_ping_succeeds_on_any_2xx_response( int $response_code, bool $expect_success ): void {
+ $webhook = $this->create_active_webhook( 'product.updated' );
+ $webhook->save();
+
+ add_filter(
+ 'pre_http_request',
+ function () use ( $response_code ) {
+ return $this->mock_http_response( $response_code );
+ }
+ );
+
+ $result = $webhook->deliver_ping();
+
+ if ( $expect_success ) {
+ $this->assertTrue( $result, "A ping answered with {$response_code} should succeed." );
+ } else {
+ $this->assertInstanceOf( WP_Error::class, $result, "A ping answered with {$response_code} should fail with an error." );
+ }
+ $this->assertSame( ! $expect_success, wc_get_webhook( $webhook->get_id() )->get_pending_delivery(), 'Only a successful ping should clear the pending ping.' );
+ }
+
+ /**
+ * Build a pre_http_request response with the given status code.
+ *
+ * @param int $response_code HTTP status code.
+ * @return array
+ */
+ private function mock_http_response( int $response_code ): array {
+ return array(
+ 'response' => array(
+ 'code' => $response_code,
+ 'message' => get_status_header_desc( $response_code ),
+ ),
+ 'headers' => array(),
+ 'body' => '',
+ );
+ }
+
/**
* @testDox The woocommerce_webhook_enable_delivery_log filter toggles delivery logging without affecting failure tracking.
*/