Pular para o conteúdo principal

Automating HubSpot with Webhooks, Workflows, and Custom Code

Automating HubSpot with Webhooks, Workflows, Code

When people talk about automation in HubSpot, most think of the visual workflows, the ones marketing builds by dragging blocks on screen. And they are great for a lot. But there is a floor above, where automation meets the API, and that is where the processes that truly scale live: reacting to events in real time, creating flows by code, running custom logic inside them.

That territory is more powerful and also more treacherous. A poorly handled webhook processes the same event twice. A workflow created via API misses a detail nobody warned you about. A code action blows its execution time at the worst moment. In this guide, I will organize the three fronts of automation by API, webhooks, workflows, and custom code, and show the precautions that separate an automation that sleeps soundly from one that wakes the team at night.

Workflows via the Automation v4 API

Creating a workflow via API is a POST to the Automation v4 API, where you define the flow type, the enrollment criteria, and the sequence of actions. For contact-based workflows you set type to CONTACT_FLOW, and PLATFORM_FLOW for the others. A healthy pattern is to create the flow disabled, with isEnabled set to false, and turn it on manually in the interface only after reviewing that it does what it should. This prevents a freshly created flow from enrolling records before you check the logic. Note that the v4 Automation API is currently in beta and requires the automation scope.

POST /automation/v4/flows

{

"isEnabled": false,

"flowType": "WORKFLOW",

"type": "CONTACT_FLOW",

"objectTypeId": "0-1",

"name": "Mark contact as nurtured",

"actions": [ ... ],

"enrollmentCriteria": {

"type": "EVENT_BASED",

"eventFilterBranches": [ ... ]

}

}

A common misconception, and one worth correcting, is that via API you can only create filter-based enrollment. You can create both. Filter-based criteria use property value and list membership, and event-based criteria use eventFilterBranches with unified events, for example enrolling a contact when they submit a specific form. Because the payloads are detailed and the API is in beta, many teams still assemble the flow structure via API and fine-tune the most complex triggers in the interface, but the event trigger itself is creatable via API.

Webhooks: reacting to events in real time

Webhooks are how HubSpot notifies your integration the instant something changes: a contact created, a deal that changed stage, an updated property. Instead of you asking HubSpot from time to time whether something changed, it tells you. This makes automation reactive and nearly instant, which is exactly what you want for time-sensitive processes, like routing a hot lead to the right rep on the spot.

The central concern with webhooks is idempotency, and it is not optional. The same event can be delivered more than once, due to retries or network instability. If your integration is not prepared for this, it will process the same change twice, creating duplicates or firing the same email again. Use the event identifier to recognize and ignore repeated deliveries, respond fast so HubSpot does not pile up retries, and validate the HubSpot request signature, the X-HubSpot-Signature header computed with your app's client secret, before processing anything.

Custom code inside the workflow

The custom code action is the bridge between the simplicity of the visual workflow and the flexibility of programming. It runs inside the flow, receives data from the enrolled record, uses secrets to store credentials securely, and can call external APIs. It is perfect for that logic the ready-made blocks do not cover, like calculating a value from complex rules or querying an outside system.

But it has limits that must be respected. There is a ceiling on execution time and memory, and actions that take too long or consume too much simply fail. The rule is to keep the code short, predictable, and focused. When the logic starts getting heavy, hitting the limits or depending on many external steps, it is a sign that it should leave the workflow and become a dedicated external integration, with more control and without the code action's constraints.

Retries and dead-letter queues

Event-driven integrations fail now and then, it is in their nature. An external call that does not respond, a destination system down for a minute. The robust pattern to handle this is to retry with exponential backoff: tried and failed, wait a bit and try again, increasing the interval on each failure. This solves transient failures, which are the majority.

For failures that persist after a few attempts, the dead-letter queue comes in. Instead of simply losing the event or retrying forever, you send it to a separate queue, where it is logged for manual reprocessing later. That way, no event is lost in silence, and you have visibility of what failed and why. An automation without a dead-letter queue is an automation that loses data without anyone knowing.

A tip from someone who has been burned: treat every webhook as if it will arrive twice, because one day it will. Idempotency is not an engineering luxury, it is what keeps your automation from sending the same email twice or creating the same record in duplicate. Storing the id of the already-processed event and ignoring it the second time solves most cases.

App webhooks vs workflow notifications

There is more than one way for HubSpot to notify you about a change, and choosing the right one avoids headaches. App webhooks belong to an app and are the robust path for reacting to CRM events at scale, with fine control over which events you want to hear. In a legacy private app, you configure the webhook subscriptions in the app settings, in the interface; in an app on the new developer platform, you configure them as code, where webhooks v3 works with a static token and v4 with OAuth. An account service key cannot do webhooks. The webhook action inside a workflow is a different, simpler thing: a step that sends an outbound call to an external system at one point of the flow.

The choice depends on reach. If you want to hear every contact creation in the portal, an app webhook is the place. If you want to notify a system only when a specific deal reaches a specific stage, the webhook action in the workflow solves it with less effort. Mixing the two without criteria is what creates that situation where nobody knows anymore where each notification comes from.

Best practices that make automation predictable

Beyond the specific precautions, a few general practices separate a mature automation from an improvised one. Log every processed event, with the identifier and the result, so you can investigate when something goes wrong. Respond to the webhook as fast as possible, doing the heavy processing asynchronously afterward, so you do not hold the connection open and trigger retries. And version your automations as code, reviewing changes before they reach production.

These practices do not show up when everything works, but they are exactly what saves the operation when something breaks. An automation with clear logs and asynchronous processing is debuggable in minutes. One without them is a black box nobody understands under pressure. The cost of adopting these practices is low, and the return shows up the first time you resolve an incident quickly instead of spending the night hunting.

Why this matters for your operation

Well-built automation is what lets an operation grow without bloating the team in the same proportion. A lead that arrives at night and gets routed on the spot, a deal that changes stage and triggers the next action on its own, data that updates across all systems with nobody typing anything. These gains only hold if the automation is reliable. An automation that processes events in duplicate or loses messages in silence is worse than no automation, because it creates a false sense of control.

That is why the technical precautions, idempotency, retries, dead-letter, are not fussiness. They are what makes the automation trustworthy enough for the operation to rely on it to make decisions and treat customers. The difference between an automation that scales and one that becomes a source of rework lies precisely in these details nobody sees when everything works.

In practice: the email that went out twice

An operation had an automation that, now and then, sent the same email to the same contact twice, which drew complaints and gave an impression of disorganization. The cause was a webhook with no idempotency handling: when HubSpot resent an event, the integration processed it again from scratch, as if it were the first time.

The fix was to store the identifier of each already-processed event and ignore repeated deliveries. The duplicate emails stopped immediately. The same pattern was extended to all of the operation's webhooks, and as a bonus the team gained visibility of the events that were failing, which used to simply vanish. A small technical detail solved a problem customers were noticing.

A reflection for operation leaders: reliable automation is the kind you forget exists, because it simply works, day after day, without duplicating or losing anything. That quiet is not luck, it is the result of idempotency, retries, and a dead-letter queue designed in from the start. The cost of doing this right is low, and the return is an operation that scales without becoming a constant source of small fires.

Checklist of a reliable automation

  1. Do workflows created via API start disabled, for review before turning on?
  2. Are the enrollment criteria set via API, filter-based or event-based, and reviewed before enabling?
  3. Does every webhook handle idempotency, ignoring repeated deliveries by event id?
  4. Does the webhook endpoint validate the HubSpot signature before processing?
  5. Do custom code actions respect the time and memory limits?
  6. Is there exponential backoff on retries and a dead-letter queue for persistent failures?

Frequently asked questions

Can I create event-based workflow triggers via the API?

Yes. The v4 Automation API supports both filter-based enrollment (property value, list membership) and event-based enrollment via eventFilterBranches with unified events, for example enrolling a contact on a form submission. The API is in beta and the payloads are detailed, so some teams still finish the most complex triggers in the interface, but the event trigger itself is creatable via API.

Why handle idempotency in webhooks?

Because the same event can be delivered more than once. Using the event id to ignore duplicates avoids processing the same change twice, which would cause repeated emails or duplicate records.

When should I move custom code logic out?

When it hits the action's time or memory limits, gets too complex to maintain inside the workflow, or depends on many external steps. Then a dedicated external integration is safer.

What is a dead-letter queue?

It is a separate queue for events that failed even after a few attempts. Instead of losing the event, you log it for manual reprocessing, ensuring visibility and that nothing is lost in silence.

How do I protect my webhook endpoint?

By validating the HubSpot request signature, the X-HubSpot-Signature header computed with your app's client secret, before processing anything, to be sure the call really came from HubSpot, and responding fast so the platform does not pile up retries.

Should I create the workflow enabled or disabled via API?

Disabled, with isEnabled set to false. That way you review the logic in the interface before the flow starts enrolling records, avoiding surprises in production.

Want automations that react to events without losing a message or processing duplicates? At Insight Sales we design resilient flows, webhooks, and queues in HubSpot. Talk to our team and find out how we can help.

Ready to take your operation to the next level?

Talk to a specialist and see how we can help.

paper-plane