Integration · Revenue operations
How to build an order-to-cash integration
The deal is closed-won in the CRM and finance has not heard about it. The thinking behind order-to-cash as a CRM to ERP integration, the prompts that build it, and what it takes to keep it running.
Built with Tray Headless
- System Salesforce
- Step Validate the deal
- Step Sales order
- Step Invoice raised
- Step Payment applied
- System NetSuite
Validation happens in the CRM where the error is readable, and the order write is keyed so a retry cannot create a second one.
The short answer
What is an order-to-cash integration?
Order-to-cash at a software company is a CRM to ERP integration in four parts: a clean handoff from closed-won to a sales order, idempotent writes so a retry never creates a second order, a reconciliation loop that compares both systems rather than trusting the last write, and status flowing back so the account team can see the invoice. Most of these come apart on idempotency. A timeout on the order create is invisible until finance finds two orders for one deal at month end.
What matters here
- Make every write idempotent with a key derived from the opportunity. A retry must find the existing order, not create a second.
- Validate before you write. A missing tax code or an unmapped product fails in the ERP, where the error is least readable.
- Reconcile on a schedule. Event-driven sync is right until one event is missed, and nothing tells you it was.
- Write status back to the CRM. An account team that has to ask finance whether an invoice went out is the problem this solves.
- Never let the integration change a price. If the quote and the order disagree, that is a stop and a human, not a resolution.
Who this is for
You run revenue operations or finance systems at a software company. Deals close in the CRM and orders live in the ERP, and the gap between them is currently somebody rekeying, or a nightly job nobody trusts.
How it works in practice
The path from a deal closing to cash being applied against it.
- 1
Closed-won triggers a validation pass
Products mapped, tax code present, billing account resolvable, currency supported. Nothing is written until the whole thing checks out.
- 2
The sales order is created idempotently
Keyed on the opportunity id, so a retry finds the existing order rather than creating a second.
- 3
Line items map through a product mapping table
Maintained as data rather than in code, because the catalogue changes more often than the integration does.
- 4
The invoice reference flows back to the CRM
So the account team can answer a billing question without opening the ERP or asking finance.
- 5
Payment status is synced as it changes
Invoiced, part paid, paid, overdue. Renewal conversations depend on knowing which.
- 6
A nightly reconciliation compares both systems
Every closed-won opportunity in the period against every order, and the difference is a report somebody reads.
What order-to-cash is made of
This is a CRM to ERP integration with money attached. It is four parts, and the second one is what stops the month-end surprise.
A validated handoff
Product mapping, tax code, billing entity and currency all checked in the CRM, where the error is readable, before anything reaches the ERP.
Idempotent writes
An external key derived from the opportunity on every create. Retries are safe, and duplicate orders become impossible rather than unlikely.
A reconciliation loop
A scheduled comparison of both systems. Event-driven sync misses events, and only a reconciliation finds out.
Status flowing back
Order number, invoice number and payment state on the opportunity, so the commercial team is not dependent on finance for an answer.
The Tray Headless prompts
Paste these into Claude Code or Codex with the Tray Headless plugin installed. Each stage runs on its own. The systems named in them are the worked example rather than a requirement, and every prompt says so.
Once per project, run
/tray-workflows:set-workspace
to pick the workspace these build in. Point it at a sandbox first.
- 1
Set up, then learn both schemas properly
Every painful order-to-cash bug is a schema assumption.
Headless skills
build-workflowUse build-workflow. The systems in play are both Salesforce and NetSuite, or whatever we run in those seats. From Salesforce I need Opportunity, OpportunityLineItem, Account and Product2 with their real field names. From NetSuite I need SalesOrder, Invoice, Customer and Item, including which fields are mandatory and which are internal ids, not names. Do not assume a mapping between the two. Show me both, and I will tell you where they meet.
- 2
Validate in the CRM, before anything reaches the ERP
Because an ERP validation error is the least readable error in the stack.
Headless skills
build-workflowUse build-workflow. Trigger on Opportunity StageName changing to Closed Won. Validate before writing anything: Every OpportunityLineItem product exists in the product mapping table The account resolves to a NetSuite customer, or flag it for creation A tax code and a billing entity are present The currency is one NetSuite is configured for Amounts are non-zero and the total matches the sum of the lines If any check fails, stop, post to the deal desk channel with exactly what is missing, and write nothing. A validation failure in the CRM is a five minute fix. The same failure inside NetSuite is a support ticket.
- 3
Write the order idempotently
The single step that prevents the duplicate order nobody finds until month end.
Headless skills
tray-patternsCreate the NetSuite sales order with an external id derived from the Salesforce opportunity id. Before creating, look up that external id. If an order already exists, update it rather than creating a second one and return the existing reference. A timeout on the create must be safe to retry. Map line items through a mapping table held as data, not in code. The product catalogue changes far more often than this integration will, and a new SKU should not be a deployment. Write the NetSuite order number back to the opportunity as soon as it exists, so the link is visible from the CRM side immediately.
Idempotency is the whole game here. Without an external key, one network timeout produces two orders and finance discovers it during close.
- 4
Flow status back, and never change a price
So the account team stops asking finance, and the integration stays inside its remit.
Headless skills
tray-gotchasUse tray-gotchas, then sync status back to the opportunity as it changes: order created, invoiced with the invoice number, part paid, paid in full, overdue with the days outstanding. Two rules the integration must not break: Never change a price, a discount or a quantity. If the quote and the order disagree, stop and raise it. That is a commercial conversation, not a field update. Never create a customer in the ERP silently. Flag it for finance to create with the right entity, tax registration and payment terms. Handle the amendment case explicitly: an opportunity edited after close must update the order if it is still open, and stop if it has already been invoiced.
- 5
Reconcile nightly, because events go missing
Event-driven sync is right until one event is lost, and nothing announces that.
Headless skills
tray-patternsBuild a nightly reconciliation over the last 90 days: Every closed-won opportunity with no matching sales order Every sales order with no matching opportunity Orders where the total does not match the opportunity amount Invoices with no order, and orders invoiced more than once Report the differences with enough context to act, and alert if the count crosses a threshold instead of emailing a zero every morning. This is the safety net. Event-driven integration is correct until one event is dropped, and the only thing that ever finds that is a reconciliation that assumes nothing.
- 6
Check it end to end, then hand the mappings to finance systems
Because the product catalogue is theirs, not the integration author’s.
Run the per-step schema checks and the whole-workflow audit before this touches production. Then open the same workflow in Tray Build so the finance systems team can maintain the product mapping table and the tax code rules in the visual canvas. Those change with every catalogue update, and none of them should require a coding assistant.
What it connects to
Two systems of record and the money moving between them, plus the places people need to be told.
Salesforce
Read the closed-won opportunity, its line items and the account. Write back the order number, the invoice reference and payment status.
Reads and writes
NetSuite
Create the sales order idempotently, read the invoice and payment state. Usually the system of record for the order itself.
Reads and writes
Salesforce CPQ
Read the approved quote, ramp structure and any negotiated discount, so the order reflects what was actually agreed.
Reads
Stripe
Read payment capture where collection happens outside the ERP, so paid means paid rather than invoiced.
Reads
Slack
Tell the deal desk when a validation fails, and the account team when an invoice goes out.
Writes
Snowflake
Land orders, invoices and their timestamps, so days from close to invoice is a query instead of a spreadsheet.
Writes
Same build, other stacks
The design does not change if you run something else in one of these seats. The same prompts build it against Microsoft Dynamics 365, SAP S/4HANA, Google BigQuery, Microsoft Teams, HubSpot or Oracle.
Named systems are the ones most teams run, not the only ones that work. Each is an authentication in your Tray workspace, referenced by name, so the workflow never holds a credential. Where we have a connector page, the name links to it.
Running it in production
This integration moves money between two systems of record. It gets audited.
It runs on the platform, not on your laptop
Retries, backoff and the nightly reconciliation execute on the same engine, with a run history for every order created.
Every write is attributable
Which run created which order, with the platform audit trail behind it. Finance asks this question during close, every quarter.
Credentials live in the workspace, never in the repo
The ERP credential can create financial records. It lives in your workspace, scoped to what the integration needs, and rotatable without a rebuild.
Finance systems own the mappings
The product mapping and tax rules open in Tray Build. Finance systems maintain them, because a new SKU should be bookable the day it is created.
Failures reach a person, not a log
A validation failure blocks a deal from being invoiced. It needs to reach the deal desk the same hour, not appear in a report the next morning.
Questions people ask
Where does this start, if we already have quote-to-order?
At the order. Quote-to-order covers approval, signature and turning a signed quote into a booked order. This picks up from that order and runs to cash: invoice raised, payment applied, and status back on the account. Build either one on its own.
Does order-to-cash need EDI?
Not in this path. EDI belongs to retail and distribution supply chains, where a supplier receives purchase orders from large customers. A software company runs order-to-cash between CRM, CPQ, ERP and a payment system, and no EDI is involved.
Why does idempotency matter so much?
Because a network timeout on the order create is invisible. Without an external key derived from the opportunity, a retry produces a second order, and nobody finds out until finance closes the month.
Why validate in the CRM rather than letting the ERP reject it?
Because an ERP validation error is the least readable error in the stack, and it arrives after the deal is closed. Checking the product mapping and tax code in the CRM turns a support ticket into a five minute fix.
Should the integration ever change a price?
No. If the quote and the order disagree, stop and raise it. Price is a commercial agreement, and an integration silently reconciling one is how a customer gets invoiced for something they did not sign.
Why reconcile if the sync is event-driven?
Because events get dropped and nothing announces it. A nightly comparison of closed-won opportunities against sales orders is the only thing that finds the deal that never became an order.
Vibe-coding app guides
Vibe-code an entire app with Helix
This moves the data between systems. It does not give anybody a screen to work in. Build that app in Claude Code, Codex or Cursor, then deploy and run it governed on Tray Helix. Same kind of guide, same kind of prompts.
How to build an order management tracker (opens helix.tray.ai in a new tab)
Further reading
Background on the same subject, for the case rather than the build.
Related guides
Revenue operations
How to build a quote-to-order integration
Approved quote to signed contract to booked order, with the approval state carried through and amendments handled. The Headless prompts that build it.
Revenue operations
How to build lead routing that assigns in seconds
Match the lead to an account first, evaluate rules in a fixed order, catch what they miss, and time it from arrival. The Headless prompts that build it.
Revenue operations
How to build a CPQ to ERP sync
Map the product catalogue as data, carry ramps and proration intact, keep both catalogues aligned, and fail loudly on an unmapped SKU. The prompts that build it.
Revenue operations
How to build a billing to CRM sync
Put invoice and payment state where the account team works, without letting the CRM become a second billing system. The Headless prompts that build it.
Finance
How to build a revenue recognition feed
Carry performance obligations instead of invoice totals, keep the allocation reconstructable, and reconcile bookings to revenue every period. The prompts.
Last reviewed September 2026.