WooCommerce REST API: Query Orders, Update Stock, and Authenticate with Consumer Keys

WooCommerce extends the WordPress REST API with its own namespace at /wp-json/wc/v3/, exposing products, orders, customers, coupons, shipping zones, payment gateways, and more as REST resources. Unlike the standard WordPress REST API which is read-mostly public, WooCommerce REST API endpoints require authentication for all requests — reads and writes alike. The recommended authentication method for server-to-server integrations is HTTP Basic Auth using Consumer Key and Consumer Secret credentials generated in WooCommerce → Settings → Advanced → REST API. For local development and testing, Basic Auth over HTTPS is straightforward; for production integrations between systems you control, it is the standard approach. The API supports all standard REST operations: GET for retrieval, POST for creation, PUT for update, and DELETE for deletion, with consistent response shapes across resource types.

Problem: An external ERP system needs to sync product stock levels to WooCommerce and retrieve new orders every 15 minutes — without using the WooCommerce admin UI or installing a plugin on the WooCommerce site.

Solution: Use the WooCommerce REST API with Consumer Key / Consumer Secret credentials. Use GET /wp-json/wc/v3/orders with after and status filters to retrieve new orders, and PUT /wp-json/wc/v3/products/{id} to update stock.

<?php
/**
 * Minimal WooCommerce REST API client using wp_remote_get / wp_remote_request.
 */
class WC_REST_Client {
    private string $base_url;
    private string $consumer_key;
    private string $consumer_secret;

    public function __construct( string $store_url, string $ck, string $cs ) {
        $this->base_url        = rtrim( $store_url, '/' ) . '/wp-json/wc/v3';
        $this->consumer_key    = $ck;
        $this->consumer_secret = $cs;
    }

    private function request( string $method, string $endpoint, array $body = [] ): array {
        $args = [
            'method'  => $method,
            'headers' => [
                'Authorization' => 'Basic ' . base64_encode( $this->consumer_key . ':' . $this->consumer_secret ),
                'Content-Type'  => 'application/json',
            ],
            'timeout' => 30,
        ];
        if ( ! empty( $body ) ) {
            $args['body'] = wp_json_encode( $body );
        }
        $response = wp_remote_request( $this->base_url . $endpoint, $args );
        if ( is_wp_error( $response ) ) {
            throw new RuntimeException( $response->get_error_message() );
        }
        return json_decode( wp_remote_retrieve_body( $response ), true );
    }

    // Get orders placed after a specific date with a specific status
    public function get_orders( string $after_date, string $status = 'processing' ): array {
        $qs = http_build_query( [ 'after' => $after_date, 'status' => $status, 'per_page' => 100 ] );
        return $this->request( 'GET', '/orders?' . $qs );
    }

    // Update product stock quantity
    public function update_stock( int $product_id, int $quantity ): array {
        return $this->request( 'PUT', '/products/' . $product_id, [
            'stock_quantity'   => $quantity,
            'manage_stock'     => true,
        ] );
    }

    // Create a coupon
    public function create_coupon( string $code, float $amount, string $type = 'percent' ): array {
        return $this->request( 'POST', '/coupons', [
            'code'          => sanitize_text_field( $code ),
            'discount_type' => $type,
            'amount'        => (string) $amount,
            'usage_limit'   => 1,
        ] );
    }
}

// Usage:
$client = new WC_REST_Client(
    'https://mystore.com',
    'ck_xxxxxxxxxxxxxxxxxxxx',
    'cs_xxxxxxxxxxxxxxxxxxxx'
);

$new_orders = $client->get_orders( '2020-11-15T00:00:00', 'processing' );
$client->update_stock( 42, 150 );

NOTE: Consumer Key / Consumer Secret credentials use HTTP Basic Auth. Always use HTTPS — sending these credentials over plain HTTP exposes them in every request. For public-facing or browser-based integrations, use OAuth 1.0a instead (WooCommerce supports it), which signs requests without transmitting the secret. The per_page parameter maxes out at 100 — for large datasets, paginate using the page parameter and the X-WP-TotalPages response header (the WooCommerce REST API returns the same pagination headers as the core WordPress REST API).