HubSpot custom coded actions: call an outside API safely

A HubSpot custom code action runs Node.js or Python in a workflow for 20 seconds, with secrets as env vars. Start slow API jobs there; don't wait.

5 min readSume
All posts

A HubSpot custom coded action is a workflow step that runs your own JavaScript (Node.js) or Python code for each enrolled record, on Data Hub Professional or Enterprise. Secrets you add to the action arrive as environment variables, so the code can call an outside API with a key HubSpot keeps out of the code. The action must finish within 20 seconds, so it can start a slow job but not wait for it.

The HubSpot facts come from HubSpot's Custom code workflow actions developer page. The Sume facts come from Create a run and Authentication. All were read on 2026-09-29. Sume has no official HubSpot app; the action makes a plain HTTPS call.

What are the limits of a custom code action?

HubSpot marks Python as beta; Node.js is the default language.

From HubSpot's Custom code workflow actions page, read 2026-09-29.
LimitValue
PlansData Hub Professional or Enterprise
Run timeMust finish within 20 seconds
MemoryUp to 128 MB
SecretsEnvironment variables; all secret values together at most 1000 characters
Properties passed inUp to 50 per action
String output valuesUp to 65,000 characters
Rate limit settingOff by default; executions per second, minute or hour

How do I call an external API from the action?

A few settings in the action, then the code. The example starts a Sume Format run for an enrolled deal.

  • In the workflow, add the Custom code action. Under Secret, add your API key, for example as SUME_API_KEY.
  • Under Properties to include in code, add the fields the call needs; they arrive in event.inputFields. The enrolled record's id is event.object.objectId.
  • Use axios, which HubSpot lists among the Node.js libraries you can require().
  • Return the job or run id with callback({ outputFields: … }) and define it under Data outputs, so a later step can store it on the record.
const axios = require("axios");

exports.main = async (event, callback) => {
  const dealId = event.object.objectId;
  const res = await axios.post(
    "https://api.sume.com/v1/formats/acme/product-promo/runs",
    {
      instruction: "15-second vertical promo",
      input: { product_name: event.inputFields["dealname"] },
      generation_spend_cap_usd: 20,
      communication: { webhook_url: "https://example.com/hooks/sume" },
    },
    {
      headers: {
        Authorization: `Bearer ${process.env.SUME_API_KEY}`,
        "Idempotency-Key": `hubspot-deal-${dealId}-v1`,
      },
    },
  );
  callback({ outputFields: { sume_run_id: res.data.data.id } });
};

Will HubSpot retry the call, and can that bill twice?

HubSpot retries when the call fails with a rate-limit error, or a 429 or 5XX error from axios or @hubspot/api-client: it reattempts the action for up to three days, starting one minute after the failure, with gaps of up to eight hours. Each attempt runs your code again, so each sends the create again.

That is safe only with a stable key. Sume's docs say to derive the Idempotency-Key from the thing being made, such as the record id plus a version you bump for a deliberate re-run. The same key with the same body returns the original run with no second run and no second charge; a changed body under the same key is 409 idempotency_conflict and nothing runs. Don't use Math.random or the time: HubSpot notes that Math.random can repeat across executions, and a key that changes defeats the check.

How do I get the result back into HubSpot?

Not in the same action. Sume's video docs say one video generation typically takes 30 seconds to several minutes, and the action has 20. The create answers 202 with the run receipt. Set communication.webhook_url to an HTTPS endpoint you run, which receives one signed POST when the run completes or fails, and have that endpoint update the record. Signed webhooks for video runs covers verifying it.

Cap the spend per record with generation_spend_cap_usd, up to the platform maximum of $500, and use the action's rate limit so a large enrollment doesn't send every create at once.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume