Multi-Currency Deal Management in HubSpot
/ 5 min read
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 Name | Internal Name | Field Type |
|---|---|---|
| EUR Value | eur_value | Number |
| USD Value | usd_value | Number |
| CHF Value | chf_value | Number |
| Exchange Rate Last Update | exchange_rate_last_update | Date |
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_valueto the outputusd_value - Set
chf_valueto the outputchf_value - Set
exchange_rate_last_updateto 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 hourconst 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 neededif (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:
- Trigger: Scheduled, daily at 2:00 AM
- Enrollment: All deals where
amountis known - 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_valuefor 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
- HubSpot’s static exchange rates aren’t suitable for accurate multi-currency reporting
- Custom coded actions can fetch live rates and update deal properties
- Consider API rate limits when designing your solution
- Lock exchange rates on closed deals for financial accuracy
- 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.