• Resolved donaldcameron

    (@donaldcameron)


    Hi:

    UPDATE: I had the body of the null request wrong at first. Updated the text below accordingly.

    I’ve searched these forums (and google in general): a bunch of ppl have raised Qs about WC webhooks firing multiple times. I’ve read all those, they are not the same as this situation.

    Firstly: everything I mention here is configured via WP-CLI, not the UI. Not sure it matters, but it’s probably significant.

    Also: all this is only running on dev ATM. There is no live presence.

    The environment is dockerised. Also unlikely to be relevant, but I figured I’d mention it anyhow.

    We have added a webhook thus:

    wp wc webhook create \
    --name="Order Created Hook" \
    --status="active" \
    --topic="order.created" \
    --delivery_url="${IV_WEBHOOK_BASE_URL}/api/webhook/order-created" \
    --user=admin

    This also uses a woocommerce_webhook_http_args filter to add an Authorization header & Bearer token. We do not use the secrets mechanism provided by default for WC webhooks.

    This works: we’re getting the webhook request on the target server whenever an order is placed. Cool.

    However at exactly the same time as the successful webhook with the order payload is made, WC is making a second request to the URL with an empty request body. This also does not include the Authorization header, so the response from our server is a 401.

    This happens on every order.created event being fired.

    The interesting thing is that if I take the auth-check off the end point, and the “null” request then gets a 200 response… the second request stops being made on subsequent order.created events. It’s like WC is “checking” something (incorrectly), but once it gets its 200 it’s happy and stops asking.

    Unlikely to be relevant, but slightly edge-case-y… the webhook URL is not HTTPS, as it’s just running across the docker network via host.docker.internal. The WP container and our app container is on the same VPS. Well: currently it’s just on my own dev PC 😉

    So how come WC is making this second request, and – more importantly – how do I make it stop? 😉

    I’m pretty new to WP/WC (but not new to dev), so unsure what other info might help: I’m happy to provide any other info that might be useful to this situation. I skipped posting the status report thing, because that seemed to be pretty high level, and didn’t contain anything that seemed relevant here: it looked like it’d be clutter.

    Cheers for any insight anyone might have!

Viewing 8 replies - 1 through 8 (of 8 total)
  • Plugin Support Frank Remmy (woo-hc)

    (@frankremmy)

    Hi @donaldcameron,

    Thanks for the detailed breakdown, super helpful! This is differently an interesting webhook behaviour you’re encountering.

    The double webhook firing with a {"payload":null} request is unusual and not standard WooCommerce behavior. Since you mentioned this is a development environment and you’re using WP-CLI configuration, there are a few things to investigate:

    1. Can you verify there’s only one webhook created? Run wp wc webhook list to see all active webhooks and share with us.
    2. Check if there are any duplicate webhooks or conflicting configurations.

    Here are some potential causes for the double firing:

    1. Even though you’re using WP-CLI, other plugins might be interfering with webhook delivery. Try temporarily deactivating all non-essential plugins to test.
    2. Your woocommerce_webhook_http_args filter might be causing unexpected behavior. Try temporarily removing it to see if the double firing stops.
    3. The host.docker.internal setup, while functional, might be causing WooCommerce to make additional verification requests.

    To troubleshoot further:

    • Enable WooCommerce logging and check WooCommerce → Status → Logs for webhook-related entries
    • Look for any custom code or plugins that might be hooking into webhook delivery
    • Consider testing with a standard HTTPS webhook URL temporarily to rule out the Docker networking

    Let us know your findings. Looking forward to your response.

    Thread Starter donaldcameron

    (@donaldcameron)

    More info.

    There’s a pending_delivery column in the wp_wc_webhooks table. This is set to 1 by default, when the wp wc webhook create is run. Fair enough I s’pose: it’s not been tested / pinged yet.

    If the “second request” gets a 200-OK, it’s set to 0, and the second requests stop. It does not seem to pay attention to the result of the actual webhook requests (which always respond with a 200-OK) to determine whether to change this pending status; it’s only paying attn to the second request.

    If I reset it to 1… the second requests start again.

    If I manually set it to 0, then the second request does not fire.

    So it seems it’s something to do with this from the UI docs:

    4/ Save webhook.

    Note: The first time your webhook is saved with the Activated status, it sends a ping to the Delivery URL.

    I bet the “second request” is this ping.

    It looks like there’s a slight glitch in the logic when it comes to WC deciding whether it needs to ping or not. I think it should only ever really be done once – when the webhook is first created – it should not continue to try pinging along with the actual webhook request, irrespective of the value of pending_delivery value. It should use the actual webhook request’s result! At the end of the day, that’s what matters after all. Potential bug #1.

    Also the ping should form the request properly… if the “actual” webhook request picks up the extra (necessary) headers, then the ping should too! Probable bug #2.

    Anyhoo… even if ppl on the WC end conclude that there are bugs here (I’m not saying there are, but it is looking that way), I don’t expect an immediate fix: we’re all busy.

    As our install of the WP/WC site is all scripted, I can just sling a 0 into that table row. I can’t find anything in the webhooks docs (which are very end-user / UI-centric, unless me and google just can’t find the treasure trove of dev-centric docs?) that indicate a way of disabling the ping, so poss an SQL UPDATE statement will need to be the order of the day :-S

    BTW, Frank: thanks for your quick response! I know I have not answered your Qs, but I thought this further info probably casts better light on the scene.

    Thread Starter donaldcameron

    (@donaldcameron)

    More info again.

    I have found the code that runs the ping, and the code that makes the delivery request, and compared them.

    https://woocommerce.github.io/code-reference/files/woocommerce-includes-class-wc-webhook.html#source-view.603

    	public function deliver_ping() {
    $args = array(
    'user-agent' => sprintf( 'WooCommerce/%s Hookshot (WordPress/%s)', Constants::get_constant( 'WC_VERSION' ), $GLOBALS['wp_version'] ),
    'body' => 'webhook_id=' . $this->get_id(),
    );

    $test = wp_safe_remote_post( $this->get_delivery_url(), $args );

    ^^^ does not add the additional headers.

    Contrast with the correct approach in https://woocommerce.github.io/code-reference/files/woocommerce-includes-class-wc-webhook.html#source-view.343:

    public function deliver( $arg ) {
    // SNIP

    // Setup request args.
    $http_args = array(
    // SNIP
    );

    $http_args = apply_filters( 'woocommerce_webhook_http_args', $http_args, $arg, $this->get_id() );

    // Add custom headers.
    // SNIP

    // Webhook away!
    $response = wp_safe_remote_request( $this->get_delivery_url(), $http_args );

    Note how deliver calls apply_filters there. deliver_ping should be doing the same.

    Can’t see how this isn’t just a bug?

    I’ve had a look through the code and the docs for the CLI and API and can see nowhere that exposes pending_delivery as a settable. There’s a (couple of ~) set_pending_delivery methods and calls thereto, but it’s all internal stuff.

    I’ll bodge this by hitting the DB directly.

    Hi @donaldcameron,

    Thank you for taking the time to thoroughly investigate this. I’ve been following this thread closely and doing some research on my end as well.

    After reviewing the behavior, the second empty ping is not a bug. It’s part of WooCommerce’s webhook delivery verification process. WooCommerce sends an initial test request to check if the webhook endpoint is reachable before sending actual order data. The empty request without authentication headers indicates it’s an internal validation ping.

    A practical way to handle both real and validation requests is to modify your webhook endpoint with the following code:

    add_action('init', function() {
        // Check if this is the webhook endpoint
        if (isset($_GET['my_webhook'])) {
            $request_body = file_get_contents('php://input');
    
            if (empty($request_body)) {
                http_response_code(200);
                exit('OK');
            }
    
            // Normal webhook processing goes here
    
            http_response_code(200);
            exit('Processed');
        }
    });

    This ensures that empty or test webhook calls don’t trigger errors or unnecessary processing, as they are immediately blocked if the request body is empty.

    For further technical questions beyond this, it’s best to open an issue with the GitHub team here: WooCommerce GitHub Issues if you believe it’s a bug or need deeper clarification.

    Thread Starter donaldcameron

    (@donaldcameron)

    Cheers for the response, Moses!

    And, yeah, I get all that. I did actually even quote the part of the docs that explains it.

    I suspect you have not actually seen my most recent response? I do explain what the story is, include relevant code etc.

    But to summarise again:

    It’s not the _emptiness of the body_ that is the issue. It’s the request doesn’t send all the headers _that I’ve configured it to_ when doing the ping. It’s not sending the auth. This makes it not a valid way to perform the test it’s trying to. I pointed out the bit of code and what’s missing.

    Regarding this:

    A practical way to handle both real and validation requests is to modify your webhook endpoint

    I amn’t using WP on the other end, and also *simply letting any old unauthorised request through to my app* is not a great suggestion, security-wise. The security on the end point says “it needs to be a post, and it needs to have valid auth. Fullstop”. The request doesn’t even get to my controller method; the routing & security systems go “nuh-uh” well before that. This is how these things tend to work in 2025, I think? It’s not for the controller to deal with this stuff on an ad hoc basis.

    I worked around it by updating the DB field when we first install the webhook. This is fine. If the webhook’s target _fails_ subsequently, we’ll get all the logging and alerts and stuff.

    Hi @donaldcameron,

    Thanks for clarifying! The test ping is mainly just a connectivity check from WooCommerce/Stripe to see if your webhook endpoint is reachable and responding with a 200 OK. It doesn’t include authentication headers or full payload data, so it isn’t meant to fully simulate a real webhook request.

    Your security setup is working as intended — blocking unauthenticated requests — and your workaround of updating the DB on webhook creation is a safe way to handle it. For fully testing your webhook logic, you’ll want to use actual webhook events, which include the proper headers and data.

    For further discussion regarding this please create a topic on GitHub as earlier suggested so the appropriate dev team can get involved in the discussion.

    For the meantime I’ll mark this thread as resolved. If you have any further questions, please feel free to reach out or create a new topic.

    Thread Starter donaldcameron

    (@donaldcameron)

    Hi @donaldcameron,

    Thanks again for creating the GitHub discussion as requested earlier—it really helps the team and contributors follow up with the conversation.

    If you have a moment, could you please share your experience with WooCommerce by leaving a review? Here’s the direct link: https://wordpress.org/support/plugin/woocommerce/reviews/#new-post

    Your feedback is valuable to us and to the entire WordPress community.

Viewing 8 replies - 1 through 8 (of 8 total)

The topic ‘order.created webhook firing twice’ is closed to new replies.