ml-connector
AcumaticaHubSpot

Acumatica and HubSpot integration

Acumatica runs finance, distribution, and accounts receivable. HubSpot runs CRM, sales, and customer-facing service. Connecting the two means the customer and contact records you maintain in Acumatica appear in HubSpot as companies and contacts, and the AR invoices and payment status from Acumatica show up against those records so sales and finance work from the same picture. ml-connector handles the different APIs on each side and moves the data on a schedule you control. Because HubSpot is a CRM and not an accounting system, the general ledger and AP stay in Acumatica where they belong.

How Acumatica works

Acumatica exposes customers, contacts, vendors, AR sales invoices, AP bills, payments, GL accounts, and items through its Contract-Based REST API, which is JSON over HTTPS on a tenant-specific instance URL. Every endpoint is version-locked to the customer's ERP release, so a path like /entity/Default/24.200.001/Customer must match the running version exactly or it returns 404, and all field values are wrapped in value objects. Authentication is OAuth 2.0 through the OpenID Connect server built into each instance, with a Client ID that embeds the company name. Acumatica push notifications exist but carry no HMAC signature and have no dead-letter queue, so records are read by polling on LastModifiedDateTime with offset paging.

How HubSpot works

HubSpot exposes companies, contacts, deals, invoices, line items, products, and payments through its REST CRM API at api.hubapi.com, with contacts keyed on a unique email and companies keyed on domain. Server-to-server access uses a Private App access token passed as a static bearer header, scoped to the object permissions the customer grants. Reads and writes use cursor pagination with batch upsert endpoints that update an existing record or create a new one by an id property. HubSpot webhooks require a separate Public App rather than a Private App, and payment objects are read with only limited write support.

What moves between them

The main flow runs from Acumatica into HubSpot. ml-connector reads Acumatica customers and their contacts and writes them into HubSpot as companies and contacts, matching on domain and email so existing records update instead of duplicating. AR sales invoices and their payment status flow the same direction and are associated to the right HubSpot company and contact so the account view shows what was billed and what was paid. HubSpot is treated as the customer-facing view, so ml-connector does not write the general ledger or AP back into HubSpot, and where a HubSpot deal closes it can optionally seed a matching Acumatica customer so the ERP record exists before the first invoice.

How ml-connector handles it

ml-connector stores both credential sets encrypted. On the Acumatica side it runs the OAuth 2.0 flow against the instance OpenID Connect server, refreshes the token using the offline_access scope, and pins every request to the customer's exact endpoint version so a release mismatch never silently breaks the sync. It wraps each field value in the required value object that Acumatica expects and unwraps it on the way back. On the HubSpot side it sends the Private App bearer token on every call and uses the batch upsert endpoint with email for contacts and domain for companies so re-running a sync does not create duplicates. Because Acumatica push notifications are unsigned and have no dead-letter queue, ml-connector polls Acumatica customers and invoices on a schedule using a $filter on LastModifiedDateTime and stores the high-water mark, rather than trusting a push. Acumatica has no idempotency-key header, so ml-connector dedupes on a natural key such as the invoice reference plus a BullMQ jobId before writing. Acumatica rate limits return HTTP 429 once the request count crosses the licensed threshold, and HubSpot returns 429 with a Retry-After header at its per-account limit, so both sides get exponential backoff with jitter. HubSpot invoice write entered public beta in early 2026, so invoice detail is mapped conservatively and falls back to a custom property on the associated company when a field is not yet writable. Every record carries a full audit trail and can be replayed if a downstream call fails.

A real-world example

A mid-sized distribution business with around 150 staff runs Acumatica for order management, billing, and accounts receivable, and runs HubSpot for sales and account management. Before the integration, account managers had no view of whether a customer's invoices were open or paid, so they pinged finance over chat before every renewal call, and new customers set up in Acumatica were re-typed into HubSpot by hand with mismatched company names. With Acumatica and HubSpot connected, each customer and contact created in Acumatica appears in HubSpot automatically, and AR invoices and payment status post against the matching company within the polling window. Account managers see billing status on the record before they call, and the duplicate data entry between billing and CRM is gone.

What you can do

  • Push Acumatica customers and contacts into HubSpot as companies and contacts, matched on domain and email.
  • Surface Acumatica AR invoices and payment status against the right HubSpot company and contact.
  • Bridge Acumatica OAuth 2.0 and version-pinned endpoints to HubSpot's static Private App bearer token.
  • Poll Acumatica on LastModifiedDateTime with high-water-mark tracking, since its push notifications are unsigned and not guaranteed.
  • Dedupe with HubSpot batch upsert and a BullMQ jobId, with retries and a full audit trail on every record.

Questions

Which direction does data move between Acumatica and HubSpot?
The main flow is Acumatica into HubSpot. Customers, contacts, AR invoices, and payment status move from Acumatica into HubSpot and are associated to the matching company and contact. HubSpot is a CRM rather than an accounting system, so the general ledger and AP stay in Acumatica and ml-connector does not write financial books back into HubSpot. A closed HubSpot deal can optionally seed a matching Acumatica customer so the ERP record exists before billing.
How does the integration handle Acumatica's version-locked endpoints and lack of signed webhooks?
ml-connector pins every Acumatica request to the customer's exact endpoint version, so a path like /entity/Default/24.200.001/Customer always matches the running release instead of returning a 404. Acumatica push notifications carry no HMAC signature and have no dead-letter queue, so the connector polls customers and invoices on a schedule using a $filter on LastModifiedDateTime and stores the high-water mark. This makes the sync reliable even when a push is missed.
Does HubSpot's Private App token cover everything, or is a Public App needed?
The Private App access token covers all the reads and writes this sync needs, passed as a static bearer header scoped to the granted object permissions. HubSpot only requires a separate Public App if you want HubSpot itself to push events out by webhook, which this integration does not depend on because the source of truth is Acumatica and it is polled. Invoice write in HubSpot is in public beta, so invoice fields are mapped conservatively and fall back to a custom property where a field is not yet writable.

Related integrations

Connect Acumatica and HubSpot

Free to use. Add your credentials, ping your real systems, and see if we fit.

Get started