---
title: "HubSpot WooCommerce Integration: Create & Update Deals"
description: "Build a HubSpot WooCommerce integration that creates a deal on order placed and moves it to Closed Won on completion — REST API, Code Glue, 2026-03 endpoint."
url: "https://wpwebhooks.org/examples/hubspot-create-deal-woocommerce/"
date: "2026-05-15"
---

[WP Webhooks](/) / [Examples](/examples/)/ [HubSpot × WooCommerce](/examples/hubspot-woocommerce-integration/) / Create & Update Deals

/Example · HubSpot × WooCommerce

# HubSpot WooCommerce Integration: Create & Update Deals

Two webhooks, one cohesive flow. Create a HubSpot deal the instant a WooCommerce order is placed, then flip it to _Closed Won_ when the order completes — using the 2026-03 CRM API, Code Glue, and zero Zapier.

**~12 min read** May 15, 2026 Part **1** of 5

[](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwpwebhooks.org%2Fexamples%2Fhubspot-create-deal-woocommerce%2F "Share on LinkedIn")[](https://twitter.com/intent/tweet?url=https%3A%2F%2Fwpwebhooks.org%2Fexamples%2Fhubspot-create-deal-woocommerce%2F&text=HubSpot%20WooCommerce%20Integration%3A%20Create%20%26%20Update%20Deals "Share on X")

Filter OK Pro hubspotwoocommerce

TL;DR — What you'll build

-   **Webhook 1 — Create Deal:** fires on `woocommerce_checkout_order_created`, POSTs order data to HubSpot, gets back a deal ID
-   **Post-dispatch Code Glue:** stores the returned deal ID in WC order meta as `_hubspot_deal_id`
-   **Webhook 2 — Update Deal Stage:** fires on `woocommerce_order_status_changed`, PATCHes the deal to _Closed Won_ when the order completes
-   Full delivery log, retries, and replay — no Zapier, no extra SaaS

/ Prerequisites

## What You Need **Before Starting**

⚡

This integration uses [Webhook Actions by Flow Systems](/wordpress-webhook-plugin/) — a free WordPress webhook plugin. The deal-create half runs on the free plugin alone. The deal-stage update half uses the Pro plugin's **Code Glue** feature for storing the returned deal ID and injecting it into the next webhook's URL.

1.  1
    
    **WordPress + WooCommerce with at least one published product**
    
    Any WooCommerce-compatible theme. You need to be able to place a real test order through the storefront or REST API.
    
2.  2
    
    **Webhook Actions (free) — v1.12.0 or later**
    
    Search for **FlowSystems** in **Plugins → Add Plugin**. Install and activate.
    
3.  3
    
    **Webhook Actions Pro Pro — v1.1.0 or later**
    
    Required for Code Glue (PHP snippets that run pre/post dispatch) and `{{ field.path }}` URL template syntax.
    
4.  4
    
    **HubSpot Service Key or Private App access token**
    
    In HubSpot: **Settings → Integrations → Service Keys → Create service key**. Grant scopes `crm.objects.deals.read` and `crm.objects.deals.write`. Copy the token — starts with `pat-...`.
    
5.  5
    
    **Webhook Actions API token with `full` scope**
    
    In **WP Admin → Webhook Actions → API Tokens → Add Token**. Select scope `full`. Copy the token — shown only once.
    
    set shell variables
    
    ```
    export SITE="https://your-wordpress-site.com"
    export TOKEN="fswa_full_..."           # Webhook Actions API token (full scope)
    export HS_PAT="pat-eu1-xxxxxxxx..."     # HubSpot Private App token
    ```
    

/ Architecture

## Two Webhooks, **One Shared State**

A clean HubSpot WooCommerce integration needs two webhooks that share a single piece of state: the deal ID returned when the first webhook creates the deal. Without that shared state, the second webhook has nothing to PATCH against.

The plugin solves this with two Pro features working together: pre-dispatch Code Glue for payload shaping and URL template injection, and post-dispatch Code Glue for persisting the returned deal ID.

flow diagram

```
# 1. ORDER CREATED
WooCommerce order placed
  └── fires woocommerce_checkout_order_created
        └── Webhook #1 POST https://api.hubapi.com/crm/objects/2026-03/deals
              └── HubSpot responds 201 + {"id": "502475571425", ...}
                    └── post-glue: update_post_meta($order_id, "_hubspot_deal_id", "502475571425")

# 2. ORDER COMPLETED (minutes, hours, or days later)
Order status changed → "completed"
  └── fires woocommerce_order_status_changed
        └── pre-glue: read _hubspot_deal_id from order meta → inject as _hs_deal_id
              └── URL template {{ _hs_deal_id }} resolves → /crm/objects/2026-03/deals/502475571425
                    └── Webhook #2 PATCH with {"properties": {"dealstage": "closedwon"}}
                          └── HubSpot responds 200 — deal moved to Closed Won
```

**Hard rule for Code Glue.** Pre-dispatch glue is for _payload shaping only_. Never do side effects there — no `update_post_meta`, no outbound HTTP, no DB writes. Pre-glue can run multiple times (dispatch, retry, replay, preview). Reads like `get_post_meta` are fine. All write side effects go in post-dispatch glue.

FIG 01 — Deal create → Closed Won

/ Step 1

## Create the **"Deal Create"** Webhook

The first webhook fires on `woocommerce_checkout_order_created`. If your store uses the block-based checkout, add `woocommerce_store_api_checkout_order_processed` to the triggers array — both fire with the same payload shape.

We POST to HubSpot's new dated CRM endpoint: `https://api.hubapi.com/crm/objects/2026-03/deals`. The legacy `/crm/v3/objects/deals` path still works but HubSpot is migrating to dated paths — any new integration should start there.

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')
```

step 1.1 — create the webhook via REST

```
curl -sk -X POST "$SITE/wp-json/fswa/v1/webhooks" \
  -H "X-FSWA-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HS - Create Deal",
    "endpoint_url": "https://api.hubapi.com/crm/objects/2026-03/deals",
    "http_method": "POST",
    "auth_credential_id": '"$CRED_ID"',
    "is_enabled": true,
    "is_synchronous": true,
    "retry_limit": 1,
    "triggers": [
      "woocommerce_checkout_order_created",
      "woocommerce_store_api_checkout_order_processed"
    ]
  }'
```

response — 201 Created

```
{
  "id": "29",
  "webhook_uuid": "a8f1d3...",
  "name": "HS - Create Deal",
  "endpoint_url": "https://api.hubapi.com/crm/objects/2026-03/deals",
  "http_method": "POST",
  "is_synchronous": true,
  "triggers": [
    "woocommerce_checkout_order_created",
    "woocommerce_store_api_checkout_order_processed"
  ]
}
```

Webhook **ID 29** created. `is_synchronous: true` matters here — the next webhook depends on the deal ID stored by this one's post-glue, and async dispatch would race the order-status-changed trigger.

Why two triggers? You need both.

WooCommerce has two checkout types and each fires its own action: `woocommerce_checkout_order_created` (classic shortcode — still common on customised storefronts and B2B portals) and `woocommerce_store_api_checkout_order_processed` (block-based checkout — default in WooCommerce 8+ block themes). Each order fires exactly one of them. Registering on both makes the webhook source-agnostic. **Why not `woocommerce_new_order`?** It fires before line items and totals are populated — pre-glue would see an empty order.

**Two triggers means double the setup.** Conditions and Code Glue snippet bindings are per-(webhook, trigger). When you attach snippets in Step 2.3, you need to do it once per trigger — that's why the curl examples use a `for TRIGGER in …; do …; done` loop. Skip the loop and only one checkout type gets the glue.

Webhook Actions admin → Webhooks list with the new _HS - Create Deal_ webhook pointing at `/crm/objects/2026-03/deals`.

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

## Map WC Order → Deal, **Capture the Deal ID**

The raw WooCommerce trigger payload isn't shaped like a HubSpot deal — we need to map fields. And after HubSpot returns the new deal's ID, we need to store it on the order so the next webhook can find it. Two snippets: pre-glue for mapping, post-glue for persistence.

### 2.1 — Pre-dispatch: map order → deal properties

create the pre-dispatch snippet

```
curl -sk -X POST "$SITE/wp-json/fswa/v1/pro/snippets" \
  -H "X-FSWA-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HS - Map WC Order to Deal",
    "code": "<PHP body below>"
  }'
```

snippet code — PHP

```
// Pre-dispatch glue — payload shaping only. NO side effects.
// Input: $payload, $args, $webhook, $trigger

$order   = $payload["args"][0] ?? [];
$orderId = $order["id"] ?? null;
if (!$orderId) return $payload;

$billing = $order["billing"] ?? [];
$name    = trim(($billing["first_name"] ?? "") . " " . ($billing["last_name"] ?? ""));

return [
    "_wc_order_id" => (string) $orderId,
    "properties"   => [
        "dealname"  => "WC Order #$orderId — $name",
        "amount"    => (string) ($order["total"] ?? 0),
        "dealstage" => "appointmentscheduled",
        "pipeline"  => "default",
        "closedate" => date("c", strtotime("+30 days")),
    ],
];
```

**About `_wc_order_id`:** we add it at the root of the returned array, alongside `properties`. HubSpot ignores unknown root-level keys, but the same payload object is what post-glue receives — so we can read the order ID back without re-parsing args. A clean way to carry context across pre and post.

### 2.2 — Post-dispatch: store the returned deal ID

Post-dispatch glue is where side effects belong. It fires _after_ the HTTP response comes back, with the response body and status code available. We parse the JSON, pull the `id`, and write it to WC order meta as `_hubspot_deal_id`.

create the post-dispatch snippet

```
curl -sk -X POST "$SITE/wp-json/fswa/v1/pro/snippets" \
  -H "X-FSWA-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HS - Store Deal ID in WC Meta",
    "code": "<PHP body below>"
  }'
```

snippet code — PHP

```
// Post-dispatch glue — runs AFTER the HTTP response. Side effects belong here.
// Available: $payload, $responseCode, $responseBody, $webhook, $trigger

$data    = json_decode($responseBody, true);
$orderId = (int) ($payload["_wc_order_id"] ?? 0);

if ((int)$responseCode === 201 && !empty($data["id"]) && $orderId) {
    $order = wc_get_order($orderId);
    if ($order) {   // HPOS-safe: use $order->update_meta_data, not update_post_meta
        $order->update_meta_data("_hubspot_deal_id", $data["id"]);
        $order->save();
    }
}
```

**HPOS heads-up.** WooCommerce 8+ stores order meta in `wp_wc_orders_meta`, not `wp_postmeta`. Using `update_post_meta($orderId, ...)` writes to the legacy table and is invisible to `$order->get_meta()`. Always use `wc_get_order()->update_meta_data()` + `$order->save()` for WC orders.

### 2.3 — Attach both snippets to the webhook + trigger

Replace `$PRE_ID` and `$POST_ID` with the snippet IDs returned above. Attach to both checkout triggers.

attach pre + post glue to both triggers

```
for TRIGGER in woocommerce_checkout_order_created woocommerce_store_api_checkout_order_processed; do
  curl -sk -X POST \
    "$SITE/wp-json/fswa/v1/pro/trigger-snippets/29/trigger/$TRIGGER" \
    -H "X-FSWA-Token: $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"pre_snippet_id": '"$PRE_ID"', "pre_enabled": true, "post_snippet_id": '"$POST_ID"', "post_enabled": true}'
done
```

Webhook Actions admin → Code Glue with both snippets attached (pre-dispatch mapping + post-dispatch persistence).

/ Step 3 Filter OK fswa\_webhook\_urlfswa\_webhook\_payload Pro

## Create the **"Deal Stage Update"** Webhook

The second webhook fires on `woocommerce_order_status_changed`. The URL template feature embeds the HubSpot deal ID directly in the endpoint URL — Pro's URL filter expands `{{ _hs_deal_id }}` at dispatch time, after pre-glue has populated it from order meta.

step 3.1 — create the update webhook

```
curl -sk -X POST "$SITE/wp-json/fswa/v1/webhooks" \
  -H "X-FSWA-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HS - Update Deal Stage",
    "endpoint_url": "https://api.hubapi.com/crm/objects/2026-03/deals/{{ _hs_deal_id }}",
    "http_method": "PATCH",
    "auth_credential_id": '"$CRED_ID"',
    "is_enabled": true,
    "is_synchronous": true,
    "retry_limit": 1,
    "triggers": ["woocommerce_order_status_changed"]
  }'
```

response — webhook ID 30

```
{
  "id": "30",
  "name": "HS - Update Deal Stage",
  "endpoint_url": "https://api.hubapi.com/crm/objects/2026-03/deals/{{ _hs_deal_id }}",
  "http_method": "PATCH",
  "triggers": ["woocommerce_order_status_changed"]
}
```

**If you already set up this integration before May 2026** and your existing webhook 30 points at the v3 endpoint, update it with a single PATCH — no other changes needed:

migrate legacy v3 endpoint → 2026-03

```
curl -sk -X PATCH "$SITE/wp-json/fswa/v1/webhooks/30" \
  -H "X-FSWA-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"endpoint_url": "https://api.hubapi.com/crm/objects/2026-03/deals/{{ _hs_deal_id }}"}'
```

Webhook Actions admin → webhook 30 with the 2026-03 endpoint and dynamic `{{ _hs_deal_id }}` URL template.

### 3.2 — Condition: only fire when order moves to "completed"

`woocommerce_order_status_changed` fires on every status transition. We only want to flip the deal when the new status is `completed`:

add condition: new\_status equals completed

```
curl -sk -X POST \
  "$SITE/wp-json/fswa/v1/schemas/webhook/30/trigger/woocommerce_order_status_changed" \
  -H "X-FSWA-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "conditions": {
      "enabled": true,
      "type": "and",
      "rules": [{
        "field": "args.2",
        "operator": "equals",
        "value": "completed",
        "cast": "string"
      }]
    },
    "conditions_evaluate_on": "original"
  }'
```

### 3.3 — Pre-glue: inject the deal ID for the URL template

Pre-glue reads `_hubspot_deal_id` from order meta and returns it as `_hs_deal_id` for the URL filter to consume, plus the actual PATCH body in `properties`.

create the update pre-dispatch snippet

```
curl -sk -X POST "$SITE/wp-json/fswa/v1/pro/snippets" \
  -H "X-FSWA-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HS - Inject Deal ID for URL + Stage Update",
    "code": "<PHP body below>"
  }'
```

snippet code — PHP

```
// Pre-dispatch glue — payload shaping only.
// Reads _hubspot_deal_id stored by Step 2 post-glue (local read, allowed).

$orderId = (int) ($payload["args"][0] ?? 0);
$dealId  = $orderId ? (string) get_post_meta($orderId, "_hubspot_deal_id", true) : "";

return [
    "_hs_deal_id" => $dealId,   // consumed by URL template, not sent to HubSpot
    "properties"  => [
        "dealstage" => "closedwon",
    ],
];
```

**Why root-level keys like `_hs_deal_id` are safe.** HubSpot's deals PATCH endpoint reads `properties` and `associations` at the body root and silently ignores everything else. Root-level keys act as a private channel for Webhook Actions features without polluting the HubSpot payload.

attach pre-glue (no post-glue needed for this webhook)

```
curl -sk -X POST \
  "$SITE/wp-json/fswa/v1/pro/trigger-snippets/30/trigger/woocommerce_order_status_changed" \
  -H "X-FSWA-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pre_snippet_id": '"$UPDATE_PRE_ID"', "pre_enabled": true, "post_enabled": false}'
```

/ Step 4

## End-to-End Test — **Real Order, Real Deal**

Place a real test order through your WooCommerce storefront. The instant the order is created, the first webhook fires and creates a HubSpot deal. Post-glue writes `_hubspot_deal_id` to the order, and HubSpot now shows a deal in _Appointment Scheduled_.

HubSpot → CRM → Deals — the WooCommerce order has just created a deal in _Appointment Scheduled_.

Now mark the order **Completed** in **WP Admin → WooCommerce → Orders**. The second webhook fires immediately — pre-glue reads `_hubspot_deal_id` from order meta, the URL template expands to `/crm/objects/2026-03/deals/502475571425`, and HubSpot PATCHes the deal to `closedwon`.

HubSpot → CRM → Deals — the same deal, now in _Closed Won_, after the order was marked completed.

Webhook Actions admin → Logs — both deliveries succeed: 201 from _Create Deal_, 200 from _Update Deal Stage_.

**That's the full integration.** Every WooCommerce order now creates a HubSpot deal automatically, and every completed order flips that deal to Closed Won. Failures are visible in the log, can be replayed with one click, and the per-webhook delivery view shows exactly what payload was sent and what HubSpot returned.

/ Common Pitfalls

## Things **That Bite**

**Wrong endpoint version.** Older HubSpot docs use `/crm/v3/objects/deals`. That still works, but mixed-version setups confuse maintainers. Standardise on `/crm/objects/2026-03/deals` for any new webhook.

**Pipeline / dealstage internal IDs.** HubSpot's UI shows "Closed Won" — the API needs the internal ID. For the default pipeline: `appointmentscheduled`, `closedwon`, `closedlost`. For custom pipelines, fetch them via `GET /crm/v3/pipelines/deals`.

**Side effects in pre-glue.** The hard rule: pre-dispatch glue must be pure. If you put `update_post_meta` in pre-glue, every retry doubles the meta write. Move it to post-glue.

**Async dispatch racing the next trigger.** If `is_synchronous` is `false` on webhook 29, post-glue may not have finished writing the deal ID before the status-changed trigger fires. Keep webhook 29 synchronous.

**Missing condition on webhook 30.** Without the `args.2 = completed` condition, webhook 30 PATCHes on every status change — hitting HubSpot three times per order. The condition keeps it single-fire.

/ HubSpot × WooCommerce Series

← Previous (Series start) [All 5 parts Series Index](/examples/hubspot-woocommerce-integration/) [Next → Part 2 — Sync Products to HubSpot](/examples/hubspot-product-sync-woocommerce/)

/Notes

→ [WordPress Webhook REST API](/webhook-wordpress-plugin-api/) — full reference for webhooks, schemas, conditions, and logs

→ [Webhook Actions by Flow Systems](/wordpress-webhook-plugin/) — plugin overview

→ [HubSpot Deals API — create-deal reference (2026-03)](https://developers.hubspot.com/docs/api-reference/latest/crm/objects/deals/create-deal)

→ [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.

Does this HubSpot WordPress plugin work without writing code? +

The free plugin sets up the deal-create webhook with zero code — REST API or admin UI. The deal-stage update half needs Code Glue: two short PHP snippets pasted into the plugin admin, not custom theme code. If you prefer pure no-code, the series hub covers examples that are pure REST.

How is this different from the official HubSpot WooCommerce integration? +

HubSpot's official integration pushes a fixed schema you don't control. This setup lets you decide exactly which order fields become which deal properties, on which trigger, with which conditions — plus a full delivery log with replay. Less out-of-the-box, more precision.

Why use the new 2026-03 HubSpot endpoint instead of /crm/v3/objects/deals? +

HubSpot's 2026-03 dated API endpoint is the current versioned path. The /crm/v3/objects/deals path still works but HubSpot is migrating examples to the dated paths. If your existing webhook still uses v3, update endpoint\_url via a single PATCH — auth header, condition, and pre-glue stay identical.

What happens if the Create Deal webhook fails — do we still try to update? +

No. The post-dispatch Code Glue only writes \_hubspot\_deal\_id to WC order meta on a 201 response. The Update Deal Stage webhook depends on that meta via the URL template — if empty, the URL is malformed and HubSpot returns 400, visible in the log. Add an is\_not\_empty condition on webhook 30 to skip cleanly.

Can I do this on the free plugin without Code Glue? +

The deal-create half — yes, fully free. The deal-stage update needs the HubSpot deal ID from the create response. Without Code Glue you can hook fswa\_webhook\_url and fswa\_glue\_post\_dispatch in your own mu-plugin. The Pro snippet-based flow is recommended because snippets are portable, editable in admin, and visible to non-developers.

Do I need to install this on every WordPress site that runs WooCommerce? +

Yes — Webhook Actions is a per-site plugin. If you run 5 stores, each gets its own install and API tokens. The Pro license covers unlimited sites, and webhooks/snippets can be exported and re-imported via JSON export/import in the admin.

/More examples

## Related integrations.

[

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/)[

Part 2 of 5

HubSpot WooCommerce Product Sync

Push WC products to HubSpot on publish (POST), keep them in sync on update (PATCH).

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

Tutorial

Ask Claude Code to Set Up a WooCommerce Webhook

A real Claude Code session — REST API discovery, payload-path correction, and a live fix before shipping.

Read →](/examples/woocommerce-order-webhook-claude-code/)

## Structured data

```json
{"@context":"https://schema.org","@type":"Article","headline":"HubSpot WooCommerce Integration: Create & Update Deals from Orders","description":"Build a HubSpot WooCommerce integration that creates a deal on order placed and moves it to Closed Won on completion — REST API, Code Glue, 2026-03 endpoint.","datePublished":"2026-05-15","dateModified":"2026-05-15","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/hubspot-create-deal-woocommerce/","image":{"@type":"ImageObject","url":"https://wpwebhooks.org/examples/hubspot-woocommerce-integration/og_image.jpg"},"keywords":["hubspot woocommerce integration","hubspot woocommerce","woocommerce hubspot integration","hubspot wordpress plugin","hubspot crm wordpress","hubspot deal woocommerce","woocommerce order to hubspot"],"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":"Does this HubSpot WordPress plugin work without writing code?","acceptedAnswer":{"@type":"Answer","text":"The free plugin sets up the deal-create webhook with zero code — REST API or admin UI. The deal-stage update half needs Code Glue: two short PHP snippets pasted into the plugin admin, not custom theme code. If you prefer pure no-code, the series hub covers examples that are pure REST."}},{"@type":"Question","name":"How is this different from the official HubSpot WooCommerce integration?","acceptedAnswer":{"@type":"Answer","text":"HubSpot's official integration pushes a fixed schema you don't control. This setup lets you decide exactly which order fields become which deal properties, on which trigger, with which conditions — plus a full delivery log with replay. Less out-of-the-box, more precision."}},{"@type":"Question","name":"Why use the new 2026-03 HubSpot endpoint instead of /crm/v3/objects/deals?","acceptedAnswer":{"@type":"Answer","text":"HubSpot's 2026-03 dated API endpoint is the current versioned path. The /crm/v3/objects/deals path still works but HubSpot is migrating examples to the dated paths. If your existing webhook still uses v3, update endpoint_url via a single PATCH — auth header, condition, and pre-glue stay identical."}},{"@type":"Question","name":"What happens if the Create Deal webhook fails — do we still try to update?","acceptedAnswer":{"@type":"Answer","text":"No. The post-dispatch Code Glue only writes _hubspot_deal_id to WC order meta on a 201 response. The Update Deal Stage webhook depends on that meta via the URL template — if empty, the URL is malformed and HubSpot returns 400, visible in the log. Add an is_not_empty condition on webhook 30 to skip cleanly."}},{"@type":"Question","name":"Can I do this on the free plugin without Code Glue?","acceptedAnswer":{"@type":"Answer","text":"The deal-create half — yes, fully free. The deal-stage update needs the HubSpot deal ID from the create response. Without Code Glue you can hook fswa_webhook_url and fswa_glue_post_dispatch in your own mu-plugin. The Pro snippet-based flow is recommended because snippets are portable, editable in admin, and visible to non-developers."}},{"@type":"Question","name":"Do I need to install this on every WordPress site that runs WooCommerce?","acceptedAnswer":{"@type":"Answer","text":"Yes — Webhook Actions is a per-site plugin. If you run 5 stores, each gets its own install and API tokens. The Pro license covers unlimited sites, and webhooks/snippets can be exported and re-imported via JSON export/import in the admin."}}]}

{"@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":"Create & Update Deals","item":"https://wpwebhooks.org/examples/hubspot-create-deal-woocommerce/"}]}

{"@context":"https://schema.org","@type":"HowTo","name":"How to Build a HubSpot WooCommerce Integration That Creates and Updates Deals","description":"Set up two webhooks — one creates a HubSpot deal on WooCommerce order creation, the other moves the deal to Closed Won when the order is completed — using the Webhook Actions plugin and HubSpot's 2026-03 CRM API.","step":[{"@type":"HowToStep","name":"Prepare HubSpot and Webhook Actions credentials","text":"Create a HubSpot Private App with crm.objects.deals.write scope. Create an Webhook Actions API token with full scope in the WordPress admin."},{"@type":"HowToStep","name":"Create the Deal Create webhook","text":"POST to /wp-json/fswa/v1/webhooks with endpoint https://api.hubapi.com/crm/objects/2026-03/deals on the woocommerce_checkout_order_created trigger."},{"@type":"HowToStep","name":"Add Pro Code Glue to capture the deal ID","text":"Create a pre-dispatch snippet that maps WC order to HubSpot deal properties, and a post-dispatch snippet that stores the returned deal ID in WC order meta."},{"@type":"HowToStep","name":"Create the Deal Stage Update webhook","text":"POST to /wp-json/fswa/v1/webhooks with endpoint https://api.hubapi.com/crm/objects/2026-03/deals/{{ _hs_deal_id }} on the woocommerce_order_status_changed trigger, with PATCH method and a condition on status=completed."},{"@type":"HowToStep","name":"End-to-end test","text":"Place a real order through WooCommerce, watch the Webhook Actions log show 201 from Create Deal, then mark the order Completed and watch the second webhook return 200 from the 2026-03 deals endpoint."}]}
```
