I got a call from a client running a busy WooCommerce shop. Their inventory was a mess. They use a third-party warehouse system to manage stock, but the only way they could update their site was by manually exporting a CSV and importing it. Every. Single. Day. It was slow, error-prone, and a total nightmare. They needed a way for the warehouse system to just… tell the website when stock changes. The perfect solution here is a custom WordPress REST API endpoint.
This is a classic problem. You need to let an external service talk to WordPress, but you need to do it securely and efficiently. You need a private doorbell, not a wide-open door.
Don’t Hack It, Build It Right
My first thought, years ago, might have been to create a standalone PHP file in the theme. The warehouse could POST to `my-site.com/wp-content/themes/my-theme/update-stock.php`. And yeah, it would work. But trust me on this, it’s a terrible idea. You’re completely outside of the WordPress environment. You have to handle your own security, your own database connection, everything. It’s a security hole waiting to be exploited.
The WordPress REST API is the answer. But you don’t need the whole `WP_REST_Controller` class structure for something this simple. That’s for building a whole suite of endpoints for a resource, like posts or users. We just need one, single endpoint. That’s where `register_rest_route` comes in.
Your First Custom WordPress REST API Endpoint
We can add our own endpoint by hooking into `rest_api_init`. The real magic is in the `permission_callback`. This is our bouncer. It checks for a secret key and makes sure the request is legit before it ever touches our logic. Here’s the kicker: if this callback doesn’t return `true`, WordPress just kills the request. Simple as that.
add_action( 'rest_api_init', function () {
register_rest_route( 'myplugin/v1', '/update-stock/', array(
'methods' => 'POST',
'callback' => 'my_stock_update_callback',
'permission_callback' => function ( $request ) {
// Simple secret key authentication
$secret_key = 'your-super-secret-key-here';
$provided_key = $request->get_header( 'X-Secret-Key' );
return $provided_key === $secret_key;
},
) );
} );
function my_stock_update_callback( $request ) {
$sku = sanitize_text_field( $request->get_param( 'sku' ) );
$stock = (int) $request->get_param( 'stock' );
if ( empty( $sku ) ) {
return new WP_Error( 'no_sku', 'SKU not provided.', array( 'status' => 400 ) );
}
$product_id = wc_get_product_id_by_sku( $sku );
if ( $product_id ) {
$product = wc_get_product( $product_id );
$product->set_stock_quantity( $stock );
$product->save();
return new WP_REST_Response( array( 'status' => 'success', 'new_stock' => $stock ), 200 );
}
return new WP_Error( 'invalid_sku', 'Product not found for SKU.', array( 'status' => 404 ) );
}What’s the Big Deal?
This approach is clean, secure, and uses the WordPress core systems. We’re not reinventing the wheel. We’re letting WordPress handle the heavy lifting of routing and security, and we’re just providing the specific logic. This idea of building small, focused systems is something I’ve learned over years in the trenches, and it’s a concept that Carl Alexander explains in great detail over on his blog if you want to go deeper into the theory.
Look, this stuff gets complicated fast. If you’re tired of debugging someone else’s mess and just want your site to work, drop my team a line. We’ve probably seen it before.
Leave a Reply