skip to content
san.is
Table of Contents

International businesses face a persistent challenge in HubSpot: deal amounts in multiple currencies that need to stay synchronised. While HubSpot supports multiple currencies, the exchange rates are static and require manual updates. For accurate forecasting and reporting, you need real-time conversion.

This article shows you how to build a custom coded workflow that automatically updates deal amounts across currencies whenever a deal is created or modified.

The Business Problem

Consider a European company selling globally. Their primary currency is EUR, but they need to report in USD for investors and track CHF for Swiss operations. When a sales rep enters a €100,000 deal, the team needs to instantly see the equivalent in USD and CHF.

The manual approach fails because:

  • Exchange rates fluctuate daily
  • Manual updates are forgotten
  • Reports become unreliable
  • Forecasting accuracy suffers

Prerequisites

To implement this solution, you need:

  • Operations Hub Professional or Enterprise (required for custom coded actions)
  • Custom deal properties for each currency amount
  • API access to an exchange rate service

Step 1: Create Custom Properties

First, create three custom deal properties:

Property NameInternal NameField Type
EUR Valueeur_valueNumber
USD Valueusd_valueNumber
CHF Valuechf_valueNumber
Exchange Rate Last Updateexchange_rate_last_updateDate

Also consider which currency is your “source of truth”. In this example, we’ll assume EUR is the primary currency that reps enter, and USD/CHF are calculated.

Step 2: The Custom Coded Workflow Action

Create a deal-based workflow with the trigger “Deal amount is known” or “Deal amount has changed”. Add a custom coded action with the following JavaScript:

const axios = require('axios');
exports.main = async (event, callback) => {
// Get the deal amount (assuming EUR is primary)
const amount = event.inputFields['amount'];
if (!amount || amount === 0) {
callback({
outputFields: {
status: 'skipped',
reason: 'No amount specified'
}
});
return;
}
try {
// Fetch current exchange rates
// Using exchangerate-api.com (free tier: 1500 requests/month)
const response = await axios.get(
'https://api.exchangerate-api.com/v4/latest/EUR'
);
const rates = response.data.rates;
// Calculate converted amounts
const usdValue = (parseFloat(amount) * rates.USD).toFixed(2);
const chfValue = (parseFloat(amount) * rates.CHF).toFixed(2);
const timestamp = new Date().toISOString();
callback({
outputFields: {
usd_value: usdValue,
chf_value: chfValue,
exchange_rate_last_update: timestamp,
usd_rate: rates.USD.toFixed(4),
chf_rate: rates.CHF.toFixed(4),
status: 'success'
}
});
} catch (error) {
console.error('Exchange rate fetch failed:', error.message);
callback({
outputFields: {
status: 'error',
error_message: error.message
}
});
}
};

Step 3: Configure Input Fields

In the workflow action configuration, add the deal’s amount property as an input field. This passes the current deal amount to your custom code.

Step 4: Add Property Updates

After the custom coded action, add “Set property value” actions to update each currency property:

  • Set usd_value to the output usd_value
  • Set chf_value to the output chf_value
  • Set exchange_rate_last_update to the output timestamp

Handling Edge Cases

Deals with Zero Amount

The code above skips deals with no amount, but you might want to clear the currency fields:

if (!amount || parseFloat(amount) === 0) {
callback({
outputFields: {
usd_value: '0',
chf_value: '0',
status: 'zeroed'
}
});
return;
}

API Rate Limiting

Free exchange rate APIs have limits. For high-volume scenarios, cache the rates:

// Store rates in a property or external cache
// Check if rates were updated within the last hour
const lastUpdate = event.inputFields['exchange_rate_last_update'];
const oneHourAgo = new Date(Date.now() - 3600000);
if (lastUpdate && new Date(lastUpdate) > oneHourAgo) {
// Use cached rates from previous calculation
// Skip API call
}

Multiple Source Currencies

If deals can be entered in different currencies, you need detection logic:

const dealCurrency = event.inputFields['deal_currency_code'];
const amount = event.inputFields['amount'];
let baseCurrency = 'EUR';
let eurAmount = amount;
// Convert to EUR first if needed
if (dealCurrency !== 'EUR') {
const response = await axios.get(
`https://api.exchangerate-api.com/v4/latest/${dealCurrency}`
);
eurAmount = amount / response.data.rates.EUR;
baseCurrency = dealCurrency;
}
// Now convert EUR to other currencies
// ...

Alternative: Scheduled Bulk Updates

Instead of triggering on every deal change, you might prefer nightly batch updates. This approach:

  • Reduces API calls
  • Updates all deals consistently
  • Works within rate limits

Create a scheduled workflow that runs daily:

  1. Trigger: Scheduled, daily at 2:00 AM
  2. Enrollment: All deals where amount is known
  3. Action: Custom coded action (same code as above)

Important: Enable re-enrollment so the workflow processes deals repeatedly.

Using Premium Exchange Rate APIs

For production systems, consider paid APIs with better reliability:

Open Exchange Rates

const response = await axios.get(
'https://openexchangerates.org/api/latest.json',
{
params: {
app_id: process.env.OXR_API_KEY,
base: 'EUR'
}
}
);

Add your API key as a secret in the workflow settings, then access it via process.env.

XE Currency Data

const response = await axios.get(
'https://xecdapi.xe.com/v1/convert_from.json/',
{
auth: {
username: process.env.XE_ACCOUNT_ID,
password: process.env.XE_API_KEY
},
params: {
from: 'EUR',
to: 'USD,CHF'
}
}
);

Reporting on Multi-Currency Deals

Once your properties are populated, create calculated reports:

Total Pipeline by Currency

  • Create a report showing sum of usd_value for all open deals
  • Filter by deal stage to show conversion-weighted pipeline

Currency Mix Analysis

  • Group deals by original currency
  • Identify which markets generate the most revenue

Exchange Rate Impact

  • Compare amounts over time
  • Show how rate fluctuations affect reported figures

Handling Historical Accuracy

For financial reporting, you may need to preserve the exchange rate at the time of close rather than continuously updating. Add logic to lock rates on closed deals:

const dealStage = event.inputFields['dealstage'];
const closedStages = ['closedwon', 'closedlost'];
if (closedStages.includes(dealStage)) {
// Skip update for closed deals
callback({
outputFields: {
status: 'skipped',
reason: 'Deal is closed - preserving historical rate'
}
});
return;
}

Implementation Checklist

  • Create custom properties for each currency
  • Choose and configure exchange rate API
  • Build custom coded workflow action
  • Test with sample deals
  • Configure error alerting
  • Document the approach for your team
  • Set up reporting using new properties

Key Takeaways

  1. HubSpot’s static exchange rates aren’t suitable for accurate multi-currency reporting
  2. Custom coded actions can fetch live rates and update deal properties
  3. Consider API rate limits when designing your solution
  4. Lock exchange rates on closed deals for financial accuracy
  5. Use the calculated properties for pipeline reporting and forecasting

This pattern extends beyond currency conversion - any scenario where deal values need transformation based on external data can use this approach.


Need help implementing multi-currency solutions or other custom HubSpot workflows? Connect with me on LinkedIn.