Nice build! This is a great first for a HubSpot workflow. Running everything off a single Statut dossier property is a solid pattern and it’ll hold up better than you’d think as you add steps. A few things to build on what’s already been said.
The biggest thing I haven’t seen mentioned is that your workflow writes Statut dossier while also being subscribed to contact.propertyChange, so every write you make fires the webhook again. If your Switch isn’t filtering on the incoming value, pushing the status to ACCEPTE can loop back into the workflow and kick off steps you didn’t mean to run. The event payload gives you propertyName and propertyValue, so branch on those and ignore any transition you don’t actually care about.
Something that caught me out early on is the webhook timeout, which is only 5 seconds, and HubSpot retries if you miss it. Your steps (the LLM qualification, Carbon PDF, Gmail send) are never finishing in 5s, and when you don’t respond in time HubSpot counts it as a failed delivery and resends, up to 10 times over about a day. That’s basically a recipe for duplicate quotes and invoices. Respond 200 right away and then do the actual work after, either from the Webhook node continuing the branch or by handing off to a sub-workflow. HubSpot documents it at https://developers.hubspot.com/docs/api-reference/webhooks-webhooks-v3/guide
That ties into the idempotency point @oimrqs_ops raised. The easiest fix is to write the proof back into HubSpot, like a quote_sent_at timestamp or an invoice_id, and only send if it’s empty. Your status property is already half a state machine, so you might as well let it carry the dedup too.
The one I’d really push you not to skip is validating the X-HubSpot-Signature-v3 header. A lot of people assume HTTPS already covers this, but TLS only guarantees the connection is encrypted and that the client reached the right server. It says nothing about who actually sent the request. Your webhook URL is just a public endpoint, so anyone who learns or guesses it can POST whatever JSON they want over perfectly valid HTTPS, including faked status changes that trip your quote and invoice logic. The signature is the only thing proving the request genuinely came from HubSpot (it’s an HMAC of the method, URL, body and timestamp signed with your app secret). I’d also reject anything where the timestamp is more than 5 minutes old to kill replays. The validation steps are at Webhooks | Validating Requests - HubSpot docs
On your actual questions. HTTP Request nodes are the right call for Carbon and Groq since there’s no native node for either. For the HubSpot reads and writes I’d switch to the native HubSpot node, which handles the private-app auth and scopes for you, and use the HubSpot Trigger node instead of a raw Webhook since it manages the subscription itself (HubSpot Trigger node documentation | n8n Docs).
For where to split, I’d cut at each external send (quote, signature callback, invoice) like @oimrqs_ops suggested. The nice side effect is that each one becomes its own execution, so when the invoice step breaks you can rerun just that from the executions list without re-qualifying the lead.
And +1 to @nguyenthieutoan on error handling. I’d just also set a global Error Workflow under Options > Settings so a failure in the trigger itself doesn’t slip through (Error handling | n8n Docs).