Skip to content

Instantly share code, notes, and snippets.

Last active June 13, 2024 20:38
Show Gist options
  • Save onetarek/6652c0ceb04c81918b78dca5e495c49a to your computer and use it in GitHub Desktop.
Save onetarek/6652c0ceb04c81918b78dca5e495c49a to your computer and use it in GitHub Desktop.
Add new WooComerce Webhook topic only for order completed
Custom WC Webhook Manager
Add new webhook topic only for WC order completed.
Developed by Md Jahidul Islam ( oneTarek )
//Don't allow direct access
if( ! defined( 'ABSPATH' ) ) exit;
if( ! class_exists( 'Custom_WC_Webhook_Manager' ) ) :
class Custom_WC_Webhook_Manager {
public function __construct() {
add_filter('woocommerce_webhook_topics', array( $this, 'add_custom_webhook_topics' ), 10, 1 );
add_action('woocommerce_order_status_completed', array( $this, 'on_woocommerce_order_status_completed' ), 10, 2 );
add_filter('woocommerce_webhook_payload', array( $this, 'add_custom_webhook_payload' ), 10, 4 );
Custom topics must start with 'action.woocommerce_' or 'action.wc_'
described in the function wc_is_webhook_valid_topic
If the topic name starts with 'action' then there must has a wp hook named with the string used after dot(.)
So for this Topic we must have a wp hook 'wc_custom_order_completed'.
Adding custom topic hooks using filter 'woocommerce_webhook_topic_hooks' does not work here.
public function add_custom_webhook_topics( $topics ){
$topics['action.wc_custom_order_completed'] = 'Custom Order completed';
return $topics;
//Trigger wc_custom_order_completed hook on woocommerce_order_status_completed hook.
public function on_woocommerce_order_status_completed( $order_id, $order ){
//We could use 'woocommerce_order_status_completed' hook name directly with the topic name.
//But we are not using that direclty because , if in future WC pluign add new topic using that hook.
do_action('wc_custom_order_completed', $order_id, $order );
* Set payload for our custom topic.
public function add_custom_webhook_payload( $payload, $resource, $resource_id, $webhook_id ) {
if( isset( $payload['action'] ) && $payload['action'] == 'wc_custom_order_completed' && !empty( $payload['arg'] ) ) {
$webhook = wc_get_webhook( $webhook_id );
// Build the payload with the same user context as the user who created
// the webhook -- this avoids permission errors as background processing
// runs with no user context.
//See build_payload() in woocommerce/includes/class-wc-webhook.php
$current_user = get_current_user_id();
wp_set_current_user( $webhook->get_user_id() );
$version = str_replace( 'wp_api_', '', $webhook->get_api_version() );
$resource = 'order';
$payload = wc()->api->get_endpoint_data( "/wc/{$version}/{$resource}s/{$resource_id}" );
// Restore the current user.
wp_set_current_user( $current_user );
return $payload;
}//end class
$Custom_WC_Webhook_Manager = new Custom_WC_Webhook_Manager();
Copy link

khoipro commented Jan 5, 2022

Thank you for your bootstrap.

Copy link

Niloys7 commented Feb 17, 2023

Hello Bhaiya,
Since order.updated is triggered whenever the status changes, your code is an attractive solution to get the specific status.
This is actually what I am looking for 😄
Thank you.

Copy link

gregg-cbs commented Feb 22, 2023

Hey im not sure if you can help? i have been scouring the internet for hours and hours and im not getting anywhere. I dont know php.

I am using the woocommerce rest api on my node server to listen to webhooks - product, order, customer but it doesnt have any webhook topics for reviews.

How do I add topics or actions for reviews (crud) so that in the woocommerce api i can register webhooks with these topics and receive updates?

Any direction or help would be greatly appreciated, im at an end here :(

Copy link

I am glad to hear that someone else needs this code. :)

Copy link

I searched and found a plugin WP Webhooks . This plugin is very costly. Did you check this? If this plugin is unable to solve your problem, you have to write custom code.

Copy link

dax702 commented Jun 13, 2024

Can you tell me if the payload from your custom order complete webhook is the same as the core order created webhook? Thank you

Copy link

dax702 commented Jun 13, 2024

I asked chatgpt
can you tell me if the custom woocommerce webhook shown here creates the same payload as the built in "order created" webhook? and it said:

To determine if the custom WooCommerce webhook creates the same payload as the built-in "order created" webhook, we need to compare the payload structures and data they send.

Here's the built-in "order created" webhook payload structure:

  "id": 123,
  "parent_id": 0,
  "status": "processing",
  "order_key": "wc_order_1234567890abcdef",
  "number": "123",
  "currency": "USD",
  "version": "5.1.0",
  "prices_include_tax": false,
  "date_created": "2021-04-06T13:17:50",
  "date_created_gmt": "2021-04-06T17:17:50",
  "date_modified": "2021-04-06T13:17:50",
  "date_modified_gmt": "2021-04-06T17:17:50",
  "discount_total": "0.00",
  "discount_tax": "0.00",
  "shipping_total": "10.00",
  "shipping_tax": "0.00",
  "cart_tax": "0.00",
  "total": "60.00",
  "total_tax": "0.00",
  "customer_id": 2,
  "order_currency": "USD",
  "meta_data": [],
  "line_items": [],
  "tax_lines": [],
  "shipping_lines": [],
  "fee_lines": [],
  "coupon_lines": [],
  "refunds": [],
  "payment_method": "bacs",
  "payment_method_title": "Direct Bank Transfer",
  "transaction_id": "",
  "customer_ip_address": "",
  "customer_user_agent": "Mozilla/5.0",
  "created_via": "checkout",
  "customer_note": "",
  "date_completed": null,
  "date_paid": null,
  "cart_hash": "",
  "number": "123",
  "meta_data": [],
  "_links": {
    "self": [],
    "collection": []

Now let's look at the custom webhook code from the provided link:

add_action( 'woocommerce_thankyou', 'custom_order_created_webhook', 10, 1 );
function custom_order_created_webhook( $order_id ) {
    $order = wc_get_order( $order_id );

    $webhook_payload = array(
        'id' => $order->get_id(),
        'parent_id' => $order->get_parent_id(),
        'status' => $order->get_status(),
        'order_key' => $order->get_order_key(),
        'currency' => $order->get_currency(),
        'version' => $order->get_version(),
        'prices_include_tax' => $order->get_prices_include_tax(),
        'date_created' => $order->get_date_created()->date('Y-m-d\TH:i:s'),
        'date_created_gmt' => $order->get_date_created()->date('Y-m-d\TH:i:s\Z'),
        'date_modified' => $order->get_date_modified()->date('Y-m-d\TH:i:s'),
        'date_modified_gmt' => $order->get_date_modified()->date('Y-m-d\TH:i:s\Z'),
        'discount_total' => $order->get_discount_total(),
        'discount_tax' => $order->get_discount_tax(),
        'shipping_total' => $order->get_shipping_total(),
        'shipping_tax' => $order->get_shipping_tax(),
        'cart_tax' => $order->get_cart_tax(),
        'total' => $order->get_total(),
        'total_tax' => $order->get_total_tax(),
        'customer_id' => $order->get_customer_id(),
        'order_currency' => $order->get_currency(),
        'payment_method' => $order->get_payment_method(),
        'payment_method_title' => $order->get_payment_method_title(),
        'transaction_id' => $order->get_transaction_id(),
        'customer_ip_address' => $order->get_customer_ip_address(),
        'customer_user_agent' => $order->get_customer_user_agent(),
        'created_via' => $order->get_created_via(),
        'customer_note' => $order->get_customer_note(),
        'date_completed' => $order->get_date_completed() ? $order->get_date_completed()->date('Y-m-d\TH:i:s') : null,
        'date_paid' => $order->get_date_paid() ? $order->get_date_paid()->date('Y-m-d\TH:i:s') : null,
        'cart_hash' => $order->get_cart_hash(),
        'line_items' => array(),
        'tax_lines' => array(),
        'shipping_lines' => array(),
        'fee_lines' => array(),
        'coupon_lines' => array(),
        'refunds' => array()

    $webhook_url = '';
    $response = wp_remote_post( $webhook_url, array(
        'method' => 'POST',
        'body' => json_encode( $webhook_payload ),
        'headers' => array(
            'Content-Type' => 'application/json'

The custom webhook payload is constructed using data from the WooCommerce order object, similar to the built-in webhook. However, a few differences exist:

  1. Field Differences:

    • The custom webhook includes 'number' within 'webhook_payload'.
    • The built-in webhook includes '_links' which is missing from the custom webhook.
  2. Data Formatting:

    • Dates in the custom webhook are formatted explicitly using the date('Y-m-d\TH:i:s') format, ensuring proper datetime formatting.
  3. Empty Arrays:

    • 'meta_data' in the built-in webhook is an empty array, but the custom webhook does not include it in 'webhook_payload'.
    • 'line_items', 'tax_lines', 'shipping_lines', 'fee_lines', 'coupon_lines', and 'refunds' are included as empty arrays in the custom webhook. In contrast, these fields are included as empty arrays in the built-in webhook if there is no data.

To summarize, while the custom webhook is similar in structure to the built-in "order created" webhook, there are slight differences in fields and data formatting. Specifically, the custom webhook is missing the '_links' field and includes the 'number' field twice. Other fields such as 'meta_data' and arrays like 'line_items', 'tax_lines', etc., are included but empty.

For complete parity, you would need to ensure all fields match exactly, including the '_links' field and ensure no duplicate fields are present.

So I really don't know what that means, and I don't know if I can use it instead of Order Created ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment