DEV Community

Jake Hoffman
Jake Hoffman

Posted on

Stripe annual subscriptions: how to recognize the revenue correctly, with the actual journal entries

A customer pays $1,200 for a year of your SaaS. Stripe takes its cut and drops the rest in your balance. So how much of that $1,200 is revenue this month?

If you answered $1,200, your books are wrong, and an accountant will tell you so. You collected the cash, but you haven't earned it yet. You owe the customer twelve months of service, and the revenue is earned as you deliver it. Booking the whole year in January overstates that month and leaves the next eleven looking empty. This is revenue recognition, and for annual plans it's the part of Stripe bookkeeping people get wrong most often.

Here is how to do it correctly, with the real entries.

The cash lands in a liability, not revenue

When the annual invoice is paid, the money you collected is a liability until you earn it. It goes to an account called Deferred Revenue. A $1,200 annual plan, after Stripe's processing fee:

Dr  Stripe Clearing        $1,164.00
Dr  Stripe Processing Fees    $36.00
Cr  Deferred Revenue               $1,200.00
Enter fullscreen mode Exit fullscreen mode

Two things worth noticing. The Stripe fee is a business expense, on its own line, not netted against revenue, so your top line still reflects what the customer actually paid. The cash that reaches your balance is the amount minus that fee, which is why the clearing account gets $1,164 and not the full $1,200. The credit is the full $1,200, and it goes to Deferred Revenue, a liability, because right now you owe a year of service and have earned none of it.

Then you recognize it, one month at a time

Each month, as you deliver the service, you move one twelfth from the liability into actual revenue:

(every month, for twelve months)
Dr  Deferred Revenue        $100.00
Cr  Subscription Revenue           $100.00
Enter fullscreen mode Exit fullscreen mode

Twelve of those, dated one per month, and the liability drains to zero exactly as the year completes. That's all revenue recognition is: cash up front sitting in a liability, released to revenue as it's earned. Accountants call it ratable recognition, and it's what ASC 606 and IFRS 15 ask for. Your monthly income statement now shows the $100 you actually earned, instead of a $1,200 spike followed by eleven flat months.

The three details that trip people up

The concept is simple. The places it goes wrong are in the details.

Sales tax is not revenue. If Stripe Tax collected tax on the invoice, that money isn't yours. You're holding it for the tax authority. It comes out to its own liability at the moment of collection, and it is not deferred, because you owe it now regardless of when you earn the revenue. A $1,000 plan plus $100 of tax:

Dr  Stripe Clearing        $1,067.00
Dr  Stripe Processing Fees    $33.00
Cr  Sales Tax Payable                $100.00
Cr  Deferred Revenue               $1,000.00
Enter fullscreen mode Exit fullscreen mode

Only the $1,000 gets a recognition schedule. The $100 of tax sits in its liability until you remit it.

The schedule has to reconcile to the penny. $1,000 over twelve months is $83.33 a month, and twelve times $83.33 is $999.96. If you post $83.33 twelve times, four cents are stranded in Deferred Revenue and the account never returns to zero. The fix is to let the last month absorb the remainder: eleven months of $83.33 and a final month of $83.37, which sums to exactly $1,000. It's a small thing that quietly breaks a lot of homegrown schedules, and it's the first place I'd look if your deferred revenue balance won't clear.

A one-time fee needs its own check. Being billed once does not always mean the fee is earned right away. If the customer receives a separate service, such as a finished consulting session, you may recognize that fee when you deliver it. If the fee only covers work that gets the customer ready for the annual service, ASC 606 and IFRS 15 often treat it as an advance payment for that future service. Recognition depends on what the customer receives, not only on how the Stripe line item is dated. Ledgerly treats an instant-period line as earned now, so use that shape only when the line truly represents a separate service and have an accountant check the setup.

You shouldn't be doing this by hand

The mapping from a Stripe event to these entries is deterministic. The same invoice always produces the same journal entry, which makes it exactly the kind of work that should be automated instead of reconciled by hand every month.

That's the problem I built Ledgerly for. It's an open-source TypeScript engine that turns Stripe webhook events into these balanced entries and exports them to QuickBooks Online or Xero, including the twelve-month schedule, the tax split, and the rounding remainder above. It's Apache-2.0 and self-hostable, so it runs on your own infrastructure with no third party sitting in your financial data.

You can watch it map a real annual invoice in about ten seconds, with no install:

cat invoice.json | npx ledgerly
Enter fullscreen mode Exit fullscreen mode

Pipe any Stripe event in and it prints the entry it produces, or the exact QuickBooks or Xero JSON with --qbo or --xero. There are sample events in the repo if you don't have one handy.

Before you ship it

Chart of accounts and tax treatment vary by business and by jurisdiction, so run your setup past an accountant before you rely on it. The mechanics above are standard, but the account names and the tax handling depend on where you are and how you're registered.

If you keep books for a SaaS and any of these entries looks wrong to you, I'd genuinely like to hear it. Getting the accounting right is the whole point, and the corner cases are where it's easy to be quietly off.

Repo: https://github.com/jakethehoffer/ledgerly

Top comments (3)

Collapse
 
mihirkanzariya profile image
Mihir kanzariya

The stranded four cents is the sharpest bit here, because that bug doesn't surface until months later as a deferred balance that refuses to clear, and calling it the first place to look is exactly right.

One case worth adding sits upstream of the schedule: a mid-term subscription change. If a customer upgrades, downgrades, or changes seat count in month five, Stripe prorates it and writes credit and debit lines against the remaining term, so both what they're entitled to and what you still owe them shift on that date. A schedule keyed to the original $1,200 invoice has no idea that happened and will keep releasing $100 a month for the rest of the year against a contract that no longer exists in that form. Mid-term cancellations and refunds have the same shape, where the remaining deferred balance has to be reversed instead of continuing to run.

How the proration itself should be booked is your accountant's call, but on the Stripe side the takeaway is that the schedule gets rebuilt from the change date rather than continued, and seat changes are routine enough in SaaS to plan for.

Collapse
 
jake_hoffman profile image
Jake Hoffman

You’re right that booking the proration invoice is only half the job. Ledgerly handles the new credit and charge lines, but it does not yet cancel and rebuild the unused part of the original yearly schedule, so this is a real gap. The same problem can happen after a cancellation when no credit note is made. In your system, do you replace the rest of the old schedule, or keep it and add a second schedule for the change?

Collapse
 
mihirkanzariya profile image
Mihir kanzariya

Quick correction before I answer: we don't run a deferred-revenue ledger at all, Referralful is affiliate software for SaaS, so I'm coming at this from the Stripe subscription and webhook side rather than the books.

On your question, I'd pick neither of the two. Close the original schedule out at the change date and open a new one for the amended terms. Keeping the old schedule running and adding a second is the one that bites, because the original keeps releasing revenue against a term that no longer exists, and mutating the original in place loses the record of what was actually contracted first.

Close-out plus a new schedule also mirrors the shape Stripe already hands you, since proration arrives as new invoice line items rather than an edit to the original invoice, so your ledger stays aligned with the source system instead of drifting from it. The practical payoff is that each schedule's deferred balance can be proven to zero on its own, which is exactly the reconciliation you described chasing with the stranded four cents, and a schedule that has been amended twice never resolves that cleanly.

Cancellation is the same shape: close out at the cancellation date so the unearned remainder comes off instead of continuing to run, though whether you book that as a credit note or a reversal is your accountant's call rather than a modelling one.