Order status transitions have hooks of their own

Hooking save_post to react to an order being paid works occasionally and misses every transition made from a payment gateway callback, the REST API or WP-CLI. The status transition has its own hooks and they fire in every path.

// any transition into processing
add_action( 'woocommerce_order_status_processing', 'shop_notify_warehouse' );

// one specific transition
add_action( 'woocommerce_order_status_on-hold_to_processing', 'shop_release_stock' );

// every transition, with both ends
add_action( 'woocommerce_order_status_changed', function ( $id, $from, $to, $order ) {}, 10, 4 );

Prefer the specific form where the business rule is specific: “notify the warehouse when an order becomes processing” is not the same rule as “when it becomes processing from on-hold”, and the general hook fires for both. These run inside the request that changed the status, so anything slow in them delays a gateway callback — which is a good way to make a payment provider retry.