API Rate Limiting During Initial Workflow Enrollment

bryce-corey
Participant

Hey All,

 

I'm running into an issue, wondering if anyone has had the same experience, and could share if they found a solution!

 

When making API calls within the Custom Code Action in a workflow, I am running into an API throttling issue (“You have reached your secondly limit”) when a large number of contacts are enrolled at the same time in the workflow. This seems to be due to all of the contacts going through the custom code in their workflow at the same time, and because the API calls are being made using the same HAPIKEY, HubSpot is throttling the amount of calls being made.

 

For example, I enabled a workflow that enrolled 5,000 contacts automatically, and 4,000 custom code actions failed because of the rate-limiting. Luckily, I was able to remove the API call from that specific workflow by handling some logic outside of the custom code. However, the next plan we have is to format phone numbers on our contacts. Since we have to retrieve the phone number through an API call within the custom code and we will be enrolling around 10,000 contacts, we cannot avoid the problem this time around.

 

My initial thought was to set some sort of delay within the workflow, but I realized that they would all have the same delay and then run at the same time, which would defeat the purpose. The same goes for setting a timeout in the JS within the custom code. I’m a little lost at where to go from here and am afraid we might have to enroll them in different segments.

 

If you have any ideas of how to get around this, please let me know!

4 Accepted solutions
spotthehub
Solution
Participant

Hah @bryce-corey I had the exact same issue today and tried a JS timer as well.  Did you end up finding a solution?  Support suggested throwing an error instead of catching the error, suggesting that would cause the workflow job instance to rerun. 

View solution in original post

0 Upvotes
KimM
Solution
Top Contributor

Bit late to the game here but thought I'd add my insight on how I handled this. I used a combination of different approaches throttle the API calls.

 

Exactly what has been previously mentioned, it's important to throw the error in your catch statement. This just means that hubspot will continue to retry this action until it succeeds. Some people on the discussion boards suggest that the retries will only happen certain number of times, but the documentation doesn't clearly state it and I haven't come across it yet.

 

I also implemented a hacky solution using the 'Rotate Leads to Owner' workflow action, an owner property I use exclusively for this purpose, and the built-in delay function.

Check my response out here for more details: https://community.hubspot.com/t5/9881-Operations-Hub/How-many-contacts-go-through-a-workflow-concurr...

View solution in original post

0 Upvotes
Unmatched
Solution
Participant | Diamond Partner
Participant | Diamond Partner

Hey! We've built an app that will be able to help you with this. It's called Time Turner, and it allows you to throttle your HubSpot workflows and drip-feed your emails out over time. 

 

Let me know what you think? 

 

Here's the install link for Time Turner!

View solution in original post

0 Upvotes
RubenBurdin
Solution
Top Contributor

Hi @bryce-corey  , that “everything enrolls at once and hammers the API” pain is real.

 

In 2025 The cleanest fix inside HubSpot is to let the workflow’s Custom Code action retry for you and add jitter so calls don’t land in the same second. In Node, don’t swallow the 429.

 

Throw it so HubSpot retries the step automatically; the docs are explicit about this for both Node and Python (https://developers.hubspot.com/docs/api-reference/automation-actions-v4-v4/custom-code-actions )

 

Pair that with a small randomized delay before each outbound call so 10k enrollments don’t align. Even 0–9 seconds of jitter usually collapses collisions nicely.

 

Outside the code, stagger the firehose. A simple way is to pre-slice your audience into static lists and trigger the workflow list by list on a schedule, or rotate to a temporary “throttle owner” and gate each branch with a short delay so only a fraction progresses at any moment. That keeps concurrency under control without changing your business logic

 

One clarifier to size the throttle: are you calling HubSpot APIs, external APIs, or both from the custom code?

Two extra considerations. First, honor any Retry-After header you get on 429s and back off progressively; the built-in retries do their part, but a bit of exponential backoff in your code is cheap insurance

 

Second, if you’re authenticating as a public app, note HubSpot’s burst limit is now 110 requests per 10 seconds, not 100, which gives a touch more headroom but still demands batching and jitter

(https://developers.hubspot.com/changelog/increasing-our-api-limits )

 

Hope this helps. If consistency between HubSpot and your finance or ops system is the gap, Stacksync keeps them mirrored as changes happen.

 
 
Did my answer help? Please mark it as a solution to help others find it too.

Ruben Burdin Ruben Burdin
HubSpot Advisor
Founder @ Stacksync
Real-Time Data Sync between any CRM and Database
Stacksync Banner

View solution in original post

0 Upvotes
8 Replies 8
RubenBurdin
Solution
Top Contributor

Hi @bryce-corey  , that “everything enrolls at once and hammers the API” pain is real.

 

In 2025 The cleanest fix inside HubSpot is to let the workflow’s Custom Code action retry for you and add jitter so calls don’t land in the same second. In Node, don’t swallow the 429.

 

Throw it so HubSpot retries the step automatically; the docs are explicit about this for both Node and Python (https://developers.hubspot.com/docs/api-reference/automation-actions-v4-v4/custom-code-actions )

 

Pair that with a small randomized delay before each outbound call so 10k enrollments don’t align. Even 0–9 seconds of jitter usually collapses collisions nicely.

 

Outside the code, stagger the firehose. A simple way is to pre-slice your audience into static lists and trigger the workflow list by list on a schedule, or rotate to a temporary “throttle owner” and gate each branch with a short delay so only a fraction progresses at any moment. That keeps concurrency under control without changing your business logic

 

One clarifier to size the throttle: are you calling HubSpot APIs, external APIs, or both from the custom code?

Two extra considerations. First, honor any Retry-After header you get on 429s and back off progressively; the built-in retries do their part, but a bit of exponential backoff in your code is cheap insurance

 

Second, if you’re authenticating as a public app, note HubSpot’s burst limit is now 110 requests per 10 seconds, not 100, which gives a touch more headroom but still demands batching and jitter

(https://developers.hubspot.com/changelog/increasing-our-api-limits )

 

Hope this helps. If consistency between HubSpot and your finance or ops system is the gap, Stacksync keeps them mirrored as changes happen.

 
 
Did my answer help? Please mark it as a solution to help others find it too.

Ruben Burdin Ruben Burdin
HubSpot Advisor
Founder @ Stacksync
Real-Time Data Sync between any CRM and Database
Stacksync Banner
0 Upvotes
Unmatched
Solution
Participant | Diamond Partner
Participant | Diamond Partner

Hey! We've built an app that will be able to help you with this. It's called Time Turner, and it allows you to throttle your HubSpot workflows and drip-feed your emails out over time. 

 

Let me know what you think? 

 

Here's the install link for Time Turner!

0 Upvotes
KimM
Solution
Top Contributor

Bit late to the game here but thought I'd add my insight on how I handled this. I used a combination of different approaches throttle the API calls.

 

Exactly what has been previously mentioned, it's important to throw the error in your catch statement. This just means that hubspot will continue to retry this action until it succeeds. Some people on the discussion boards suggest that the retries will only happen certain number of times, but the documentation doesn't clearly state it and I haven't come across it yet.

 

I also implemented a hacky solution using the 'Rotate Leads to Owner' workflow action, an owner property I use exclusively for this purpose, and the built-in delay function.

Check my response out here for more details: https://community.hubspot.com/t5/9881-Operations-Hub/How-many-contacts-go-through-a-workflow-concurr...

0 Upvotes
spotthehub
Participant

So when opening the workflow I have nothing in the retries section: 

spotthehub_0-1632418690356.png

 

0 Upvotes
spotthehub
Solution
Participant

Hah @bryce-corey I had the exact same issue today and tried a JS timer as well.  Did you end up finding a solution?  Support suggested throwing an error instead of catching the error, suggesting that would cause the workflow job instance to rerun. 

0 Upvotes
OTanti
Participant | Platinum Partner
Participant | Platinum Partner

I createa a nodejs code snippet to create a random delay of between 0 and 9 seconds before the code snippet which sends API calls. In case this helps:

 

 

 

exports.main = async (event, callback) => {

  // Function to create a random delay between 0 seconds (0 ms) and 9 seoncds (9000 ms)
  const randomDelay = () => {
    const min = 0; // 0 milliseconds
    const max = 9000; // 9 seconds in milliseconds
    return Math.floor(Math.random() * (max - min + 1)) + min;
  };

  // Delay execution
  await new Promise(resolve => setTimeout(resolve, randomDelay()));

  /*****
    Use the callback function to output data that can be used in later actions in your workflow.
  *****/
  callback({
    outputFields: {
    }
  });
};

 

 

 



0 Upvotes
bryce-corey
Participant

Hey there @spotthehub! Thanks for the reply, I should've updated this with the solution. Support was correct with their suggestion, once I stopped catching the error, and started letting it raise to the top of the stack, the custom code action was able to retry due to the exception. 

0 Upvotes
spotthehub
Participant

Thanks for the reply @bryce-corey ... Is this how you implemented the throw?

 

It's not super clear when the retries are happening, etc. 

 

try {

// custom code here

} catch (err) {
console.log(err);
throw err;
};

 

 

0 Upvotes