Custom code actions in workflows changed what HubSpot can do. They run in a Node.js sandbox, get up to 20 seconds, and have access to a hubspotClient instance. Here are six patterns I’ve shipped in production.
1. Round-robin assignment with availability check
exports.main = async (event, callback) => {
const reps = ["123", "456", "789"];
const ooo = await getOOOReps(); // call your custom API
const available = reps.filter(r => !ooo.includes(r));
const next = available[Date.now() % available.length];
callback({ outputFields: { assigned_rep: next } });
};
Native round-robin doesn’t skip OOO reps. This does.
2. Multi-currency normalization
exports.main = async (event, callback) => {
const rates = await fetch("https://api.exchangerate.host/latest?base=USD")
.then(r => r.json());
const usd_amount = event.fields.amount / rates.rates[event.fields.currency];
callback({ outputFields: { amount_usd: usd_amount } });
};
Pipe deal amount in any currency to a normalized USD field for forecasting.
3. External enrichment fallback
When your enrichment provider misses a company, hit a secondary API. Don’t gate the workflow on the first call succeeding.
4. Slugify a property for URL use
exports.main = (event, callback) => {
const slug = event.fields.title
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "");
callback({ outputFields: { slug } });
};
Drives the URL of programmatically-generated CMS pages from a custom object.
5. Conditional sequencing across deals
When a contact has 3+ deals stuck in “Negotiation” for 30 days, escalate. The UI can express the count but not “across all deals.” Custom code can.
6. Webhook with retry and circuit breaker
exports.main = async (event, callback) => {
for (let attempt = 0; attempt < 3; attempt++) {
try {
const res = await fetch(targetUrl, { method: "POST", body: JSON.stringify(event.fields) });
if (res.ok) return callback({ outputFields: { synced: true } });
} catch (e) { /* retry */ }
await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));
}
callback({ outputFields: { synced: false, error: "max_retries" } });
};
Native webhook actions don’t retry. This pattern saves you the integration headaches.
Three rules that keep custom code maintainable
- Keep each action under 60 lines. Anything longer belongs in a serverless function.
- Log to
console.log; the action’s execution log is your debugger. - Always set output fields, never throw. Throws halt the workflow without telling you why.
What to do this week
Audit your most complex multi-action workflow. If you have a chain of 5+ “if/then” branches, replace it with one custom code action. Maintenance load drops 70%.