WooCommerce catalogs change constantly: prices are adjusted, stock is reserved, products are unpublished, and merchandising teams update attributes throughout the day. A batch export to search can leave shoppers seeing stale inventory or outdated offers. Vespa can receive product document updates as those changes occur, allowing search and recommendation applications to reflect the catalog without waiting for a full reindex.
What real-time updates mean in a WooCommerce integration
In this architecture, WooCommerce remains the system of record. Vespa stores the searchable product representation used by applications such as product search, category discovery, autocomplete, and recommendations. When a product changes, an integration publishes the affected fields to Vespa through its document API.
A typical update flow is:
- A product is created or changed in WooCommerce.
- WooCommerce records an event containing the product ID and change metadata.
- A worker reads the current product state and transforms it into the Vespa document format.
- The worker sends a full document replacement or a partial document update to Vespa.
- Vespa distributes the update across the content nodes that hold the document.
- Queries begin using the updated values as the document becomes available to the serving layer.
The integration should publish the product ID rather than attempting to serialize a complete product object inside a synchronous WordPress request. A queue or Action Scheduler job provides retries, rate control, and better protection against slow network calls.
Model product fields for updates
Design the Vespa schema around the way the storefront searches and filters products. Fields used for filtering, ranking, or sorting commonly need an attribute index. Text fields used for matching can be indexed, while summary fields control what is returned to the application.
schema product {<br> document product {<br> field product_id type string {<br> indexing: summary | attribute<br> }<br> field title type string {<br> indexing: index | summary<br> index: enable-bm25<br> }<br> field description type string {<br> indexing: index | summary<br> }<br> field price type double {<br> indexing: attribute | summary<br> }<br> field inventory type int {<br> indexing: attribute | summary<br> }<br> field in_stock type bool {<br> indexing: attribute | summary<br> }<br> field categories type array<string> {<br> indexing: attribute | summary<br> }<br> field updated_at type long {<br> indexing: attribute | summary<br> }<br> }<br>}
Keep the document ID stable, for example product::12345. Do not use the product title or a generated URL as the document ID because either value can change. Stable IDs make retries idempotent and make deletes straightforward.
Publish WooCommerce changes through a queue
WooCommerce provides product-related actions, but the callback should enqueue work rather than call Vespa directly. The following example schedules a single product synchronization job when a product is saved:
add_action('woocommerce_update_product', function ($product_id) {<br> if (!$product_id) {<br> return;<br> }<br><br> if (function_exists('as_enqueue_async_action')) {<br> as_enqueue_async_action(<br> 'my_sync_product_to_vespa',<br> array('product_id' => (int) $product_id),<br> 'vespa-catalog'<br> );<br> }<br>});<br><br>add_action('my_sync_product_to_vespa', function ($product_id) {<br> $product = wc_get_product($product_id);<br> if (!$product) {<br> return;<br> }<br><br> $payload = array(<br> 'fields' => array(<br> 'product_id' => (string) $product->get_id(),<br> 'title' => $product->get_name(),<br> 'description' => wp_strip_all_tags($product->get_description()),<br> 'price' => (float) $product->get_price(),<br> 'inventory' => (int) max(0, $product->get_stock_quantity() ?: 0),<br> 'in_stock' => $product->is_in_stock(),<br> 'updated_at' => strtotime($product->get_date_modified()->date('c'))<br> )<br> );<br><br> my_send_product_to_vespa($product_id, $payload);<br>});
The exact event strategy depends on the store. Inventory changes can be generated by stock operations that do not follow the same path as an administrative product edit, so agencies should test stock reduction, refunds, backorders, variable products, imports, and scheduled sales separately. For high-volume stores, a product-change table or message broker can provide more explicit delivery guarantees than relying only on WordPress hooks.
Send a full product document
For a normal catalog synchronization, send the complete current representation. A full replacement reduces the risk of leaving stale fields in Vespa when a value is removed from WooCommerce.
curl --request PUT \<br> --header 'Content-Type: application/json' \<br> --data '{<br> "fields": {<br> "product_id": "12345",<br> "title": "Waterproof Hiking Jacket",<br> "description": "A lightweight shell for wet-weather hikes.",<br> "price": 129.95,<br> "inventory": 18,<br> "in_stock": true,<br> "updated_at": 1715606400<br> }<br> }' \<br> 'https://vespa.example.com/document/v1/catalog/product/docid/product::12345'
The document API path contains the Vespa namespace, document type, and document ID. In production, use HTTPS and the authentication method configured for the Vespa deployment. Do not place credentials in theme files or expose the document endpoint to unauthenticated browser requests.
Use partial updates for high-frequency fields
Price and inventory often change more frequently than titles or descriptions. A partial update avoids resending the complete document when only a few fields changed:
curl --request PUT \<br> --header 'Content-Type: application/json' \<br> --data '{<br> "fields": {<br> "price": {"assign": 119.95},<br> "inventory": {"assign": 12},<br> "in_stock": {"assign": true},<br> "updated_at": {"assign": 1715606460}<br> }<br> }' \<br> 'https://vespa.example.com/document/v1/catalog/product/docid/product::12345'
Use assign when WooCommerce is sending the authoritative value. Vespa also supports operations such as numeric increment, but incrementing inventory in Vespa is usually unsafe when WooCommerce is the source of truth. If two jobs are retried or delivered out of order, an increment can produce an incorrect stock level.
Prevent stale events from overwriting newer data
At-least-once delivery means the same event may be processed more than once, and asynchronous jobs can complete out of order. Include a WooCommerce modification timestamp or an integration sequence number in the document. Before applying an update, compare the event version with the latest version known by the worker, or make the synchronization job re-read the current product before constructing the payload.
A practical pattern is:
- Use the WooCommerce product ID as the stable document ID.
- Store
updated_ator a monotonically increasing catalog version in Vespa. - Make full document writes safe to retry.
- Use a queue deduplication key such as
product_idso several edits collapse into one current synchronization job. - Log the Vespa response, document ID, source version, and attempt number.
If strict ordering is required for a particular product, partition events by product ID or process one product’s updates sequentially. Do not assume that the order in which WordPress actions are scheduled is the order in which distributed workers will finish.
Handle product removal and visibility
When a product is permanently removed, send a document delete to Vespa:
curl --request DELETE \<br> 'https://vespa.example.com/document/v1/catalog/product/docid/product::12345'
For products that should remain available for reporting but not appear in storefront search, a visibility field is often preferable to deletion:
{<br> "fields": {<br> "is_visible": {"assign": false},<br> "updated_at": {"assign": 1715606500}<br> }<br>}
Apply is_visible = true as a query filter, or incorporate it into the ranking profile or query contract. This allows an agency to distinguish an unpublished product from a document that failed to synchronize.
Query with current catalog constraints
A search query should filter out unavailable products using the fields updated by the feed. For example, a YQL request can require products that are visible and in stock:
yql=select * from product where (userQuery()) and is_visible=true and in_stock=true
Real-time does not mean that every query observes a change at the exact instant the WooCommerce request completes. The document update must be accepted, distributed, and made available to serving nodes. Measure this propagation delay in the deployment and choose an operational target, such as p95 visibility within a few seconds. If checkout requires an authoritative stock decision, validate inventory against WooCommerce or the dedicated inventory system rather than treating search results as a reservation.
Monitor the feed as a production pipeline
Track synchronization as a data pipeline rather than only as a WordPress hook. Useful metrics include queued jobs, successful document writes, failed writes, retry count, update latency, delete count, and the age of the oldest unsynchronized product. Alert when Vespa rejects documents, when the queue grows continuously, or when a product’s updated_at value is older than the corresponding WooCommerce value.
Run a periodic reconciliation job as a safety net. It can compare a WooCommerce export or product ID list with Vespa, repair missing documents, remove orphaned documents, and report field differences. Event-driven updates provide low latency; reconciliation provides protection against lost webhooks, plugin failures, deployment mistakes, and temporary Vespa connectivity problems.