Wrong Currency After Sync: Number Moved, Code Didn't
An amount shows the wrong currency after a sync because the integration copies the number but not its currency code, and the receiving system labels the number with its own default: the record, customer, account, or field currency. The HubSpot-Salesforce integration syncs only the number and applies no exchange rate, QuickBooks fixes a customer's currency at creation, and Airtable stores a symbol on the field rather than a code on the value. The fix is to move every amount as a pair of number and ISO 4217 code, write the code before or with the number, and never sync converted amounts, because each system converts with a different rate, direction, and date.
When an amount shows the wrong currency after a sync, the number usually arrived intact and its currency code did not. A EUR 1,000 opportunity becomes a USD 1,000 deal because the integration copied 1000 and the receiving system labeled it with its own default: the record's currency, the customer's currency, the account's home currency, or a symbol set on the field. No step errors, because 1,000 is a valid amount in every currency. The fix is to treat money as a pair, a number plus an ISO 4217 code, move both together, and never sync an amount that some other system already converted.
Why does a EUR deal land as USD?
Money has two parts and most field mappings carry one. The payload says "amount": 1000. The currency lives somewhere else: a separate CurrencyIsoCode field on the record, a currency fixed on the customer, a company-wide default, or nowhere at all. When the mapping moves the amount and skips the code, the receiver fills the gap with whatever it considers the default, and the record looks complete.
HubSpot says this plainly for its own Salesforce integration: opportunities in multi-currency orgs can sync, but "only the number value will sync between Salesforce and HubSpot, and no exchange rates will be applied." That is a first-party integration between two multi-currency CRMs. A Zap or an n8n workflow built from a field list does no better unless someone maps the code on purpose.
The US-based version of this problem is sneaky. Nearly every record is USD, so the default is right for almost every record and the test deal passes. The three Canadian and one UK customer are wrong, and they are wrong by the exchange rate, which is small enough to look like a pricing difference. A CAD 50,000 contract reported as USD 50,000 overstates revenue by the full CAD-to-USD gap, and nobody questions a round number.
Where each system keeps the currency
This is the table we check before mapping any amount field. The column that matters most is the last one, because in several systems the currency is a one-way door.
| System | Where the currency lives | If you send only the number | Can you change it later? |
|---|---|---|---|
| Salesforce (multi-currency on) | CurrencyIsoCode on each record; one code for every amount field on that record | Record takes a default; Salesforce documents the user's personal currency as the default for opportunities, and for an automation that user is the integration user | Yes, but amounts are not converted, they "display the new currency code" |
| HubSpot (multiple currencies on) | Deal Currency property; Amount is "in the deal's currency" | Deal takes the account's company currency | Yes on the deal; company currency changes do not reconvert deals on free tools |
| QuickBooks Online | CurrencyRef on the customer, vendor, or account; transactions follow it | The customer's currency is whatever the create call set, and it is locked | No. Customer and vendor currency "cannot be changed" once configured |
| Stripe | currency on every PaymentIntent, Price, and balance transaction, lowercase ISO code | Not possible; the API requires it | Fixed per object |
| Shopify | MoneyBag with both shopMoney and presentmentMoney | You picked one of the two and dropped the other | Fixed per order |
| Airtable | A symbol option on the currency field; no code on the value | The whole column is one currency by definition | Changing the symbol relabels every row |
Two of these rows deserve a second look.
Salesforce shares one code across the whole record. An Opportunity has a single CurrencyIsoCode that applies to Amount, every custom currency field, and anything a formula returns as currency. You cannot store a EUR list price and a USD cost on the same record without converting one of them first. Integrations that map a "cost in home currency" field onto a foreign-currency opportunity silently relabel it.
QuickBooks decides on the first write. Intuit's docs say multicurrency "cannot be disabled once it's been enabled," the home currency cannot be changed once set, and a customer's currency is set at creation and then locked. An automation that auto-creates QuickBooks customers from new CRM accounts has exactly one chance to send CurrencyRef. Miss it and every future invoice for that customer is in the wrong currency until someone creates a second customer record.
Airtable is the quiet one. The currency field's options are precision and symbol. There is no per-row code. A table that mixes USD and CAD invoices in one currency column is wrong on every CAD row, and the fix is a separate single-select column holding the code.
Why converted amounts never agree across systems
Once the code travels correctly, the next temptation is to also sync the converted amount, the "Amount in company currency," so every system shows the same home-currency figure. Do not. A conversion is not one number. It is a rate, a direction, and a date, and the major systems disagree on all three.
| System | Rate direction | Which date's rate |
|---|---|---|
| Salesforce | Corporate to foreign: "1 USD to 1.39 AUD" | Current static rate for everything; "changing the exchange rate automatically updates converted amounts on all records, including on closed opportunities." Dated rates only with advanced currency management, and only for opportunity-family objects |
| HubSpot | Deal to company: Amount x Exchange rate = company amount | Open deals follow the latest rate; closed deals use the rate in effect at the close date |
| QuickBooks Online | Home units per one foreign unit | Rates stored by as-of date; each transaction carries its own ExchangeRate |
| Stripe | amount in A x exchange_rate = amount in B | The rate actually used at conversion, on the balance transaction |
Read the first column again. Salesforce expresses rates as foreign units per one corporate unit. QuickBooks and HubSpot express them as home units per one foreign unit. A person who copies the EUR rate from Salesforce into a QuickBooks rate override, or an automation that reuses one rate table for both, inverts it. At a EUR rate of 0.92 the inverse is about 1.087, so every converted amount is off by roughly 18 percent. That is not a clean factor of 100 like the percentage convention mismatch. It is a plausible number, which is why it survives.
The date column is the other trap. The same closed EUR deal shows one home-currency value in Salesforce (today's rate, recalculated every time an admin updates rates), another in HubSpot (the close-date rate, frozen), a third on the QuickBooks invoice (the rate on the invoice date), and a fourth in Stripe (the rate at settlement). All four are correct by their own rules. If you sync any one of those converted figures into another system as if it were an amount, you have created a fifth number with no rate and no date attached, and your reports will drift by a little every month.
The currency contract we put on every money field
This is the rule set, in the order we apply it when we build or audit a sync.
- Every amount travels with its code. In the field map, a money field is two columns:
amountandcurrency, with the code in ISO 4217. A mapping row that carries an amount without a code fails review. Stripe's lowercaseeurand QuickBooks'EURare the same code, so normalize case at the boundary. - Write the code first, or in the same call. On Salesforce, set
CurrencyIsoCodeon the create request, not in a follow-up update. On HubSpot, send the deal currency in the same properties object as the amount. On QuickBooks,CurrencyRefgoes on the customer create, because there is no second chance. - Sync originals, never conversions. Map the transaction-currency amount only. Let each receiver compute its own home-currency figure with its own rates. If a report needs a single converted number, compute it in the reporting layer with a named rate source and date, and store the rate beside the result.
- Pick one rate table and write down its direction. If you push rates into Salesforce and QuickBooks from a feed, keep one canonical form (for example, USD per one unit of foreign currency) and invert explicitly for Salesforce. Label the column with the direction so the next person does not guess.
- Guard on mismatch. Before writing an amount to an existing record, compare the incoming code to the record's code. If they differ, stop and alert instead of writing. A mismatched write is the one case where the right action is to do nothing, and it belongs in the run log with both codes.
Rule 5 matters most in two-way syncs. A deal edited in HubSpot with one currency and in Salesforce with another is a conflict on the currency field, and the standard last-write-wins logic will happily keep one side's number with the other side's code. Treat currency as a field one system owns, the same way we handle other two-way sync conflicts.
How to find and fix the records that already landed wrong
Start with a query, not a spot check. Export every record with an amount from the source and the receiver, keyed on the shared ID, with both currency codes. Any row where the codes differ is a mislabeled record. In a mostly-USD business this list is short, and it is often the same list your finance team has been adjusting by hand.
Then decide per row whether the number is original or converted. Compare it to the source amount.
- The number matches the source and only the code is wrong. Relabel. In Salesforce, changing the record currency does not convert amounts, which is exactly what you want here. In HubSpot, set the deal currency.
- The number does not match the source. Something converted it on the way. Rewrite the amount from the source and set the code in the same call. Relabeling alone would make it worse.
- The record is a QuickBooks customer in the wrong currency. You cannot change it. Create a new customer in the right currency, point the sync's ID map at it, and leave the old one for its existing transactions.
Closed periods are the exception. If a mislabeled invoice already sits in a closed accounting period, do not rewrite it from an automation. Hand the list to whoever closes the books. That fix is a journal entry, not a sync.
Last, add the test that would have caught it: create one record in a non-default currency with an odd amount, such as CAD 1,234.56, and follow it through every hop. Check that each receiver shows CAD and 1,234.56, not USD and not a converted figure. A test deal in USD proves nothing, because USD is the default everywhere and a sync that drops the code still passes.
What to do next
Pull your field map and find every money field. For each one, write the name of the field that carries its currency code in the source and the receiver. If you cannot name it, that mapping is copying a number without its unit, and the first foreign customer will expose it. Then run the non-default-currency test record through the whole chain before your next quarterly close.
Currency errors sit between sales data and the books, which is where most of our data and reporting work lives. If your CRM and accounting totals disagree on foreign deals and you want a second set of eyes on the sync, tell us what connects to what.
Frequently Asked Questions
SOURCES & CITATIONS
- Sync Salesforce opportunities to HubSpot — HubSpothttps://knowledge.hubspot.com/salesforce/sync-salesforce-opportunities-to-hubspot
- Considerations for Enabling Multiple Currencies — Salesforcehttps://help.salesforce.com/s/articleView?id=sales.admin_enable_multicurrency_implications.htm&type=5
- Manage multiple currencies (QuickBooks Online API) — Intuithttps://developer.intuit.com/app/developer/qbo/docs/workflows/manage-multiple-currencies
- The Balance Transaction object (exchange_rate) — Stripehttps://docs.stripe.com/api/balance_transactions/object
About Alexey Yushkin
Alexey is the founder of GENERAL INFORMATICS LLC. He designs and ships AI and automation systems for businesses and operators across the US.
Related reading
Want this kind of system in your business?
We build practical AI and automation systems for operators. Send us your current workflow and we will show you what to automate first.
