Order Lifecycle Hooks in WooCommerce

As you probably know WooCommerce (or Woo, how should we call it today?) is packed with hooks. And sometimes when you’re creating a custom plugin for WooCommerce or even building a theme, it could be quite overwhelming to figure out which one of the hooks is better to use for your specific purpose.

The good news is – I have described every situation in this tutorial!

Order is Created via Checkout

When customers make a purchase on our store and as a result an order is created, there is a bunch of hooks which are going to be fired.

The hooks below are mentioned in the same order they are used by WooCommerce when an order is created from the checkout page, please also keep in mind that the hooks are different for a checkout shortcode and for a checkout block.

Classic Checkout

If your store checkout page is created like this…

Classic checkout shortcode in WooCommerce
In WooCommerce version prior 8.3 we used [woocommerce_checkout] shortcode by default.

…then here is the list of hooks which are going to be used in this case:

  1. woocommerce_checkout_create_order – before an order is saved,
  2. woocommerce_new_order,
  3. woocommerce_checkout_order_created – right after an order and its custom order item meta is saved,
  4. woocommerce_checkout_order_processed – after an order is successfully created and before processing the payment, kind of similar to the previous hook, but it is also in use in case there was an error when creating an order,
  5. All order status change hooks (the order status is changing from pending to processing here).
  6. woocommerce_payment_complete – when completing the payment but for order statuses “On-hold”, “Pending”, “Failed”, “Cancelled” (can be changed with a filter). You can actually find a little but more explanation in my guide about payment complete hooks.
  7. woocommerce_thankyou – this is actually the hook you should avoid using because depending on a payment method not everyone finishes on the “Thank you” page.

Block Checkout

But more and more WooCommerce stores are starting to use the checkout block.

WooCommerce checkout block

The action hooks for checkout pages created with blocks are quite different:

  1. woocommerce_new_order,
  2. woocommerce_update_order (multiple times),
  3. woocommerce_store_api_checkout_order_processed,
  4. Order status change hooks (from draft to pending),
  5. Once again, all order status change hooks (from pending to processing),
  6. woocommerce_payment_complete,
  7. woocommerce_thankyou.

Please keep in mind that woocommerce_update_order can be fired around 5 times here.

Order is Created or Updated Manually (in WordPress Admin)

If previously you used something like save_post or save_post_shop_order, I recommend you to stop doing so, at least because these hooks are designed for regular WordPress CPTs and not for WooCommerce orders, more than that, they are not going to work with HPOS-orders.

The hooks we need are:

  • woocommerce_new_order,
  • woocommerce_update_order (multiple times).

The main question here – is there is a way to differentiate whether these hooks are fired from the WordPress dashboard or from somewhere else?

Easy – just by checking the nonce!

add_action( 'woocommerce_new_order', 'rudr_create_or_update_order', 25, 2 );
add_action( 'woocommerce_update_order', 'rudr_create_or_update_order', 25, 2 );

function rudr_create_or_update_order( $order_id, $order ) {

		isset( $_REQUEST[ '_wpnonce' ] ) 
		&& wp_verify_nonce( $_REQUEST[ '_wpnonce' ], "update-order_{$order_id}" ) 
	) {

		// do your stuff



You can also use check_admin_referer() if it suits you best.

Order is deleted

When you’re deleting an order only two hooks are going to be fired – which one – depends on whether you’re deleting an order permanently or just moving it to the trash.

  1. woocommerce_before_delete_order or woocommerce_before_trash_order,
  2. woocommerce_delete_order or woocommerce_trash_order.

If you’re using one of these hooks and want to double check that it is fired from the WordPress admin dashboard, you can verify the nonce like this:

add_action( 'woocommerce_before_trash_order', 'rudr_trash_order' );

function rudr_trash_order( $order_id ) {

		isset( $_REQUEST[ '_wpnonce' ] ) 
		&& wp_verify_nonce( $_REQUEST[ '_wpnonce' ], 'bulk-orders' )
		// agreed, would be better if it was something like "trash-order_{$order_id}"
	) {

		// do your stuff



Order is Created or Updated via REST API

In the legacy version of the API there were very nice hooks like woocommerce_api_create_order and woocommerce_api_edit_order but we can not use them anymore.

The hooks we can use when an order is created or updated via WooCommerce REST API are:

  1. woocommerce_new_order,
  2. woocommerce_update_order (multiple times),
  3. All order status change hooks (from pending to processing),
  4. woocommerce_payment_complete.

Ok, since we don’t have unique hooks for REST API requests specifically, can we differentiate somehow whether the hooks are fired by a REST API request or in some other way?

Yes, we can!

And in order to do so we just need to check REST_REQUEST constant in our function.

add_action( 'woocommerce_new_order', 'rudr_create_or_update_order_restapi' );
add_action( 'woocommerce_update_order', 'rudr_create_or_update_order_restapi' );

function rudr_create_or_update_order_restapi( $order_id ) {

	if( defined( 'REST_REQUEST' ) ) {

		// do your stuff



Order Status is Changed

Order statuses in WooCommerce can be changed often enough, that’s why you see a link to this chapter from almost every other chapter of this tutorial.

The hooks for order status changes:

  1. woocommerce_update_order,
  2. woocommerce_order_status_{$status_from}_to_{$status_to},
  3. woocommerce_order_status_changed.

The second hook allows us to provide previous and new order status names as a part of the hook name, the third one has order status names available as function arguments anyway.

Changing order status to Refunded

And the last but not least let’s take a look at the hooks that are fired when we’re refunding a WooCommerce order.

  1. woocommerce_order_fully_refunded or woocommerce_order_partially_refunded,
  2. All order status change hooks (from the current status to refunded),
  3. woocommerce_update_order,
  4. woocommerce_order_refunded.

Ok, that’s pretty much everything I wanted to show you guys, if you think I missed a hook in this guide, please let me know in the comments section.

Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X