HubSpot Custom Code Actions: Secrets, Limits, and Retries
Custom code actions are the escape hatch of HubSpot workflows: when the native actions can't express your logic, you drop in a block of JavaScript or Python and the workflow runs it for every enrolled record. They are also where automations quietly break in production, because three things about them are widely misunderstood: how secrets actually reach your code, what the execution limits really are, and what HubSpot does (and doesn't do) when your code fails. This guide covers the three, with the exact numbers and the patterns we use after years of building and rescuing these actions.
One prerequisite before anything: custom code actions require Operations Hub Professional or Enterprise. If the action isn't showing up in your workflow editor, that's the first thing to check, not your code.
What a custom code action can (and can't) do
A custom code action is a small serverless function that HubSpot executes inside a workflow. You get the enrolled record's data as input, you can call any API (HubSpot's or external), and you can return output fields that later workflow actions consume. The environment in practice:
- Languages: Node.js or Python, chosen per action in the editor.
- Preloaded libraries: the essentials come ready, including
axiosand the official@hubspot/api-clienton Node (andrequestsplus the HubSpot client on Python). The full, current library list with versions lives in the official custom code documentation; you can't npm-install anything else, so plan around what's provided. - Stateless execution: nothing persists between runs. No global variables, no local files, no memory of the previous record. State lives in CRM properties or external systems, period.
- Output fields: the values you return become available to subsequent actions (branches, copies to properties), which is how custom code composes with the rest of the workflow.
The right mental model: custom code actions are for surgical logic on one record at a time: scoring with business rules, formatting and validation, calling an external API for enrichment, creating an associated object with logic native actions can't handle. They are not a data pipeline. For heavy or long-running jobs, orchestrate outside (a queue or an automation platform like n8n) and let the workflow trigger it; our guide on integrating HubSpot with n8n covers exactly that architecture.
Secrets: doing credentials right (and the gotcha that returns undefined)
Secrets are HubSpot's mechanism for keeping credentials out of your code. You create them in the custom code action editor, and they arrive at runtime as environment variables: process.env.MY_SECRET in Node, os.getenv('MY_SECRET') in Python. Three rules keep this clean:
- Never hardcode a token in the code block. The code is visible to every user who can edit workflows, it gets cloned when workflows are cloned, and rotating a hardcoded token means hunting through every action that pasted it. Secrets centralize that.
- Select the secret in each action that uses it. This is the classic gotcha: creating the secret is not enough. Each custom code action has its own list of selected secrets, and a secret you didn't add to this action simply doesn't exist in its environment. If
process.env.MY_SECRETcomes backundefinedand you're sure the secret exists, this is almost always why. - Scope tokens to least privilege. The private app token you store as a secret should carry only the scopes that action needs. If your calls start failing with 403s after a scope change, our deep-dive on the HubSpot API missing scopes error walks through the diagnosis, and the foundations are in our guide to HubSpot API authentication and app setup.
On rotation: when you update a secret's value, expect a short propagation window before running actions pick it up. Rotate outside business-critical windows, and if you're invalidating the old token at the provider, leave both valid during the switchover.
The limits that shape your architecture
The numbers that matter, confirmed in HubSpot's documentation and repeatedly in the HubSpot Community:
- 20 seconds of execution time. Hard limit, no extension available. That budget includes cold start, all your API calls, and their network latency.
- 128 MB of memory. Enough for record-level logic, not for loading large datasets into memory.
- Log output truncates around 4 KB.
console.logof a large object will be cut off exactly where you needed to read it. Log selectively: IDs, status codes, decision points.
These limits aren't arbitrary; they're telling you what the tool is for. The architectural consequences:
- Set explicit timeouts on every HTTP call. axios defaults to no timeout, so one slow third-party API eats your entire 20-second budget and the action dies without a useful error. Something like
axios.get(url, { timeout: 5000 })turns a mystery timeout into a catchable, loggable failure. - One record, few calls. If the logic needs data from many objects, fetch with batch endpoints (the CRM API's batch reads exist for this) rather than looping single requests.
- Heavy work goes elsewhere. Syncs, mass updates, and multi-minute jobs belong in an external worker triggered by a webhook from the workflow, with results written back to the CRM. The custom code action is the dispatcher, not the engine.
Retries: what HubSpot does for you, and what it expects from you

This is the least understood part of custom code actions, and the one that causes real production incidents. The behavior, per HubSpot's documentation:
- If your code throws on a rate limit (429) or a server error (5xx), HubSpot automatically retries the action, starting about a minute after the failure and continuing with backoff for up to three days. The enrolled record waits at that step until the retry succeeds or the window expires.
- Any other unhandled exception fails the action, and depending on your workflow settings the record either stops there or continues down the flow with no output from your code.
Which leads to the pattern the community converged on, and the one we ship: throw what should be retried, catch what shouldn't. Let 429s and 5xx errors propagate so HubSpot's retry machinery does its job; catch business-logic failures (a 404 for a record that doesn't exist, a validation error) and handle them explicitly, logging and returning a status output field your workflow can branch on.
The corollary that bites teams: if your action can be retried, it must be safe to run twice. A retry after a partial failure re-executes the whole function. If the code had already created an invoice, sent an email, or incremented a counter before dying, the retry does it again. Make actions idempotent: check whether the deal already has the associated invoice before creating it, use external IDs or search-before-create, and design writes so that running the same input twice produces the same result once.
A tip from someone who has been burned: we were once called to debug "random duplicate invoices" that turned out to be a perfectly healthy retry system doing its job on a non-idempotent action. The code created the invoice, then timed out calling a slow ERP, then threw; HubSpot dutifully retried, and created the invoice again. The fix wasn't in the retry settings, it was three lines that searched for an existing invoice by deal ID first. Write every custom code action as if it will run twice, because one day it will.
A production checklist for custom code actions
- Secrets created and selected in this action, never hardcoded, scoped to least privilege.
- Explicit timeout on every outbound HTTP call, well under the 20-second budget.
- Throw on 429/5xx (let HubSpot retry), catch and branch on business errors.
- Idempotent writes: search-before-create, external IDs, no unconditional side effects.
- Logs limited to IDs, status codes, and decisions (remember the ~4 KB truncation).
- Output fields for everything downstream actions need, including an explicit status.
- Heavy jobs dispatched to external workers, not squeezed into the 20 seconds.
- Tested with the editor's test feature against records that hit the edge cases, not just the happy path.
Frequently asked questions
What are HubSpot custom code actions?
Workflow actions that run your own JavaScript (Node.js) or Python code for each enrolled record, letting you implement logic native actions can't: custom calculations, external API calls, conditional record creation. They require Operations Hub Professional or Enterprise and execute as stateless serverless functions with defined output fields.
What are the limits of HubSpot custom code actions?
Execution must finish within 20 seconds using at most 128 MB of memory, log output truncates around 4 KB, and only the preloaded libraries (like axios and the official HubSpot client) are available. There is no way to extend these limits; longer jobs should run in external systems triggered by the workflow.
Do HubSpot custom code actions retry automatically?
Yes, in one specific case: when the code throws on a rate limit error (429) or a server error (5xx), HubSpot retries the action automatically, starting about a minute after the failure and continuing for up to three days. Other unhandled errors fail the action without retry, so the recommended pattern is to throw retryable errors and explicitly catch everything else.
How do secrets work in custom code actions?
You create secrets in the custom code editor and read them as environment variables (process.env in Node, os.getenv in Python). The critical detail: each action has its own list of selected secrets, so a secret that exists in the portal but wasn't added to that specific action arrives as undefined. Store API tokens as secrets, never in the code itself.
Custom code or an external automation tool: when to use which?
Use custom code for surgical, record-level logic that fits comfortably in 20 seconds: scoring, formatting, a lookup, a conditional write. Use an external platform (n8n, serverless functions) when the job involves volume, long execution, multiple systems, or complex orchestration, with the workflow triggering it via webhook and results written back to the CRM.
Ready to take your operation to the next level?
Talk to a specialist and see how we can help.