---
title: "WooCommerce HubSpot Integration: Sync Order Line Items to Deals"
description: "Chain-trigger every WooCommerce order to a HubSpot line_items batch with inline deal associations — full graph in a single outbound POST, every hop logged and replayable."
url: "https://wpwebhooks.org/examples/woocommerce-order-line-items-hubspot/"
date: "2026-05-15"
---

[WP Webhooks](/) / [Examples](/examples/)/ [HubSpot × WooCommerce](/examples/hubspot-woocommerce-integration/) / Line Items

/Example · HubSpot × WooCommerce

# WooCommerce HubSpot Integration: Sync Order Line Items to Deals

Builds on Parts 1 + 2. When the deal-create webhook returns `201`, Webhook Actions's **Webhook Chains** feature fires one more webhook that POSTs every line item to HubSpot's `/line_items/batch/create` endpoint — with the deal↔line\_item associations attached inline on each input, so the whole graph lands in a single HubSpot call. Each hop is a real webhook with its own log, retry, conditions, and replay — no `wp_remote_post` hidden inside post-glue.

**~14 min read** May 15, 2026 Part **3** of 5

[](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwpwebhooks.org%2Fexamples%2Fwoocommerce-order-line-items-hubspot%2F "Share on LinkedIn")[](https://twitter.com/intent/tweet?url=https%3A%2F%2Fwpwebhooks.org%2Fexamples%2Fwoocommerce-order-line-items-hubspot%2F&text=WooCommerce%20HubSpot%20Integration%3A%20Sync%20Order%20Line%20Items%20to%20Deals "Share on X")

Filter OK fswa\_webhook\_payloadfswa\_glue\_post\_dispatch Pro hubspotwoocommerceline-itemsassociations

TL;DR — What you'll build

-   **Webhook 1 — HS - Create Deal (from Part 1):** stays unchanged. On `woocommerce_checkout_order_created` it POSTs the deal, post-glue persists `_hubspot_deal_id`.
-   **Webhook 2 — HS - Batch Line Items (NEW, chain target):** fires on the deal-create's 2xx response. Pre-glue reshapes `args[0]` (deal id + WC line\_items + product meta) into HubSpot's batch body where each input carries its own `associations` array pointing at the deal — one POST to `/crm/objects/2026-03/line_items/batch/create` creates the line items AND wires them to the deal. Post-glue stores the returned line\_item IDs in WC order meta.
-   **One chain link, full graph.** A 20-item cart is one outbound batch instead of 40 sub-calls. Inline `associations` on the batch create endpoint removes the need for a separate `/v4/associations/.../batch/create` hop.
-   **Resolve `hs_product_id`** from WC product meta written by Part 2 inside the batch pre-glue — fall back to free-text line items for products not yet synced.

FIG 01 — Order → deal → line-items chain

/ Prerequisites

## What You Need **Before Starting**

⚡

Part 3 is the first example in the series that _requires_ the previous two. The post-glue assumes (a) a HubSpot deal ID has been written by Part 1's create flow and (b) WC products carry `_hubspot_product_id` from Part 2. Run them in order — or use the Backfill section below to retrofit Part 3 onto existing orders.

1.  1
    
    **Part 1 complete: deal-create webhook + post-glue persisting `_hubspot_deal_id`**
    
    Verify by inspecting a recent test order's post meta — `_hubspot_deal_id` should hold a HubSpot record ID.
    
2.  2
    
    **Part 2 complete: product sync + post-glue persisting `_hubspot_product_id`**
    
    Verify by inspecting a recent product's post meta. If you skipped Part 2, line items will still post but without `hs_product_id` association.
    
3.  3
    
    **HubSpot scopes added: `crm.objects.line_items.write` + `crm.objects.line_items.read`**
    
    Edit the same Private App from Parts 1+2. After saving, copy the new token if HubSpot regenerated it.
    
4.  4
    
    **Shell env from previous parts**
    
    Re-export if needed.
    
    re-export if needed
    
    ```
    export SITE="https://your-wordpress-site.com"
    export TOKEN="fswa_full_..."
    export HS_PAT="pat-eu1-xxxxxxxx..."
    ```
    

/ Architecture

## One WC Event, **One Chain Link**

Before Webhook Actions 1.13.0 this article shipped with a post-glue loop that called `wp_remote_post` N times per order — functional, but every sub-call was invisible to the Webhook Actions log, with no retries, no conditions, no replay button per item. Webhook Chains replace that pattern: a webhook completing successfully (2xx) becomes the trigger for the next webhook in the chain, which receives the upstream response, the payload sent, and the pre-mapping original payload as its starting `args[0]`.

For line items we collapse the loop into a single **batch** call. HubSpot's `/crm/objects/2026-03/line_items/batch/create` takes up to 100 inputs per request, and each input can carry an `associations` array that wires the new line\_item to existing records (the deal we just created) at creation time. Two chained webhooks, two log rows, full graph in HubSpot — even a 20-item cart is one outbound deal POST + one outbound line-items batch.

flow diagram

```
# PART 1 (unchanged): create the deal
WooCommerce order placed
  └── fires woocommerce_checkout_order_created
        └── Webhook #31 POST /crm/objects/2026-03/deals → 201 deal_id=502475571425
              └── post-glue (Part 1): persist _hubspot_deal_id to WC order meta
              └── 2xx → fswa_glue_post_dispatch → ChainDispatcher fires next link

# PART 3 (NEW): one batch webhook, chain-triggered, with inline associations
                    └── Webhook #53 HS - Batch Line Items (chain target, trigger fswa_chain_link:N)
                          └── pre-glue: build {inputs:[{properties, associations:[deal]}, ...]}
                          └── POST /crm/objects/2026-03/line_items/batch/create → 201 results[3 ids, all associated to the deal]
                                └── post-glue: persist _hubspot_line_item_ids to WC order meta
                                └── done. Full graph in HubSpot.
```

**Why this beats the old loop:** filter Logs by _HS - Order Line Items_ chain and you see all rows for a single order, in order, with their own retry/replay buttons. If the association batch fails because HubSpot rate-limited mid-burst, you replay one row — not the entire deal-create flow with its double-line-item risk.

/ Step 1 Filter OK fswa\_webhook\_payloadfswa\_glue\_post\_dispatch Pro

## Webhook 2 — **HS - Batch Line Items**

Create the first chain target: a webhook with no WP-hook triggers, configured for HubSpot's line\_items batch endpoint. It will be wired as the chain target of Webhook 31 (_HS - Create Deal_) from Part 1, so it fires automatically on every deal-create success.

### 1.1 — Create the webhook via REST

First, store the HubSpot PAT once in the plugin's **Credentials Vault** — webhooks then reference it by id via `auth_credential_id`, which takes precedence over the legacy plaintext `auth_header`. The secret is encrypted at rest and write-only over the API: it never shows up in webhook configs, logs, or agent responses.

one-time — store the PAT in the Credentials Vault

```
CRED_ID=$(curl -sk -X POST "$SITE/wp-json/fswa/v1/credentials" \
  -H "X-FSWA-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "HubSpot PAT", "type": "bearer", "secret": "'"$HS_PAT"'"}' | jq -r '.id')
```

create webhook 53 — no WP triggers (chain-only)

```
curl -sk -X POST "$SITE/wp-json/fswa/v1/webhooks" \
  -H "X-FSWA-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HS - Batch Line Items",
    "endpoint_url": "https://api.hubapi.com/crm/objects/2026-03/line_items/batch/create",
    "http_method": "POST",
    "auth_credential_id": '"$CRED_ID"',
    "is_enabled": true,
    "is_synchronous": false,
    "retry_limit": 2,
    "triggers": []
  }'
```

response — 200 OK

```
{
  "id": "53",
  "webhook_uuid": "b41fc8...",
  "name": "HS - Batch Line Items",
  "endpoint_url": "https://api.hubapi.com/crm/objects/2026-03/line_items/batch/create",
  "triggers": []
}
```

Empty `triggers: []` is allowed in v1.13.0+ — the webhook will be triggered exclusively by the chain link we add in step 1.4. Without a chain link this webhook is dormant.

### 1.2 — Pre-dispatch Code Glue: build the batch inputs

The chain target receives the upstream deal-create response and the original WC payload as `args[0]`. The pre-glue's job is to reshape that into HubSpot's batch body: `{ inputs: [{properties: {...}}, ...] }`. **Payload shaping only — no outbound HTTP, no DB writes here.**

snippet code — pre-dispatch glue

```
// Read chain args. $payload is the chain payload built by ChainDispatcher.
$chain   = $payload["args"][0] ?? [];
$dealId  = (string) ($chain["response"]["body"]["id"] ?? "");

// Recover the WC order id. Resolution order:
//   1) _wc_order_id if Part 1's pre-glue stashed it in the sent payload
//   2) payload.args[0] — woocommerce_checkout_order_created passes the order_id scalar
//   3) original_payload.args[0] — same data, set when mapping is applied
$wcOrderId = (int) (
    $chain["payload"]["_wc_order_id"]
    ?? $chain["payload"]["args"][0]
    ?? $chain["original_payload"]["args"][0]
    ?? 0
);

if (!$dealId || !$wcOrderId) return ["inputs" => []];

$order = wc_get_order($wcOrderId);
if (!$order) return ["inputs" => []];

$inputs    = [];
$wcItemIds = []; // parallel array so post-glue can map result[i] back to a WC order item id

foreach ($order->get_items() as $itemId => $item) {
    $wcProductId = $item->get_variation_id() ?: $item->get_product_id();
    $hsProductId = (string) get_post_meta($wcProductId, "_hubspot_product_id", true);

    $props = [
        "name"     => $item->get_name(),
        "quantity" => (string) $item->get_quantity(),
        "price"    => (string) ($item->get_total() / max(1, $item->get_quantity())),
        "amount"   => (string) $item->get_total(),
    ];
    if ($hsProductId) {
        $props["hs_product_id"] = $hsProductId;
    }

    // Inline association: line_item → deal (HUBSPOT_DEFINED type id 20).
    // HubSpot creates the line item AND wires it to the deal in this same batch call.
    $inputs[] = [
        "properties"   => $props,
        "associations" => [[
            "to"    => ["id" => $dealId],
            "types" => [[
                "associationCategory" => "HUBSPOT_DEFINED",
                "associationTypeId"   => 20,  // line_item_to_deal
            ]],
        ]],
    ];
    $wcItemIds[] = (int) $itemId;
}

// Side-channel: HubSpot ignores unknown top-level fields on this endpoint.
// Post-glue reads these to map result[i].id back to the corresponding WC order item.
return [
    "inputs"       => $inputs,
    "_wc_order_id" => $wcOrderId,
    "_wc_item_ids" => $wcItemIds,
];
```

**HubSpot only reads `inputs` from the body** and ignores extra top-level fields like `_wc_order_id` / `_wc_item_ids`. We use those fields as a side-channel for the post-glue — they show up in the Webhook Actions log's sent-payload column but never reach HubSpot's parser. If your downstream API is strict, use Webhook Actions's _excluded paths_ field-mapping option to drop them before send.

### 1.3 — Post-dispatch Code Glue: persist the line\_item ids

snippet code — post-dispatch glue

```
$body    = json_decode($responseBody, true);
$results = $body["results"] ?? [];

// Side-channel data stashed by pre-glue.
$orderId   = (int) ($payload["_wc_order_id"] ?? 0);
$wcItemIds = (array) ($payload["_wc_item_ids"] ?? []);

if ($responseCode < 200 || $responseCode >= 300 || !$orderId) return;

$order = wc_get_order($orderId);
if (!$order) return;

$liIds = [];

// HubSpot returns results in the same order we sent inputs — we can zip by index.
foreach ($results as $idx => $r) {
    $liId = (string) ($r["id"] ?? "");
    if (!$liId) continue;
    $liIds[] = $liId;

    // Per-WC-order-item meta — survives refunds + per-line edits + downstream sync.
    $wcItemId = (int) ($wcItemIds[$idx] ?? 0);
    if ($wcItemId) {
        wc_update_order_item_meta($wcItemId, "_hubspot_line_item_id", $liId);
    }
}

// Order-level rollup for quick lookups + replay guards.
$order->update_meta_data("_hubspot_line_item_ids", $liIds);
$order->save();
```

**Per-order-item meta vs order meta.** Storing the HubSpot id on each WC order item (`wc_update_order_item_meta`, written to `wp_woocommerce_order_itemmeta`) makes per-line operations cheap later: refund a single item and sync just that refund to HubSpot; edit a line quantity and PATCH only the matching `hs_line_item`; report mismatches per row. The order-level `_hubspot_line_item_ids` array is the fast rollup for "does this order already have line items synced?" replay guards.

### 1.4 — Attach both snippets, then wire as chain target

Save the two snippets in the Webhook Actions snippet library, attach them to webhook 53 (no trigger needed — chains use the synthetic `fswa_chain_link:N` trigger which gets bound when you link the webhooks). Then in the admin: edit webhook 53, toggle _"Use other Webhooks as triggers"_, pick the existing chain (or create one named _"HS - Order Line Items"_) and select **Webhook 31 (HS - Create Deal)** as the source.

Or wire it via REST:

create the chain (if it doesn't exist)

```
curl -sk -X POST "$SITE/wp-json/fswa/v1/chains" \
  -H "X-FSWA-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "HS - Order Line Items", "description": "Deal create → batch line items → batch associations"}'
# → {"id":"10","name":"HS - Order Line Items",...}
```

link webhook 31 → webhook 53

```
curl -sk -X POST "$SITE/wp-json/fswa/v1/chains/10/links" \
  -H "X-FSWA-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"source_webhook_id": 31, "target_webhook_id": 53}'
```

**Why `is_synchronous: false`?** The deal-create webhook stays synchronous (Part 1 needs the response inline). Chain targets fire on the queue by default — if HubSpot rate-limits the line\_items batch or returns a 5xx, retries happen in the background without blocking the WC checkout response. Customer hits the thank-you page in milliseconds; line items land in HubSpot within seconds.

**When to flip to sync.** Set `is_synchronous: true` on this chain target when (a) you have a post-checkout thank-you page that reads HubSpot data directly, (b) an internal CRM-driven email / Slack notification fires next and needs the full graph immediately, or (c) the store is internal / B2B where checkout latency doesn't matter. Cost: ~150–400ms HubSpot round-trip added to the checkout response, plus checkout blocks if HubSpot has an outage. For most public storefronts, async is the safer default.

/ Step 2

## End-to-End Test — **Three-Line Order**

Place a test order with three different products in the cart. The deal-create webhook (Part 1) fires once and returns the deal ID. On its 2xx response the chain dispatcher fires the line-items batch webhook, which POSTs all three line items + their deal associations in a single call.

Open HubSpot → CRM → Deals → (your test deal). Scroll to the **Line items** section — you should see all three products listed with their names, quantities, and total amounts. Each `hs_product_id` link should resolve to the matching HubSpot product from Part 2.

HubSpot → Deal detail → Line items card showing all three products from the WooCommerce order with quantities and amounts, each associated to the deal inline via the batch/create endpoint.

**Verify in Webhook Actions.** Filter the Logs view by chain _HS - Order Line Items_: you should see exactly two rows for the test order — one `woocommerce_checkout_order_created` dispatch (the deal create, status `success`, 201) and one `fswa_chain_link:N` dispatch (the line-items batch, status `success`, 201) sharing the same event UUID.

**Verify in WC.** Inspect the order's meta — `_hubspot_deal_id` holds the deal ID, `_hubspot_line_item_ids` holds the array of HubSpot line\_item record IDs. Each WC order item also carries an individual `_hubspot_line_item_id` in `wp_woocommerce_order_itemmeta`:

wp-cli inspection

```
wp wc shell <<'PHP'
$order = wc_get_order($ORDER_ID);
echo "deal_id: " . $order->get_meta("_hubspot_deal_id") . PHP_EOL;
echo "line_item_ids: " . json_encode($order->get_meta("_hubspot_line_item_ids")) . PHP_EOL;
foreach ($order->get_items() as $itemId => $item) {
    echo $itemId . " → " . wc_get_order_item_meta($itemId, "_hubspot_line_item_id") . PHP_EOL;
}
PHP
# deal_id: 502475571425
# line_item_ids: ["18923771401","18923771402","18923771403"]
# 9201 → 18923771401
# 9202 → 18923771402
# 9203 → 18923771403
```

Webhook Actions admin → Logs filtered by chain _HS - Order Line Items_. Deal-create row (201) and the line-items batch chain-link row (201) for the same order, with the humanized _Chain HS - Order Line Items ← HS - Create Deal_ pill on the chain-link row.

/ Backfill

## Retrofit **Existing Orders**

If you already have orders with HubSpot deals but no line items, run a one-off backfill that calls the same post-glue logic for each existing order:

wp-cli backfill

```
wp eval '
$orders = wc_get_orders(["limit" => -1, "status" => ["processing","completed"], "return" => "ids"]);
foreach ($orders as $oid) {
    $dealId = get_post_meta($oid, "_hubspot_deal_id", true);
    $existingLi = get_post_meta($oid, "_hubspot_line_item_ids", true);
    if (!$dealId || $existingLi) continue;   // skip orders without deal or already done

    // Simulate the post-glue context: payload, responseCode, responseBody
    do_action("fswa_glue_backfill_line_items", $oid, $dealId);
    echo "backfilled order $oid (deal $dealId)
";
}'
```

Bind `fswa_glue_backfill_line_items` in your mu-plugin to the same code as the Part 3 block above (extracted to a function). This keeps the line-item loop testable in isolation and reusable from both the live post-glue and the WP-CLI backfill.

/ Common Pitfalls

## Things **That Bite**

**100 inputs per batch limit.** HubSpot's `/line_items/batch/create` accepts up to 100 inputs per request. A single cart over 100 items would need chunking — rare for B2C but possible for B2B order forms. The pre-glue can chunk using `array_chunk` — but then the batch endpoint only takes one chunk per dispatch, so keep the cart-size assumption documented and add a fallback warning if `count($inputs) > 100`.

**Missing `hs_product_id`.** If a product was created in WC before Part 2 ran, no `_hubspot_product_id` meta exists. The pre-glue falls back to creating a free-text line item — visible in HubSpot but with no SKU link. Run the Part 2 backfill first to fix it across the catalogue.

**Association type id 20 (line\_item\_to\_deal) vs 19 (deal\_to\_line\_item).** The direction matters in v4 associations — we create the line\_item, so the edge is FROM line\_item TO deal, which is HUBSPOT\_DEFINED type `20`. Reading associations on the deal returns type `19` (the inverse). Mixing them gets you a 400 from HubSpot. Look up the canonical id at `/crm/v4/associations/line_items/deals/labels`.

**Currency precision.** Some HubSpot line\_item reports round to two decimals while WC stores totals at higher precision. The pre-glue casts to string before sending; HubSpot accepts string numerics for currency properties.

**Replay doubles line items.** The chain target webhook is _not_ idempotent by default. Replaying the deal-create from the Webhook Actions log re-fires the chain and creates a second set of line items. Two guard strategies: (a) add a condition on the chain link "skip if order already has `_hubspot_line_item_ids`", or (b) add a check at the top of the pre-glue: `if ($order->get_meta("_hubspot_line_item_ids")) return ["inputs" => []];` — an empty inputs array is a no-op POST that HubSpot accepts and the chain still completes cleanly.

/ HubSpot × WooCommerce Series

[← Previous Part 2 — Product Sync](/examples/hubspot-product-sync-woocommerce/) [All 5 parts Series Index](/examples/hubspot-woocommerce-integration/) [Next → Part 4 — Customer → Contact](/examples/add-customer-to-hubspot-on-woocommerce-order/)

/Notes

→ [HubSpot Line Items — create-line-item (2026-03)](https://developers.hubspot.com/docs/api-reference/latest/crm/objects/line-items/create-line-item)

→ [HubSpot Line Items — update-line-item (2026-03)](https://developers.hubspot.com/docs/api-reference/latest/crm/objects/line-items/update-line-item)

→ [HubSpot Line Items — get-line-items (2026-03)](https://developers.hubspot.com/docs/api-reference/latest/crm/objects/line-items/get-line-items)

→ [HubSpot Associations API](https://developers.hubspot.com/docs/api-reference/latest/crm/associations/overview)

→ [All WordPress webhook automation examples](/examples/)

/FAQ

## Common questions always ask.

Don't see yours? Open an issue on GitHub or check the full reference in the API docs.

Why a chain webhook instead of a post-dispatch loop? +

Before Webhook Actions 1.13.0 this article used a post-glue loop calling wp\_remote\_post for each line item — functional but every sub-call was invisible to the Webhook Actions log, with no per-item retry or replay. Webhook Chains (v1.13.0+) make each downstream call a real webhook: its own log row, its own retry policy, its own replay button. Filter logs by chain to see all hops for one order side by side.

What if a WC product wasn't synced to HubSpot first (Part 2)? +

The pre-glue checks for \_hubspot\_product\_id; if missing, it falls back to creating a free-text line item with just name + price + quantity (no hs\_product\_id association). You lose SKU-level reporting but the deal still has line items attached. Run the Part 2 backfill to retrofit.

Does this need Pro? +

Chains themselves are free in Webhook Actions v1.13.0+. Pro makes the pre-glue and post-glue payload shaping easy: snippets live in the admin and attach per webhook+trigger. The free-plugin alternative is to hook fswa\_webhook\_payload (pre-dispatch shape) and fswa\_glue\_post\_dispatch (post-dispatch persist) in your own mu-plugin — same hooks under the hood.

Why inline associations on batch create instead of a second association webhook? +

HubSpot's /crm/objects/2026-03/line\_items/batch/create accepts an associations array per input that wires the new line\_item to existing records at creation time. That collapses what used to be two HubSpot round-trips into one. For line\_item to deal we use HUBSPOT\_DEFINED type id 20 (line\_item\_to\_deal direction).

Will replaying the chain double the line items? +

Yes by default. Guards: (a) attach a per-trigger condition to the synthetic fswa\_chain\_link:N trigger saying 'skip if order has \_hubspot\_line\_item\_ids', or (b) add a pre-glue idempotency check that returns inputs:\[\] when the order is already synced. An empty-inputs POST is a no-op HubSpot accepts gracefully.

/More examples

## Related integrations.

[

Part 1

Create & Update Deals from WC Orders

The deal-create webhook this part extends with the line-items post-glue.

Read →](/examples/hubspot-create-deal-woocommerce/)[

Part 2

WooCommerce Product Sync to HubSpot

Stores \_hubspot\_product\_id on each WC product, which Part 3 reads for line-item associations.

Read →](/examples/hubspot-product-sync-woocommerce/)[

Series Hub

HubSpot × WooCommerce — All 5 Parts

The complete integration series — deals, products, line items, customers, and the full graph.

Read →](/examples/hubspot-woocommerce-integration/)

## Structured data

```json
{"@context":"https://schema.org","@type":"Article","headline":"WooCommerce HubSpot Integration: Sync Order Line Items to Deals","description":"Chain-trigger every WC order to a HubSpot line_items batch with inline deal associations — full graph in a single outbound POST, every hop logged and replayable.","datePublished":"2026-05-15","dateModified":"2026-05-19","author":{"@type":"Person","name":"Mateusz Skorupa","url":"https://wpwebhooks.org/about/"},"publisher":{"@type":"Organization","name":"WP Webhooks","url":"https://wpwebhooks.org","sameAs":["https://flowsystems.pl"]},"url":"https://wpwebhooks.org/examples/woocommerce-order-line-items-hubspot/","image":{"@type":"ImageObject","url":"https://wpwebhooks.org/examples/hubspot-woocommerce-integration/og_image.jpg"},"keywords":["woocommerce hubspot integration","hubspot line items woocommerce","woocommerce order line items hubspot","hubspot wordpress plugin"],"isPartOf":{"@type":"CreativeWorkSeries","name":"HubSpot × WooCommerce Integration Series","url":"https://wpwebhooks.org/examples/hubspot-woocommerce-integration/"}}

{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{"@type":"Question","name":"Why a chain webhook instead of a post-dispatch loop?","acceptedAnswer":{"@type":"Answer","text":"Before Webhook Actions 1.13.0 this article used a post-glue loop calling wp_remote_post for each line item — functional but every sub-call was invisible to the Webhook Actions log, with no per-item retry or replay. Webhook Chains (v1.13.0+) make each downstream call a real webhook: its own log row, its own retry policy, its own replay button. Filter logs by chain to see all hops for one order side by side."}},{"@type":"Question","name":"What if a WC product wasn't synced to HubSpot first (Part 2)?","acceptedAnswer":{"@type":"Answer","text":"The pre-glue checks for _hubspot_product_id; if missing, it falls back to creating a free-text line item with just name + price + quantity (no hs_product_id association). You lose SKU-level reporting but the deal still has line items attached. Run the Part 2 backfill to retrofit."}},{"@type":"Question","name":"Does this need Pro?","acceptedAnswer":{"@type":"Answer","text":"Chains themselves are free in Webhook Actions v1.13.0+. Pro makes the pre-glue and post-glue payload shaping easy: snippets live in the admin and attach per webhook+trigger. The free-plugin alternative is to hook fswa_webhook_payload (pre-dispatch shape) and fswa_glue_post_dispatch (post-dispatch persist) in your own mu-plugin — same hooks under the hood."}},{"@type":"Question","name":"Why inline associations on batch create instead of a second association webhook?","acceptedAnswer":{"@type":"Answer","text":"HubSpot's /crm/objects/2026-03/line_items/batch/create accepts an associations array per input that wires the new line_item to existing records at creation time. That collapses what used to be two HubSpot round-trips into one. For line_item to deal we use HUBSPOT_DEFINED type id 20 (line_item_to_deal direction)."}},{"@type":"Question","name":"Will replaying the chain double the line items?","acceptedAnswer":{"@type":"Answer","text":"Yes by default. Guards: (a) attach a per-trigger condition to the synthetic fswa_chain_link:N trigger saying 'skip if order has _hubspot_line_item_ids', or (b) add a pre-glue idempotency check that returns inputs:[] when the order is already synced. An empty-inputs POST is a no-op HubSpot accepts gracefully."}}]}

{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://wpwebhooks.org/"},{"@type":"ListItem","position":2,"name":"Examples","item":"https://wpwebhooks.org/examples/"},{"@type":"ListItem","position":3,"name":"HubSpot × WooCommerce","item":"https://wpwebhooks.org/examples/hubspot-woocommerce-integration/"},{"@type":"ListItem","position":4,"name":"Line Items","item":"https://wpwebhooks.org/examples/woocommerce-order-line-items-hubspot/"}]}
```
