Pitchbar for WordPress — Documentation

Welcome. This document covers everything you need to install, configure, and operate the Pitchbar WordPress & WooCommerce plugin. The plugin connects your WordPress site to a Pitchbar workspace and adds a streaming AI sales chat widget, full content sync, and deep WooCommerce integration.

You need a Pitchbar workspace to use this plugin. The plugin is the bridge between WordPress and your Pitchbar agent — it does not contain the AI itself. Create a workspace in your Pitchbar deployment (or purchase the main Pitchbar SaaS item) before installing.

Overview

Pitchbar for WordPress is a companion plugin that does six things:

  1. Embeds the Pitchbar widget on every public page via wp_footer, scoped to the post types you opt into.
  2. Syncs your content — posts, pages, custom post types, and the entire WooCommerce catalog — into the agent's knowledge base.
  3. Renders page-builder pages properly (Elementor, Divi, Beaver Builder, Oxygen, Bricks) so their visible HTML actually reaches the agent.
  4. Integrates with WooCommerce — logged-in shopper context, order lookup, coupon emission + apply, abandoned-cart trigger, and lead mirroring back into WP.
  5. Shows sync status per post + product in the WP admin list tables with green / yellow / gray pills.
  6. Authenticates securely in both directions — Bearer token outbound, HMAC-signed callbacks inbound.

The plugin's source lives in the Pitchbar monorepo at wp-plugin/pitchbar/ and ships as a versioned zip you install from Plugins → Add New → Upload Plugin.

Requirements

ComponentMinimumTested through
WordPress6.46.6
PHP7.48.4
WooCommerce8.0 (optional)9.x
MultisiteSupported per-site
Page buildersElementor (Free + Pro), Divi, Beaver Builder, Oxygen, Bricks
RTL localesYes (Arabic, Hebrew, Persian, Urdu, ...)

The plugin core (widget embed + post sync) runs on a non-Woo site. Every WooCommerce-specific feature is wrapped behind a class_exists('WooCommerce') guard, so the plugin never breaks anything when WooCommerce isn't installed.

Quick start

  1. Upload pitchbar.zip via Plugins → Add New → Upload Plugin.
  2. Activate the plugin.
  3. Open Settings → Pitchbar.
  4. Paste your Pitchbar workspace base URL and an API token (created at Settings → API tokens in your Pitchbar admin).
  5. Click Test connection. The plugin lists agents in your workspace.
  6. Pick an agent and Save changes.
  7. Click Sync posts now (and, on Woo sites, Sync products now) so the agent has knowledge to answer from.
  8. Visit any public page on the site. The widget loads in the bottom-right corner.

Install & activate

From CodeCanyon

  1. Download pitchbar.zip from your CodeCanyon downloads.
  2. Sign in to WordPress as a user with manage_options (usually administrator).
  3. Go to Plugins → Add New → Upload Plugin.
  4. Choose pitchbar.zip. Click Install Now.
  5. Click Activate Plugin.

From your Pitchbar admin (self-built)

Super-admins of a Pitchbar deployment can produce an install-ready zip from the platform admin without ssh access:

  1. Sign in to your Pitchbar admin as a super_admin.
  2. Open /admin/integrations/wordpress.
  3. Click Build latest. The server runs php artisan pitchbar:build-wp-plugin against the bundled source and writes a versioned archive to storage/app/private/wp-plugin-builds/pitchbar-{version}.zip.
  4. Click Download next to the resulting row.
Multisite: activate per site (not network-wide). Each subsite connects to its own Pitchbar workspace and can attach a different agent. Network-wide activation is not supported in v2.x.

Connect to your workspace

After activation, you'll see a “Pitchbar is installed but not configured yet” notice in the admin. Open Settings → Pitchbar to wire up the connection.

Create an API token in Pitchbar

  1. Sign in to your Pitchbar workspace.
  2. Open Settings → API tokens.
  3. Click Create token. Name it after your WordPress site (e.g. my-site.com).
  4. Select the wp:integration scope.
  5. Copy the plaintext value shown once — it's never displayed again.

Paste into the plugin settings

FieldExampleNotes
Pitchbar base URLhttps://app.pitchbar.exampleNo trailing slash. Just the workspace host.
API tokenpbar_…The plaintext you copied above. Stored in wp_options.

Click Test connection. On success the panel shows the workspace name and populates the Attached agent dropdown with every agent in that workspace. Pick one. Save changes.

Diagnostics on failure

Test connection surfaces detailed errors instead of a generic “Connection failed”:

Click Show details to reveal the full diagnostic block. Common status hints:

StatusLikely causeFix
401API token invalid or revokedReissue in Pitchbar → Settings → API tokens
403Token missing wp:integration abilityRecreate the token with that scope
404Pitchbar app not fully deployedVerify the base URL points at the Pitchbar host, not a marketing redirect
419CSRF mismatchBase URL should point at the API host, not a redirect chain
429Token rate-limitedWait a minute and retry
5xxPitchbar server errorTail storage/logs/laravel.log on the Pitchbar host

Widget display options

SettingDefaultEffect
EnabledOnInjects the widget on the front end. Uncheck to disable without disconnecting.
Post typesPosts, PagesTick more to load the widget on custom post types' singular pages.

Content sync — posts & pages

Knowledge sync is what makes the agent useful: the AI can only answer from content it has been given. The plugin pushes posts, pages, and the custom post types you enabled to your Pitchbar agent so the chat can answer from your actual site copy.

Bulk sync

On the Settings page (when configured), the Knowledge sync panel exposes a Sync posts now button. Click it once after the initial connect. The plugin paginates WP_Query in batches of 50, posts each batch to POST /api/v1/wp/posts/sync on Pitchbar, and stamps a sync timestamp + content hash on every successfully-synced row.

Idempotent. Pitchbar's server-side syncer short-circuits unchanged posts via the content hash. Re-running “Sync now” back-to-back is safe and only sends what's actually changed.

Delta hooks (automatic)

Once configured, the plugin keeps the agent up to date automatically:

Resumable on shared hosting

Many shared hosts cap max_execution_time at 30 seconds. A site with thousands of posts can't finish in one pass — the plugin handles this gracefully:

  1. Hard 20-second budget per pass.
  2. If the budget is exceeded with pages still queued, the plugin persists a resume marker in a transient (pitchbar_post_sync_resume, 1 hour TTL).
  3. It schedules a single WP-Cron event 30 seconds out (pitchbar_run_full_sync_event) which resumes from the same page.
  4. A soft info notice appears on the Plugins screen while the background tick is finishing.
  5. Re-running “Sync now” manually resumes from the same point — every batch is idempotent on external_id.

Empty-body fallback

Some posts have empty post_content — featured-image-only posts, builder-owned stubs, etc. The plugin synthesises a body from title + excerpt + taxonomy terms before sending so the agent always has indexable text to work with.


Page builders

Most page builders stash the actual layout in postmeta and leave post_content either empty or as a shortcode stub. A naive content sync would skip every page on a builder site. The plugin invokes each builder's native renderer to produce the visible HTML before sending.

BuilderDetectionRenderer
Elementor (Free + Pro)_elementor_edit_mode postmeta\Elementor\Plugin::frontend()->get_builder_content_for_display()
Divi_et_pb_use_builder postmetasetup_postdata() + the_content filter
Beaver Builder_fl_builder_enabled postmetaFLBuilder::render_content_by_id()
Oxygenct_builder_shortcodes postmetact_template_output filter
Bricks_bricks_page_content_2 postmetaBricks\Frontend::render_content()

Detection is first-match-wins based on each builder's own postmeta keys, so a site that switched builders mid-project never double-renders. If none match, the plugin falls back to the standard the_content filter pipeline.

Override the rendered HTML

The pitchbar_post_content_html filter lets you post-process the rendered HTML before it's sent to Pitchbar — strip navigation menus, force a custom Elementor template, redact a block, whatever you need:

add_filter('pitchbar_post_content_html', function ($html, $post) {
    // Strip everything between <nav>...</nav> tags
    $html = preg_replace('#<nav\b[^>]*>.*?</nav>#is', '', $html);
    return $html;
}, 10, 2);

WooCommerce products

When WooCommerce is active, a second panel appears on the Settings page: WooCommerce products. Click Sync products now to push the catalog. Same architecture as posts — batched, resumable, idempotent on external_id.

Every product type is included

The product syncer does NOT filter by type — simple, variable, grouped, external, plus subscription, bundle, membership, booking, and any custom product type extensions register. A type filter was the cause of a real symptom on subscription / multilingual stores where “Sync now” returned 0 products, 0 queued against a catalog of 10 products.

Fallback WP_Query

Some hosts and extensions hook wc_get_products in ways that hide the entire catalog (subscription gates, multilingual scope filters, custom-types-of-the-month). When the first call returns 0 products and we're on page 1, the plugin retries with a raw WP_Query against post_type=product. A Logger::warn records this so admins can debug.

What gets synced per product

FieldSource
SKUget_sku()
Nameget_name()
Permalinkget_permalink()
Image URLFirst gallery image (raw URL, so the LLM can emit a product card with a real thumbnail)
Short + long descriptionget_short_description() + get_description()
Price + sale price + currencyget_price(), get_sale_price(), get_woocommerce_currency()
Stock statusget_stock_status() (in stock / out of stock / on backorder)
On saleis_on_sale()
CategoriesProduct category term names
AttributesAll product attribute slugs + values

Coupon snapshot rides along

After every successful full product sync (not on resumed-out partial runs), the plugin calls CouponSyncer->run() which posts the store's currently-valid shop_coupon CPT entries to POST /api/v1/wp/coupons/sync. Expired and exhausted codes are filtered out. The agent's system-prompt fragment lists these so it never invents codes that don't exist.


“Indexed” admin badges

Every successful sync stamps two values on each post / product: _pitchbar_synced_at (timestamp) and _pitchbar_synced_hash (content hash sent to the server). A new Pitchbar column on the Posts, Pages, and Products admin list tables turns those into a visible pill per row:

PillCondition
IndexedThe post has a sync timestamp AND post_modified_gmt has NOT advanced past it.
Out of dateThe post has a sync timestamp but has been edited since (post_modified_gmt > synced_at).
Not indexedNo _pitchbar_synced_at meta — never synced.

The column has a forced 110px width via admin_head CSS so the header text never wraps to one letter per line on crowded WooCommerce screens.


WooCommerce — logged-in shopper context

When a logged-in WooCommerce customer browses the site, the plugin issues a short-lived signed token attached to the widget loader as data-shopper-token. The Pitchbar server verifies the signature on /widget/init and lifts the claims into the issued widget JWT so the chat session is bound to the visitor.

Claim shape

{
    "wp_user_id": "42",
    "email_hash": "sha256_hex_of_lowercased_trimmed_email",
    "source": "wordpress"
}

The plaintext email is never sent. Only the SHA-256 hash rides in the claims, used server-side for dedupe and correlation. The signature is HMAC-SHA256 with the workspace's shopper_signing_secret (received in the handshake response and stored in wp_options).

Wire format

The token is delivered as base64url(claims_json).hmac_sha256_hex. The Pitchbar server mirrors the verification path in App\Services\Widget\ShopperToken.


WooCommerce — order lookup tool

The agent can answer “Where's my order?” with real order data when the visitor is signed in. Enable the lookup_order tool on the agent in the Pitchbar admin; the LLM emits a tool-call which the Pitchbar server routes back to the plugin's REST endpoint.

POST /wp-json/pitchbar/v1/orders/lookup

Returns the last N WooCommerce orders for the signed-in wp_user_id.

Request

Header / FieldValue
X-Pitchbar-Signaturet={unix_ts},v1={hmac_sha256_hex} using shopper_signing_secret
wp_user_id (required)Integer. Lifted from the signed shopper token on the chat side.
limit (optional)1–10, default 5
order_number (optional)Filter to a specific order number

Response

{
    "data": {
        "count": 2,
        "orders": [
            {
                "id": 1234,
                "number": "1234",
                "status": "processing",
                "total": "49.99",
                "currency": "USD",
                "date_created": "2026-05-12T18:42:00+00:00",
                "items": [
                    { "name": "Aurora Hoodie", "qty": 1, "sku": "AH-001", "total": "49.99" }
                ],
                "tracking_url": "https://aftership.com/track/...",
                "order_url": "https://shop.example.com/my-account/view-order/1234/"
            }
        ]
    }
}

The tracking URL is resolved from common postmeta keys — _aftership_tracking_url, _tracking_url, _st_tracking_link — in that order. The first non-empty value wins.


WooCommerce — coupons

Pitchbar handles coupons in two halves: emit + apply.

Emit

The plugin's CouponSyncer pushes the store's currently-valid coupons (up to 50, expired and exhausted dropped) to Pitchbar after every product sync. The agent's prompt fragment lists them so the LLM only ever offers codes that actually work.

When the agent decides to offer a coupon, it emits a <coupon/> block in chat. The widget renders a card with the code, a human-readable label (“15% off”, “$10 off your order”), and two buttons: Copy and Apply.

Apply

The Apply button hits the plugin's REST endpoint:

POST /wp-json/pitchbar/v1/cart/coupon

Validates the code with WC_Coupon; on success stages it in a 15-minute transient keyed by conversation id.

The plugin can't call $cart->apply_coupon() directly from a REST context because WC's cart machinery expects the cart-page session. Instead the plugin:

  1. Validates the code with new WC_Coupon($code) — invalid / expired codes return 400 immediately.
  2. Stages the code in a 15-minute transient pitchbar_pending_coupon_{conversation_id}.
  3. Sets a first-party pitchbar_conv_id cookie on the visitor's domain so the plugin can correlate the next page load.
  4. On the visitor's next cart load (woocommerce_load_cart_from_session), the plugin reads the cookie, looks up the staged code, and fires WC()->cart->apply_coupon($code). The transient is deleted after apply.
Why a transient? A cart-load hook is the only place WC's session, the cart object, and the visitor's identity line up. Staging the code and applying on the next cart load is how the visitor sees the discount actually take effect on their next checkout step.

WooCommerce — abandoned cart

The plugin enqueues a tiny front-end script (assets/cart-state.js) that mirrors WooCommerce's jQuery cart events into localStorage under the key pitchbar_cart_state:

{
    "items": 2,
    "timestamp": 1731600000000
}

The widget reads this state on every page load. When a cart sits idle longer than the threshold configured in the agent's behavior rules (admin UI exposes abandoned_cart as a trigger kind), the widget engages proactively with the configured message.


WooCommerce — lead mirroring

When the chat captures a lead (visitor fills the in-bar form), the Pitchbar server calls back to the plugin so the contact lands in WordPress without you leaving the admin.

POST /wp-json/pitchbar/v1/leads

Creates or updates a WP user mirroring the captured lead. Idempotent on email.

Behaviour


REST API reference

Two surfaces — Plugin → Pitchbar (outbound) and Pitchbar → Plugin (inbound). Each endpoint is authenticated separately.

Plugin → Pitchbar (outbound)

All outbound calls carry Authorization: Bearer {api_token} plus an HMAC X-Pitchbar-Signature header (using the same token as the shared secret). The signature payload is {unix_ts}.{body}; the header format is t={ts},v1={hmac_sha256_hex}. 5-minute timestamp window.

POST/api/v1/wp/handshake

Validates the API token, returns the workspace name, the agents in the workspace, the recommended site_type, and the shopper_signing_secret for callback auth.

POST/api/v1/wp/posts/sync

Bulk push of up to 50 posts per batch. Server returns counts of queued / skipped-unchanged rows.

POST/api/v1/wp/posts/changed

Single-post delta — action: "upsert" | "delete". Fires from save_post / trash hooks.

POST/api/v1/wp/products/sync

Bulk push of up to 50 WooCommerce products per batch.

POST/api/v1/wp/products/changed

Single-product delta from WC's woocommerce_new_product / update_product / delete_product / trash_product hooks.

POST/api/v1/wp/coupons/sync

Snapshot of currently-valid shop_coupon entries (up to 50). Posted automatically after every successful full product sync.

Pitchbar → Plugin (inbound)

Every inbound endpoint verifies an X-Pitchbar-Signature header against the locally-stored shopper_signing_secret. A missing or mismatched signature returns 401 with a clear error code (missing_signature, plugin_unconfigured, or signature_mismatch).

POST/wp-json/pitchbar/v1/leads

Creates / updates a WP user from a captured lead. Idempotent on email.

POST/wp-json/pitchbar/v1/orders/lookup

Returns the last N WooCommerce orders for wp_user_id with status, items, tracking URL, and view-order link.

POST/wp-json/pitchbar/v1/cart/coupon

Validates a coupon code with WC_Coupon; on success stages it in a 15-minute transient that applies on the visitor's next cart load.


Security model

The plugin and the Pitchbar server authenticate each other with two distinct credentials, in opposite directions:

DirectionCredentialNotes
Plugin → Pitchbar Bearer API token + HMAC body signature Token created in Settings → API tokens, scope wp:integration. Pitchbar stores only the SHA-256 hash of the plaintext. The plugin keeps the plaintext in wp_options — treat as a wp-config.php-level secret.
Pitchbar → Plugin HMAC-SHA256 signature using shopper_signing_secret The plugin receives this per-token secret in the handshake response and stores it locally. Pitchbar uses it to sign every callback (orders, coupons, leads, shopper token) so the WordPress REST endpoints verify the caller without ever holding the bearer plaintext.

Replay window

Both directions enforce a 5-minute timestamp window. Signatures older than 300 seconds are rejected. The window is short enough to stop replay attacks and wide enough to absorb clock drift between hosts.

Data minimization

WordPress capability gates


Filters & hooks reference

HookTypePurpose
pitchbar_post_content_html filter Post-process the rendered HTML before sync. Receives ($html, $post). Use to strip nav, redact blocks, force a builder template, etc.
pitchbar_run_full_sync_event action WP-Cron continuation for resumable post sync. Fires automatically; you don't call this directly.
pitchbar_run_product_sync_event action WP-Cron continuation for resumable product sync.

Troubleshooting

The widget doesn't appear on my site

  1. Open Settings → Pitchbar. Confirm Enabled is ticked.
  2. Click Test connection. If the agent dropdown is empty, the workspace has no agents — create one in your Pitchbar admin first.
  3. Confirm the current post type is in Post types. The widget is suppressed on singular pages of post types you haven't ticked.
  4. Disable any aggressive caching plugin temporarily (WP Rocket, W3 Total Cache) and reload. Cached HTML may not contain the <script> tag yet.
  5. View page source. Search for pitchbar — if the <script async> tag is present but the widget isn't, the issue is on the Pitchbar server side (open the network tab, look for the widget.js fetch).

“Connection failed” on Test connection

Click Show details to reveal the diagnostic block. See the status-code table in Connect to your workspace. Most common cause is a stale or wrong-scope API token (401 / 403) — reissue with the wp:integration scope.

“0 products, 0 queued” on product sync

A multilingual scope filter or subscription gate may be hiding products from wc_get_products. The plugin includes a fallback WP_Query path that fires automatically on page 1; check wp-content/debug.log for the warning ProductSyncer used fallback WP_Query. If the fallback also returns 0, the store has no published products.

Sync stuck mid-run

The plugin shows a “is finishing in the background” notice while a chunked sync is mid-run — this is expected on large sites. The WP-Cron continuation runs every 30 seconds until the catalog is fully synced. If the notice persists for more than 10 minutes:

  1. Confirm WP-Cron is firing on your host. If you've disabled it (DISABLE_WP_CRON in wp-config.php), you must set up a real cron hitting wp-cron.php.
  2. Run wp transient delete pitchbar_post_sync_resume (or the product variant) to clear the resume marker.
  3. Re-run “Sync now” manually.

HMAC signature mismatch

Inbound callbacks (orders / leads / coupons) check the signature against the local shopper_signing_secret. If this is out of sync (e.g. you regenerated the API token in Pitchbar without re-running Test connection), the plugin returns 401 with code signature_mismatch. Re-run Test connection from the Settings page — the new secret is delivered in the response and stored locally.

Widget mirrors the wrong direction on RTL

The plugin passes data-page-dir="rtl" when is_rtl() returns true. If you've set the WordPress locale to an RTL language but the theme overrides this with its own LTR rules, you may need to force RTL in WordPress general settings.

Posts with empty content fail with “content_html field is required”

Solved as of v2.0.3 — the plugin synthesises a body from title + excerpt + taxonomy terms when the_content collapses to empty. If you're seeing this on an older plugin version, upgrade to 2.0.3 or later.

WooCommerce column header renders one letter per line

Solved as of v2.0.4 — the Pitchbar admin column has a forced 110px width via admin_head CSS. Hard refresh the Products list page if you're still seeing the old behaviour.

Where do I see plugin logs?

Enable WP_DEBUG + WP_DEBUG_LOG in wp-config.php:

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);

Plugin warnings and informational messages land in wp-content/debug.log. Tail it after running a sync to see the per-batch diagnostics.


Changelog

2.0.4

2.0.3

2.0.2

2.0.1

2.0.0

1.2.0

1.1.0

1.0.1

1.0.0


Pitchbar for WordPress — built by the team behind Pitchbar.
GPLv2 or later. Documentation last updated for plugin v2.0.4.