Integration · Finance
How to build a revenue recognition feed
Revenue is recognised from a spreadsheet built off invoice totals, and the auditor asks how the allocation was derived. Below is the model behind a feed that answers, the prompts that build it, and the parts that only bite once it is live.
Built with Tray Headless
- System Salesforce CPQ
- Step Extract obligations
- Step Allocate on SSP
- Step Build schedules
- System NetSuite
Performance obligations travel from the contract instead of being inferred from an invoice, and every allocation records the inputs behind it.
The short answer
What is a revenue recognition feed?
A revenue recognition feed comes down to four things: performance obligations carried from the contract instead of inferred from invoice totals, allocation against standalone selling prices with the inputs recorded, schedules generated per obligation instead of per invoice, and a reconciliation from bookings through to recognised revenue every period. Most of these come apart on starting from the invoice. An invoice is a billing event and a performance obligation is an accounting one, and they rarely correspond.
What matters here
- Start from the contract, not the invoice. Billing and recognition are different events that rarely correspond.
- Record the allocation inputs. An auditor asks how a figure was derived, and a spreadsheet formula is not an answer.
- Generate schedules per obligation. One contract can hold several with different patterns and dates.
- A modification is a new allocation, not an adjustment. Getting that wrong is a restatement.
- Reconcile bookings to billings to revenue every period, and explain the differences rather than plugging them.
Who this is for
You run revenue accounting or finance systems. Recognition runs from a spreadsheet, the allocation logic lives in somebody's head, and each audit is a reconstruction exercise.
How it works in practice
The path from a contract being signed to revenue being recognised correctly.
- 1
Performance obligations come from the contract
Each distinct promise, with its own delivery pattern, not a line on an invoice.
- 2
Standalone selling prices are applied
From a maintained table, with the basis recorded per allocation.
- 3
The transaction price is allocated across obligations
Proportionally, with every input stored for reconstruction.
- 4
A schedule is generated per obligation
Ratable over term, at a point in time, or on usage, each with its own dates.
- 5
Modifications trigger a new allocation
Prospective or cumulative catch-up per the standard, never an ad hoc adjustment.
- 6
Bookings, billings and revenue reconcile every period
With deferred revenue explained rather than balanced.
What the feed is made of
Four parts, and the second is what makes an audit survivable.
Obligations from the contract
Each distinct promise with its own pattern. An invoice is a billing event and tells you nothing about how revenue should be recognised.
Recorded allocation
The standalone prices used, the version of the table, and the arithmetic. An auditor asks how, and a formula in a spreadsheet is not an answer.
Schedules per obligation
Ratable, point in time or usage based, each with its own start and end. One contract routinely holds several.
A full reconciliation
Bookings to billings to recognised revenue to deferred, with differences explained rather than plugged.
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
First, take obligations from the contract
An invoice is a billing event, not an accounting one.
Headless skills
build-workflowUse build-workflow. The systems in play are Salesforce CPQ and NetSuite, or whatever we run in those seats. I need contract line items with products, quantities, prices and term dates, and from the ERP the revenue arrangement and schedule objects. Extract performance obligations from the contract, not from invoices: each distinct promise, its delivery pattern, its start and end, and whether it is delivered over time or at a point in time. A twelve month subscription invoiced annually in advance is one obligation recognised over twelve months and one invoice. A professional services engagement invoiced monthly might be one obligation recognised on completion. Starting from the invoice gets both wrong.
- 2
Allocate against standalone prices, and record the working
An auditor asks how the figure was derived.
Headless skills
build-workflowtray-patternsUse build-workflow. Hold standalone selling prices as a versioned table with the basis for each: observed price, adjusted market assessment, or cost plus margin. Allocate the transaction price across obligations in proportion to standalone price, and store on every allocation: which prices were used, which version of the table, the resulting percentages, and the amount allocated per obligation. Handle a discount deliberately. Where it is not attributable to a specific obligation, allocate it proportionally, and where the contract says otherwise, follow the contract and record that it did. The stored working is the point. Recomputing an allocation two years later against today prices produces a different number, and being unable to reproduce the original is what turns an audit question into a finding.
Version the price table. Allocations are computed against the table as it was, and without versioning the whole history becomes irreproducible.
- 3
Generate a schedule per obligation
One contract routinely holds several with different patterns.
Generate a schedule per obligation rather than per contract: Ratable over the term, monthly, with proration on partial periods At a point in time, on delivery or acceptance, with the trigger recorded On usage, from metered consumption On milestones, where the contract defines them Each carries its own start and end, which frequently differ within one contract. A subscription starting in March and an implementation completing in May belong to the same contract and to different schedules entirely. Post the periodic recognition and the deferred balance, with each entry linking back to the obligation and the allocation that produced it.
- 4
Handle modifications as new allocations
Getting this wrong is a restatement.
Headless skills
tray-gotchasUse tray-gotchas, then handle contract modifications properly: A mid-term upgrade adding a distinct obligation at its standalone price is treated as a separate contract A modification that is not distinct requires a reallocation across the remaining obligations with a cumulative catch-up A downgrade or partial termination requires reversing the unrecognised portion, not the recognised one A renewal is a new contract with a new allocation, not a continuation Never adjust a schedule directly. Every change produces a new allocation linked to the original, with the reason recorded. This is the part where an error becomes a restatement rather than a correction, and it is worth being slow about.
- 5
Reconcile bookings through to revenue
Deferred revenue should be explained, never balanced.
Reconcile every period: Bookings against contract value entered Contract value against total allocated, which must agree exactly Allocated against scheduled Scheduled against posted revenue Opening deferred plus billings minus revenue against closing deferred Every difference must be explainable, and none may be plugged. A balancing entry in revenue recognition is the entry an auditor finds. Report per period: revenue by obligation type, deferred balance and its ageing, contracts with no allocation, allocations with no schedule, and modifications processed with their treatment. Contracts with no allocation is the one to watch. Each is revenue nobody is recognising.
- 6
Validate, then hand the price table to revenue accounting
Because standalone prices change and the table is theirs.
Run the per-step schema checks and the whole-workflow audit before this touches production. Run parallel with the existing spreadsheet for two periods and reconcile every difference before trusting it. Then open the same workflow in Tray Build so revenue accounting can maintain the standalone price table and the obligation templates in the visual canvas.
What it connects to
The contract defines the obligations, the ERP holds the ledger, and the working has to survive both.
Salesforce CPQ
Read contract lines, products, term dates and any ramp, which is where obligations come from.
Reads
NetSuite
Create revenue arrangements and schedules, post recognition and hold the deferred balance.
Reads and writes
Salesforce
Read the closed opportunity and write the arrangement reference back for the account team.
Reads and writes
Snowflake
Hold allocations with their inputs and versions, so a figure from two years ago is reproducible.
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 produces reported revenue. It is audited every year and restated when wrong.
It runs on the platform, not on somebody's machine
Allocation and scheduling run as contracts land, and the period reconciliation runs on the close calendar, not when somebody opens a file.
Every allocation is reproducible
The prices used, the table version and the arithmetic stored together. Recomputing against today prices gives a different answer, and that gap is what becomes a finding.
Credentials are held by the platform, never hardcoded
Posting revenue entries is the most consequential write in the finance stack. Scoped narrowly, held in your workspace, rotated separately.
Revenue accounting own the price table
Standalone prices and obligation templates open in Tray Build, versioned, owned by the people who defend the policy.
Nothing is plugged
Every reconciliation difference is explained. A balancing entry in revenue recognition is precisely the entry an auditor looks for.
Questions people ask
Why start from the contract rather than the invoice?
Because an invoice is a billing event and a performance obligation is an accounting one, and they rarely correspond. An annual invoice for a twelve month subscription is one invoice and twelve months of revenue.
Why record the allocation inputs?
Because recomputing an allocation two years later against current standalone prices produces a different number. Being unable to reproduce the original is what turns an audit question into a finding.
Why a schedule per obligation?
Because one contract routinely holds several with different patterns and dates. A subscription starting in March and an implementation completing in May are the same contract and entirely different schedules.
How should modifications be handled?
As a new allocation linked to the original, prospective or with a cumulative catch-up depending on whether the added goods are distinct. Adjusting a schedule directly is how a correction becomes a restatement.
What should never happen in the reconciliation?
A plug. Every difference between bookings, billings, revenue and deferred must be explained, because a balancing entry in revenue recognition is exactly what an auditor is looking for.
Related guides
Revenue operations
How to build an order-to-cash integration
Closed-won to sales order to invoice to cash applied, as a CRM to ERP integration. Idempotent writes, a reconciliation loop, and the Headless prompts that build it.
Finance
How to build financial close automation
Model the dependencies instead of the checklist, pull the data before anybody asks, and show the critical path instead of a percentage. The prompts.
Last reviewed September 2026.