Skip to content

Instantly share code, notes, and snippets.

@onetarek
Last active February 26, 2025 14:57
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
<?php
/*
Custom WC Webhook Manager
Add new webhook topic only for WC order completed.
Developed by Md Jahidul Islam ( oneTarek ) https://onetarek.com
*/
//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
endif;
$Custom_WC_Webhook_Manager = new Custom_WC_Webhook_Manager();
@dax702
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": "127.0.0.1",
  "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:

<?php
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 = 'https://example.com/webhook';
    $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