D Diagent docs

WordPress & WooCommerce

WooCommerce deep links

When WooCommerce is active on the WordPress site, the Pitchbar plugin unlocks a deep integration that goes far beyond the standard content sync: logged-in shopper context, the lookup_order tool, coupon emission & apply, lead push-back, and the abandoned_cart trigger.

None of these endpoints fire if WooCommerce isn't loaded โ€” the plugin defers every WC-dependent hookup to the woocommerce_loaded action so alphabetical plugin load order doesn't matter.

WooCommerce load-order race

Plenty of pre-v2.0 WordPress integrations have a subtle bug: they call class_exists('WooCommerce') at plugins_loaded priority 10 and silently disable WC features when the check returns false. On sites where the integration plugin loads alphabetically before woocommerce/ (e.g. pitchbar < woocommerce), the WooCommerce main class hasn't been registered yet at priority 10 โ†’ false โ†’ broken integration with no error message.

Pitchbar v2.0.0 uses a two-layer guard:

if (function_exists('did_action') && did_action('woocommerce_loaded') > 0) {
    $this->bootWoo();   // WC already loaded โ€” wire up immediately.
} else {
    add_action('woocommerce_loaded', [$this, 'bootWoo'], 10);
}

woocommerce_loaded fires after the WC main class is fully registered, so by the time bootWoo() runs every class_exists('WooCommerce') check passes deterministically.

Logged-in shopper context

When a WooCommerce customer is signed in and visits a page that loads the widget, the plugin attaches a short-lived signed data-shopper-token to the widget script tag. The widget forwards it to /api/v1/widget/init; Pitchbar verifies it and bakes wp_user_id + email_hash claims into the widget JWT for the chat session.

What's encoded in the token:

  • wp_user_id โ€” the WordPress user ID (integer, sent as string in the token).
  • email_hash โ€” SHA-256 of the lowercased email. Never the raw email.
  • source โ€” literal "wordpress".
  • exp โ€” unix timestamp, ~24h out so the same token is valid across a normal browsing session.

The token is signed with the workspace's shopper_signing_secret (per-token plaintext, captured by the plugin on first handshake). A bad signature is silently dropped โ€” the visitor still chats anonymously, no error surfaces.

The lookup_order tool

EcommercePreset's order_status capability maps to a real tool. When the visitor asks about orders, shipping, returns, or refunds, the LLM may call lookup_order(limit, order_number?). The tool:

  1. Reads the conversation's stored shopper claims (set at /widget/init).
  2. Resolves the agent's woocommerce_products Source's site_url.
  3. Pulls a shopper_signing_secret from any active workspace API token.
  4. POSTs an HMAC-signed body to {site_url}/wp-json/pitchbar/v1/orders/lookup with a 5-second hard timeout.
  5. Returns the orders payload to the LLM verbatim for it to summarise.

Refusal paths (no callback fired):

  • No shopper claim on conversation โ†’ not_signed_in. The LLM asks the visitor to sign in.
  • No woocommerce_products source โ†’ no_wordpress_source.
  • No active API token with a signing secret โ†’ no_signing_secret.

The plugin's OrderLookupController returns up to 10 orders (default 5), each with id, number, status, total, currency, items array, tracking URL (best-effort across AfterShip, ST tracking, generic _tracking_url meta), and the customer's order view URL.

Filter to order_number if the visitor asks about a specific order โ€” the plugin filters the result set after the customer-id query so you never expose another customer's data.

Lead push-back

When the chat agent captures a lead (a visitor leaves an email or submits a lead form), Pitchbar mirrors the contact back into the WordPress site:

TriggerWhat happens
LeadCapturedEvent PushLeadToWordPress queued listener fires. It resolves the agent's WordPress or WooCommerce source, signs an HMAC body with the workspace's shopper signing secret, and POSTs to /wp-json/pitchbar/v1/leads.
WP receives the call If WooCommerce is active, plugin creates a WC customer via wc_create_new_customer. Otherwise creates a WP subscriber. pitchbar_lead_id + pitchbar_conversation_id land in user meta so the store owner can correlate the WP user with the Pitchbar transcript.

Idempotent on email: a second push for the same address updates the existing user's first_name, billing_phone, pitchbar_lead_id, and pitchbar_conversation_id meta instead of creating a duplicate user. Transport failures are logged and swallowed โ€” the listener never blocks the visitor's chat turn.

Coupon emission & apply

Ecommerce agents can emit a <coupon/> block in chat replies when a visitor's intent + an active WC coupon line up. The full data flow:

  1. Plugin's CouponSyncer runs after every product sync, enumerates publish-status shop_coupon posts, filters expired / over-limit, and POSTs the snapshot to /api/v1/wp/coupons/sync.
  2. Pitchbar persists the list on the source's config['coupons'] array (max 50).
  3. PromptBuilder reads them when assembling the system prompt for ecommerce agents. The LLM gets the full code list and an explicit instruction never to invent codes.
  4. When the LLM decides a coupon helps the visitor, it emits an XML marker: <coupon code="WELCOME10" label="10% off your first order" discount="10%"/>.
  5. InlineBlockParser extracts the marker post-stream and emits a block SSE event of kind coupon_card.
  6. The widget's blocks.tsx renderer materialises a card with a Copy button (writes the code to the clipboard) and an Apply button.
  7. The Apply button POSTs to /api/v1/widget/coupon/apply on Pitchbar.
  8. Pitchbar HMAC-signs the body with the workspace's shopper signing secret and forwards it to /wp-json/pitchbar/v1/cart/coupon on the WP site.
  9. Because a REST request usually doesn't have a loaded WC()->cart, the plugin stages the code in a 15-minute transient: pitchbar_pending_coupon_{conversation_id}.
  10. On the visitor's next page load, the plugin's woocommerce_load_cart_from_session hook reads the transient via the pitchbar_conv_id cookie (set by the widget at init) and calls WC()->cart->apply_coupon($code).
  11. The transient is deleted on successful apply; expired transients self-clean after 15 minutes.

Net result: the visitor clicks Apply in chat, walks to the cart page, and sees the discount line already applied โ€” no copy-paste, no missed codes.

Abandoned cart re-engagement

A BehaviorRule.kind = "abandoned_cart" lets you re-engage a visitor whose cart has been sitting idle.

Mirror cart state to localStorage

The plugin enqueues cart-state.js on every front-end page when WooCommerce is active. It mirrors every WC jQuery cart event into localStorage.pitchbar_cart_state:

{
  "items": 2,           // running count
  "timestamp": 1715472000000  // ms since epoch of last mutation
}

Listens for:

  • added_to_cart jQuery event โ†’ increment count, refresh timestamp.
  • removed_from_cart jQuery event โ†’ decrement; clear if <= 0.
  • DOM click on .add_to_cart_button / .single_add_to_cart_button as a jQuery-free fallback.
  • URL pathname includes /checkout, /order-received, /order-pay, /thank-you โ†’ clear the cart state entirely.

Storage failures (private browsing, quota exhausted) are silent no-ops โ€” the trigger just doesn't fire for that visitor.

Configure the trigger

On the agent's Behavior triggers admin page, add a new rule of kind abandoned_cart:

{
  "kind": "abandoned_cart",
  "conditions": { "idle_minutes": 5 },
  "action": {
    "kind": "open_with_message",
    "message": "Still thinking it over? Happy to help you decide โ€” want a hand?"
  }
}

The widget's TriggerEngine polls the localStorage entry every 30 seconds (and once immediately on mount). When it finds a non-empty cart older than the rule's idle_minutes threshold, it fires the rule's action โ€” typically a proactive engagement bubble.

A 5-minute global trigger cooldown still applies, so the visitor isn't ambushed on every page transition.

Page-context payload (for Woo products)

When the visitor is on a single-product page (is_product()), the plugin enriches the data-page-context JSON with a woo object:

{
  "source": "wordpress",
  "page_url": "https://shop.example/product/blue-tee",
  "post_id": 142,
  "post_type": "product",
  "permalink": "โ€ฆ",
  "categories": ["tees", "summer"],
  "tags": [],
  "woo": {
    "id": 142,
    "sku": "T-BLU-M",
    "name": "Blue tee",
    "permalink": "https://shop.example/product/blue-tee",
    "price": "29.00",
    "regular_price": "39.00",
    "sale_price": "29.00",
    "currency": "USD",
    "stock_status": "instock",
    "on_sale": true
  }
}

The widget folds this into the retrieval envelope so the agent prefers content from the page the visitor is currently on (current-page boost of +0.15 on chunk score).

What WooCommerce versions are supported?

The plugin's WooCommerce dependencies are all on stable public classes:

  • WC_Coupon โ€” public since WC 2.0.
  • WC_Product โ€” public since WC 2.0.
  • WC_Order โ€” public since WC 2.0.
  • WC_Customer + wc_create_new_customer โ€” public since WC 2.5.
  • wc_get_products โ€” public since WC 2.7.
  • wc_get_orders โ€” public since WC 2.7.

Effective floor: WooCommerce 3.0+. The plugin actively tests on 8.x and 9.x. HPOS (High-Performance Order Storage) is supported because the plugin uses the public wc_get_orders() + WC_Order API rather than reading order posts directly.