Feedback on HubSpot Automation Workflow

Hello n8n community,

I am new to automation and built my first HubSpot workflow to manage a process from registration to payment. The core of this workflow relies on a custom dropdown property called “Statut dossier” (Case status); every webhook and step is driven by updating this central status.

I would love to get your feedback, optimization tips, or thoughts.

The workflow does:

  • Webhook triggers when a HubSpot event occurs like contact.creation (Form Submission) or contact.propertyChange.

  • Switch routes data based on the HubSpot subscription type like contact.creation or contact.propertyChange

  1. Contact Creation: A webhook captures the registration. HTTP Request to integrate a Basic LLM Chain (with Groq Chat Model and a Structured Output Parser) to automatically qualify the lead inside HubSpot.
  2. Quote Generation: Once the status switches to “A_DEVISER” (To Quote), the workflow generates a custom quote using Carbon API via HTTP requests and sends it via Gmail to client.
  3. Quote Validation : A webhook listens for the client’s signature. If he click on “Yes”, it check the status and updating HubSpot to “ACCEPTE”.
  4. Invoice: Once the client click on “Yes”, the webhook “HTTP Request_Facture” start and automatically generates the invoice PDF via Carbon API, and emails it to the client, moving the CRM status to “FACTURE_ENVOYEE” (Invoice Sent).

My questions for the community:

  • Are HTTP Request nodes the best approach ?
  • Should I split this into multiple sub-workflows? How do you know where is the best place to cut?
  • Any obvious design flaws or n8n features that could make this cleaner?

Thanks :slightly_smiling_face:

2 Likes

Your split point is probably where the workflow sends something outside HubSpot: quote email, signature callback, invoice email. Keeping Statut dossier as the state machine is fine, but those external sends need an idempotency check so a repeated contact.propertyChange does not send a second quote or invoice.

Before changing the whole shape, run one test: fire the same A_DEVISER event twice and see whether the workflow can prove the quote was already generated/sent. If that repeats, cut the quote step into its own sub-workflow first.

1 Like

The status-driven approach with a central HubSpot property is a solid design - it keeps the logic predictable and easy to extend. One thing worth adding as it grows: an Error handling sub-workflow connected to each main step so failed HTTP calls (Carbon API, Gmail) don’t silently drop. You can use n8n’s “On Error” output on any node and route it to a dedicated error handler that at minimum logs the failure and the input item that caused it.

1 Like

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).

1 Like

Thanks for the feedback @nodrel-dev @nguyenthieutoan and oimrqs_ops :grinning_face_with_smiling_eyes:
I didn’t realize how important saving n8n executions was. Letting every single contact.propertyChange trigger the workflow is wasteful.

I’m going to rethink the setup, split the external actions into sub-workflows, and look into idempotency, error handling, and V3 signature validation.

Relying on a single central status might not be the best idea if the workflow gets too bloated with filters right after, but I’m still learning

Thanks again for helping me!

The main thing I’d watch is that your HubSpot status property may be doing too much. It is triggering steps and also acting as the case state. I’d separate event handling from state transitions, add idempotency keys for webhooks, and log each status change so quote/payment edge cases are easier to debug. That matters a lot once signatures, invoices, and retries start overlapping.

1 Like