What order-to-cash visibility actually means in practice

Most requests for a HubSpot SAP integration start with a question that sounds simple: when a salesperson looks at a deal, why can they not see whether the goods have shipped and the invoice has been paid. In practice the sales team works in HubSpot, the finance and operations teams work in SAP Business One, and the link between them is a person copying figures into a spreadsheet each Monday morning. That spreadsheet is the integration, and it fails in the usual ways — it is out of date by Tuesday, it does not survive holidays, and nobody can say with confidence which column is authoritative.

Order-to-cash visibility is not a single field. It is a chain of documents in SAP Business One — quotation, sales order, delivery note, A/R invoice, incoming payment — each of which changes state independently, and each of which a different audience cares about. The sales team wants to know whether the order was accepted and when it shipped. The finance team wants to know the invoice number, the due date and the outstanding balance. The account manager wants to know whether a customer is on credit hold before spending three weeks on a proposal.

Before scoping anything, it is worth writing down which of those questions the business is actually asking, because they have different costs. Reflecting an order number and its open or closed status back onto a deal is a modest piece of work. Reflecting line-level delivery progress on partially shipped orders, with correct handling of cancelled rows and back orders, is considerably more. A fixed scope is only meaningful once that distinction has been made in writing.

The other decision to make early is direction of truth. Our recommendation in almost every case is that HubSpot owns the opportunity up to the point of acceptance, and SAP Business One owns everything from the sales order onwards. Once a document exists in the ERP, the ERP's value is the value. HubSpot displays it, but does not edit it. That single rule removes most of the conflict handling that otherwise consumes a project.

Mapping deals to documents: where a HubSpot SAP integration succeeds or fails

The technical work of creating a sales order is trivial compared with the work of deciding what goes on it. A HubSpot deal is a loose record: an amount, a close date, a pipeline stage, an associated company, and a set of line items that may or may not have come from a product library. A sales order in SAP Business One is a strict accounting document requiring a valid CardCode, a document series, a currency and exchange rate, a tax code on every row, an item code or a service description, a warehouse, a unit of measure and a price that the system is willing to accept. Discovery is largely the exercise of closing that gap field by field.

Customer identity is the first hard problem. HubSpot companies are created by marketing, by sales and sometimes by form fills, and there will be duplicates. SAP Business One business partners are created by finance with credit terms and tax attributes attached. Matching on company name is not adequate. The workable pattern is to store the CardCode on the HubSpot company record as a read-only property, populated either by an initial reconciliation exercise or by a controlled 'create customer in SAP' step that a finance user approves. New business partners should not be created silently by an integration; payment terms and credit limits are a commercial decision.

Product mapping deserves the same discipline. If HubSpot's product library is maintained independently of the SAP Business One item master, prices will drift and the integration will start rejecting orders on price list mismatches. The cleaner arrangement is to treat the item master as the source and push a nightly one-way feed of item code, description and price-list price into HubSpot products, with the ERP item code carried as a property on every product and line item. Where sales are genuinely bespoke, allow a service-type line with a fixed revenue account rather than inventing item codes on the fly.

A small number of fields cause a disproportionate share of failed postings, and they are worth listing explicitly in the mapping document:

  • Document series — SAP Business One allocates DocNum from a numbering series. The integration must be assigned a specific series agreed with finance, not left to the default, or numbering will interleave with manually keyed documents in ways that complicate audit.
  • Tax codes — VAT treatment is driven by the business partner and the item, not by the CRM. Determine the tax code in SAP Business One wherever possible and let the ERP calculate the tax total, rather than sending a figure calculated in HubSpot and reconciling two answers.
  • Currency and rate — If a customer transacts in a foreign currency, the document currency must match the business partner's currency and the rate must exist for the posting date. A missing exchange rate is a common cause of first-of-month failures.
  • Credit limit and hold — An order that breaches a credit limit will be rejected or blocked. That is not an integration error and should not be retried indefinitely; it should surface on the deal for a human to resolve.
  • Deal reference on the document — Store the HubSpot deal ID in a user-defined field on the sales order header, for example U_HS_DealId. It is the anchor for every subsequent status update, and it makes duplicate prevention a simple lookup rather than a guess.

The mechanics: Service Layer, the CRM API and how change is detected

SAP Business One exposes the Service Layer, an OData interface published at a path of the form /b1s/v1/ on the licensed server. Authentication is a Login call carrying CompanyDB, UserName and Password, which returns a session cookie with a finite timeout — thirty minutes by default — so any client must handle re-authentication cleanly and must not open a new session per request, as sessions consume licences. Clustered installations also return a routing cookie that has to be echoed back on subsequent calls. Availability of the Service Layer depends on your version and database platform; on older installations, or where a required operation is not exposed, the DI API remains the fallback, with the operational consequence that it needs a Windows host and a licence slot. The SAP Business One Integration Framework is a third route where one is already in place and maintained.

On the HubSpot side, a private app access token with narrowly defined scopes is the appropriate credential — read and write on deals, companies and line items, plus schema access if custom objects are in scope. Rate limits are applied per account over a ten-second window with a separate daily cap, and the precise figures depend on subscription tier, so any integration that intends to backfill history must use the batch endpoints, which accept up to one hundred records per call, and must implement backoff on a 429 rather than retrying immediately. Note also that HubSpot custom objects require an Enterprise subscription; if you are on Professional, invoice detail has to live in deal properties or a related record type you already have, and that constraint changes the design.

Change detection is the part most often underestimated, because neither platform gives you exactly what you want. HubSpot webhooks are a feature of public apps rather than private apps, so a private-app design either polls the CRM search endpoint on hs_lastmodifieddate with an overlapping watermark, or uses a workflow webhook action where the customer's Operations Hub subscription allows it. SAP Business One has no outbound webhook at all. The practical approach is a polling worker that filters documents on UpdateDate and UpdateTime, with a deliberate overlap window of a few minutes and idempotent handling so that reprocessing the same document is harmless.

Cadence should be set by business need rather than by instinct. Order creation from a closed-won deal is usually near real time, triggered within a minute or two of the stage change. Status reflection back into HubSpot — delivery, invoice, payment — is almost always fine on a fifteen or thirty minute cycle, and running it less often materially reduces API consumption. Ageing and outstanding balance can be a single overnight pass. Three different cadences in one integration is normal and is cheaper than one aggressive cycle doing everything.

Reflecting status back: the fields finance and sales actually use

The temptation is to mirror everything. Resist it. A deal cluttered with thirty read-only ERP fields is ignored just as thoroughly as a blank one. In our experience a working set is roughly a dozen properties, grouped so that they render as a single card on the deal record: SAP order number and DocEntry, order status, order date, delivered date, invoice number, invoice date, due date, invoice total, amount outstanding and days overdue. Each is read-only in HubSpot, updated only by the integration, and each is populated from the ERP value without recalculation.

Order status needs a deliberate translation rather than a raw copy. SAP Business One closes a sales order when it has been fully delivered or manually closed, and marks cancellation separately, which means the internal status alone does not distinguish 'shipped' from 'cancelled by the customer'. Derive a small vocabulary that means something to a salesperson — Accepted, Part Shipped, Shipped, Invoiced, Paid, Cancelled, On Hold — and document the exact ERP conditions behind each one. Where an order is partially delivered, compare open quantity against ordered quantity at line level to decide between Part Shipped and Shipped.

Invoice and payment status follow the same logic. The A/R invoice carries its own document status and a paid-to-date figure, and an incoming payment applied against it moves both. Outstanding balance on the deal should be derived from the invoice rather than inferred from the existence of a payment record, because partial settlements and credit notes are common and the arithmetic is not something the integration should attempt independently. If multiple invoices can arise from one order — staged billing, part shipments — a single set of invoice fields on the deal will not hold the data honestly, and either a child record type or a summary-plus-link approach is required.

One further field earns its place: a link. Storing a deep link to the document in SAP Business One, or at minimum the DocNum in a format the finance team recognises, removes the phone call that otherwise follows every question. It costs nothing to populate and it is the single property users mention most often after go-live.

Failure handling, acceptance testing and the first month

A production integration is defined by what it does when something is wrong. Classify failures on arrival: a transport error or an expired session is transient and should be retried with backoff; a validation failure from the Service Layer, such as an unknown item code, a missing exchange rate or a credit limit breach, is a business exception and must stop, be recorded against the deal, and be visible to a named person. Silent retries against a business exception generate noise and, occasionally, duplicates. Every message that cannot be processed after its retry budget belongs in a dead-letter store with the full request and response preserved, because reconstructing a failure from a log line three weeks later is rarely possible.

Acceptance testing should be written as scenarios in business language and agreed before build, not demonstrated afterwards. A credible set includes: a standard single-line order; a multi-line order with mixed VAT treatment; a foreign-currency order; an order for a customer at their credit limit; a part-shipped order becoming fully shipped; an order cancelled after acceptance; an invoice part-paid then settled; a credit note against a paid invoice; and a duplicate stage change that must not create a second sales order. Each scenario should specify the expected state in both systems, and each should be executed against a copy of the live company database rather than a demo dataset, because the reference data is where the surprises live.

Plan an initial reconciliation before go-live rather than after. Export open orders and open invoices from SAP Business One, match them to open deals in HubSpot, and accept that a proportion will not match — abandoned deals, orders placed by phone with no CRM record, historic duplicates. The point is not to achieve a perfect match but to decide, explicitly, what the integration will do with each unmatched category. A running reconciliation report that counts documents created, documents updated, exceptions outstanding and unmatched records should then be produced daily and sent to a finance mailbox for the first month.

Finally, treat the handover as part of the deliverable. The mapping document, the exception vocabulary, the credential rotation procedure for the private app token and the Service Layer service user, and the runbook for the three most likely failures should all be handed over with the code. An integration that only its author can diagnose is a liability regardless of how well it performs on the day it goes live. If you would like to discuss a specific HubSpot and SAP Business One requirement, we are on 01303 883111 or hello@api-integrations.co.uk.

Key points

  • Let HubSpot own the opportunity and SAP Business One own everything from the sales order onwards; a single direction of truth removes most conflict handling before it starts.
  • Customer, item, tax code, currency and document series mapping is where these projects actually succeed or fail — resolve it in discovery, not during build.
  • Reflect a small, deliberate set of read-only status fields back to the deal, classify failures as transient or business exceptions, and run a daily reconciliation report for the first month after go-live.