RevOps HQ
← BACK TO BLOG
9/22/2026
Revenue OperationsHubSpot

Revenue Recognition on Milestone Contracts: From a HubSpot Deal to Sage Intacct

Automating revenue recognition for milestone contracts from HubSpot into Sage Intacct: what crosses the boundary, what stays behind, and where the schedule has to live.

P

Paul Maxwell

AUTHOR

GET WEEKLY REVOPS INSIGHTS

No spam. Unsubscribe anytime.

A milestone contract is signed in January for $480,000 across four delivery stages, and the first question anyone asks is what it does to the quarter. Sales answers $480,000, because that is the deal they closed. Finance answers something between nothing and $120,000, depending on whether stage one has been accepted. Both are working from the same signature and neither can see the other's arithmetic, so the number reported to the board is whichever system the person building the deck happened to open.

This article describes how to automate revenue recognition for milestone contracts that originate as HubSpot deals and are recognised in Sage Intacct. It starts with what a milestone contract is in each system, then sets the boundary between them, then describes the deal structure that survives the handoff, the schedule that has to live in the finance system, and the identity key that makes reconciliation possible. It closes with what breaks under contract amendment, the business case for building this, and the checks to run before the first month-end close depends on it.

Three terms are used precisely throughout. A milestone is a contractual delivery stage with an acceptance condition attached to it. A recognition schedule is the set of dated amounts that move from deferred revenue to recognised revenue as those conditions are met. Billing is a separate matter entirely: an invoice can be raised before, after, or nowhere near the point at which the associated revenue is recognised, and conflating the two is the error this whole arrangement exists to prevent.

The Milestone Contract in Each System

In HubSpot a contract is a deal with an amount, a close date, a pipeline stage, and line items describing what was sold. Line items carry price and quantity and associate to the deal (HubSpot line items API). The deal object has no native concept of a delivery stage that has been accepted, and no field whose meaning is "revenue that may now be recognised" (HubSpot deals API).

In Sage Intacct the same contract is a set of accounting entries with dates and accounts attached, posted against a customer and a project. The general ledger is the system of record for what has been recognised, and the journal entry is the artefact that records it (Sage Intacct journal entries).

The asymmetry matters more than the terminology. HubSpot holds the commercial fact — this customer agreed to this scope at this price — and Intacct holds the accounting consequence. Neither is a subset of the other, and an integration that treats one as a mirror of the other produces a finance system carrying sales assumptions or a CRM carrying accounting entries.

What crosses the HubSpot to Sage Intacct boundary, and what stays on each sideHubSpot holds the deal and its line items, the pipeline stage, and the record of what was sold and for how much. Sage Intacct holds the recognition schedule, the journal entries, and the record of what has been recognised and when. Three things cross from HubSpot into Intacct at the point a deal closes: customer identity matched on a key rather than a name, milestone values as one amount per delivery stage, and milestone dates recording expected acceptance rather than an invoice date. One thing crosses back the other way, the contract identifier, written onto the deal so the CRM can find the contract. Recognised revenue does not cross at all and stays on the finance side.HUBSPOTSAGE INTACCTDeal and line itemsPipeline stageWhat was sold, and for how muchRecognition scheduleJournal entriesWhat has been recognised, and whenCustomer identityMatched on a key, never on a nameMilestone valuesOne amount per delivery stageMilestone datesExpected acceptance, not invoice dateContract identifierWritten back so the CRM can find itRecognised revenue does not cross. It stays in the finance system.On the deal record it becomes a number a salesperson can report without reconciling it.
What HubSpot owns, what Sage Intacct owns, and the three fields that cross between them.
What HubSpot owns, what Sage Intacct owns, and the three fields that cross between them

The Boundary and the Three Fields That Cross It

Only three things need to cross from HubSpot into Intacct at the point a deal closes: the customer identity, the contract value broken down by milestone, and the expected date of each milestone. Everything else the finance system needs it already has or will generate itself.

Nothing needs to cross in the other direction except one value, and that value is not money. It is the reconciliation key described below, written back onto the deal so that a person looking at the CRM can find the contract in the finance system. Recognised revenue does not belong on the deal record. A salesperson who can see recognised revenue on a deal will report it, and the number they report will be stale between the moment it is read and the moment the period closes.

This is a decision about permissions as much as about architecture. A CRM field containing an accounting figure will be used as an accounting figure by someone with no obligation to reconcile it.

The Deal Structure That Survives the Handoff

A milestone contract cannot be expressed as a single deal amount, because a single amount has no internal structure for a schedule to be derived from. Two structures carry that internal detail successfully, and a third, which is the one most often reached for, does not.

The structure that works at low volume is one line item per milestone, each carrying the milestone value and a date property. Line items already associate to the deal and already carry the amounts, so no new object is required (HubSpot associations).

The structure that works at higher volume is a custom object, one record per milestone, associated to the deal. This costs more to build and buys two things the line item cannot: a milestone can carry its own status, and it can be edited after the deal closes without altering the commercial record of what was sold.

The structure that does not work is a set of deal properties named milestone_1_amount through milestone_4_amount. It fails the first time a contract has five milestones, and every subsequent report has to union four columns that should have been four rows (HubSpot properties).

Milestone countOne line item per milestoneAnyCustom object per milestoneAnyNumbered deal propertiesFixed at build time
Status per milestoneOne line item per milestoneNoCustom object per milestoneYesNumbered deal propertiesOnly as more properties
Editable post-closeOne line item per milestoneAlters the sold recordCustom object per milestoneYes, independentlyNumbered deal propertiesYes, silently
Build costOne line item per milestoneLowestCustom object per milestoneModerateNumbered deal propertiesLow, then rising
Reporting shapeOne line item per milestoneRowsCustom object per milestoneRowsNumbered deal propertiesColumns needing a union

Ownership of the Recognition Schedule

The recognition schedule belongs in Sage Intacct, and the argument for that is not a preference about tooling.

A schedule is an accounting artefact governed by a revenue standard, audited, and subject to restatement. It has to survive the departure of the person who built it and produce the same answer when an auditor recalculates it in two years. Intacct's platform holds the schedule against the contract and posts against it (Sage Intacct platform services).

HubSpot's role stops at supplying the inputs. What crosses the boundary at close is a proposed schedule — amounts and dates derived from the milestones — and what Intacct does with it is subject to review by someone who is accountable for the general ledger. The proposal is data. The schedule is a record.

Teams that invert this, computing recognition in HubSpot workflows and posting the results, end up with revenue policy encoded in a marketing automation tool that nobody in finance can read, version, or defend.

A deal with four milestones and the recognition schedule derived from acceptance datesA worked example. One contract worth 480,000 dollars signed in January, divided into four milestones of 120,000 dollars each: discovery and design, build phase one, build phase two, and handover and sign-off. All four are signed in January. They are accepted in March, June, September and December respectively, so the recognition schedule places the revenue in four different periods across the year rather than in the quarter the contract was signed. The figures illustrate the structure and are not a measurement.ONE DEAL, SIGNED IN JANUARY — $480,000RECOGNITION SCHEDULEMilestoneValueRecognised on acceptanceDiscovery and design$120,000acceptedMar$120,000Build, phase one$120,000acceptedJun$120,000Build, phase two$120,000acceptedSep$120,000Handover and sign-off$120,000acceptedDec$120,000Every milestone is signed in January. None of them is recognised in January.Figures illustrate the structure and are not drawn from a measured engagement.
One deal, four milestones, and the recognition schedule derived from their acceptance dates.
One deal, four milestones, and the recognition schedule derived from their acceptance dates

Identity and the Reconciliation Key

No two commercial systems share an identifier space, and this pairing is no exception. The HubSpot deal has a numeric record ID and the Intacct contract has its own, so neither system can look the other up without a key stored deliberately on both sides.

The arrangement that holds is a single field on the deal carrying the Intacct contract identifier, written back by the integration once the contract exists, and a customer-defined field on the Intacct contract carrying the HubSpot deal ID. Both are set once and never edited by a person. The customer record needs the same treatment, because a company in HubSpot and a customer in Intacct are matched on name by default, and names diverge the first time a legal entity changes.

Matching on company name is the failure that produces revenue posted against the wrong customer, and it is invisible until someone reconciles a statement.

Triggering and What Fires the Write

The trigger is the acceptance of a milestone rather than the closing of the deal. A deal closing creates the contract and the proposed schedule. A milestone being accepted is what permits recognition against it, and acceptance is a delivery event rather than a sales event.

Two mechanisms carry it. A webhook subscription fires on the property change that records acceptance and calls the integration (HubSpot webhooks). A custom workflow action does the same inside a workflow, which is easier for an administrator to see and to change (custom workflow actions).

Either way the write into Intacct goes through its web services layer, which is the documented interface for posting transactions (Sage Intacct web services).

The write must be idempotent, keyed on the milestone identifier, because a webhook can be delivered more than once. An integration that posts on every delivery will double-recognise revenue, and the error surfaces at close rather than at the point it is made.

Failure Under Amendment

Contracts change, and amendment is where this architecture is tested against something other than the happy path.

A scope increase adds a milestone to an agreement that has already closed. If milestones are line items, adding one after close alters the record of what was sold, and that pressure is what moves higher-volume implementations to a custom object. If milestones are a custom object, the new record is created, a new schedule line is proposed, and the sold record stays intact.

A date slip moves a milestone without changing its value, and this is the common case. It changes which period the revenue lands in, which is exactly the thing finance needs to know early, so the date has to flow through rather than be corrected by hand in the finance system. A date corrected only in Intacct leaves the CRM asserting a delivery date the business no longer expects.

A cancellation after partial delivery is the case that cannot be automated, and it should not be. Reversing recognised revenue is a judgement with audit consequences, so the integration's correct behaviour is to stop and raise the case for a person to settle rather than to post a reversal.

The Business Case

What this buys is a single close date that does not require reconciliation by hand, and a forecast that finance can use without rebuilding it. The recurring saving is the days each month spent matching a spreadsheet of signed contracts against the ledger, and the one-off saving is the class of argument in which two departments present different revenue figures for the same quarter.

What it costs is a build against two APIs, a reconciliation key on four object types, and a standing obligation: every change to the milestone model in HubSpot is now a change to something finance depends on. That is a governance cost rather than a licence cost, and it is the one teams underestimate.

The case is strongest where contracts are large, milestone-based, and few enough that each error is material — professional services, construction, capital equipment, implementation-heavy software. It is weakest where revenue is subscription-based and ratable, because a subscription schedule is generated from a start date and a term, and none of the structure described here is warranted to produce it.

Verification Before the First Close Depends On It

Six checks, each derived from a property of the arrangement above rather than from a build this practice has run.

Post a contract with four milestones and confirm the proposed schedule sums to the deal amount, to the cent. Rounding distributed across milestones is where a few pence goes missing, and a close will not balance because of it.

Deliver the same webhook twice and confirm one journal entry, not two.

Amend a milestone date and confirm the schedule moves period without changing total value.

Change a customer's legal name in HubSpot and confirm the integration still finds the right Intacct customer, which it will only do if matching is on the key rather than the name.

Close a deal with no milestones and confirm the integration declines it rather than posting the full amount to the close date.

Reconcile one full month end to end, comparing the ledger against a hand-built schedule, before anyone is permitted to rely on the automated figure.

Boundaries of This Approach

This describes the mechanics of moving a milestone structure across a system boundary. It does not state which revenue standard applies to a given contract, nor when a performance obligation is satisfied, and those are determinations for the firm's accountants rather than for an integration.

It assumes throughout that milestones are discrete and separately acceptable. Contracts recognised on percentage of completion need a different input — measured progress rather than a binary acceptance — and the schedule is then derived from a cost or effort measure that HubSpot does not hold at all.

It also assumes that one contract maps to exactly one deal. Master agreements with statements of work beneath them are a different object model, and flattening them to a single deal loses the level at which the commercial relationship is actually governed.

In Summary

A milestone contract has structure, and a single deal amount does not carry it. Express the milestones as rows — line items at low volume, a custom object once they need their own status — and let the deal hold the commercial fact rather than the accounting one.

Move three things across the boundary at close: customer identity, milestone values, milestone dates. Keep the recognition schedule in Sage Intacct, where it can be audited and restated. Write one reconciliation key in each direction and never match on a name.

Trigger recognition on milestone acceptance rather than on deal close, make the write idempotent, and refuse to automate a cancellation that reverses revenue already recognised.

Recognised revenue on the deal record is the cheapest thing to remove and the one worth removing first. While it sits there, any salesperson can report a finance figure without reconciling it, and at some point one of them will.

HubSpot services

Onboarding, implementation, integration, migration, administration and training, each scoped and priced before the work begins

WEEKLY PROGRAM

RevOps Office Hours

A recurring weekly RevOps operating program. Live support plus hands-on HubSpot implementation work.

$1,500/mo
Monthly Operating Program
  • 1 live Office Hours session per week
  • 4 hours of hands-on implementation work per month
  • Hours allocated against priorities agreed at the start of each period
  • Recurring monthly cadence