Client work

/ Case study / Operations platform

An agency operations platform that replaced four SaaS tools and a spreadsheet

Safock built an internal operations platform that runs a software development agency from signed deal to settled invoice. A deal creates the client and project, developers log time against it, and invoices generate and settle through Stripe in the same app. It replaced four SaaS subscriptions and the master spreadsheet that sat between them.

Client
A software development agency
Name withheld by agreement with the client
Industry
Professional services, software development
Stack
Next.jsTypeScriptSupabasePostgreSQLStripeReact Query

What it includes

  • Postgres row-level security
  • Idempotent Stripe webhooks
  • Deal-to-settlement workflow
  • Live budgets and margins
  • Invoices from logged time
  • Full-stack Next.js app
Client work
Professional services, software development

/ At a glance

  • 01Four SaaS subscriptions retired
  • 02Zero exports or manual reconciliation in billing
  • 03One source of truth for the team and the client
  • 04Two roles enforced by Postgres row-level security
On this page
  1. 01Four tools and a spreadsheet, reconciled by hand every month
  2. 02One app from signed deal to settled payment
  3. 03Access rules enforced by Postgres row-level security
  4. 04Stripe billing that always settles on the right state
  5. 05Live budgets for every project
  6. 06The monthly close stopped being a project

01The challenge

Four tools and a spreadsheet, reconciled by hand every month

The agency ran on disconnected third-party tools. Time tracking lived in one, invoicing in another, and deals were tracked by hand. A master spreadsheet tied them together at the end of each month.

Every billing cycle followed the same manual routine:

  1. 01

    Export time from the tracker.

  2. 02

    Reconcile it against the terms of each deal.

  3. 03

    Generate the invoices.

  4. 04

    Send payment links.

  5. 05

    Chase the payments that did not arrive.

Budget tracking happened after the fact. Knowing whether a project was on budget meant doing the maths once the month had closed. With several client projects running in parallel, the monthly close had grown into a part-time job.

02The solution

One app from signed deal to settled payment

We built a single Next.js application with typed entities from the database up. Its workflow follows how the agency already operates. Admins and developers work in the same app at one URL, and each sees only what their role allows.

Because time is logged against the same records that hold the deal terms, budgets update live as developers work. Invoices draw on that data directly, so nobody exports anything to build them. Paid, failed and refunded states flow back into the app through Stripe webhooks.

  1. 01

    DealA signed deal starts everything.

  2. 02

    ClientCreated from the deal.

  3. 03

    ProjectSpun up to deliver what the deal promised.

  4. 04

    TimeDevelopers log hours against the project's tasks.

  5. 05

    InvoiceGenerated from the same time and deal data.

  6. 06

    SettlementPaid through Stripe, with the status synced back.

03How it works

Access rules enforced by Postgres row-level security

The agency operations platform has two strictly scoped roles. Admins manage users, projects, clients, deals and invoices. Developers see only the projects they are assigned to, and log time against them. Clients, deals and invoices stay outside a developer's view.

Those boundaries live in PostgreSQL as row-level security policies. Every request is filtered at the data layer, whether it comes from the app or a direct API call, and the database refuses to return rows a user is not allowed to see.

This takes access checks out of the application code. Every query is safe by default. A bug in the interface cannot leak another project's data, because the database never sends it.

04How it works

Stripe billing that always settles on the right state

Invoices are generated in the app and routed to Stripe's hosted checkout. Stripe webhooks then report paid, failed, partially refunded and disputed states, which update the deal record. The agency's view of what each client has paid stays current without anyone opening the Stripe dashboard to check.

Most of the engineering went into keeping the local record correct when a webhook arrives twice, arrives out of order, or turns up weeks later after someone refunds a payment by hand in the Stripe dashboard.

The cycle that used to take manual exports, sends and follow-ups is now one button, and the record corrects itself as money moves.

  1. 01

    Idempotency keys on every state change make duplicate events harmless.

  2. 02

    A small state machine encodes which status transitions are allowed.

  3. 03

    Repeated, out-of-order and late events all converge on the true final state.

05How it works

Live budgets for every project

Each project view tracks budget, costs, profit and margin live, updated as time is logged. Clients are added once and connected to their projects and invoices, so the team and the client read the same numbers in the same place.

Safock delivered the platform as a solo full-stack build, covering the data model, the access policies, the interface and the Stripe integration.

06The outcome

The monthly close stopped being a project

The agency retired four SaaS subscriptions and the spreadsheet that connected them. The manual reconciliation that used to swallow the close cycle is gone, since nothing needs exporting or matching any more.

Budgets now show where each project stands during the month, so overruns surface before month end. Invoices come from the time developers were already logging. Billing has dropped out of the team's standups entirely.

/ FAQ

About this project

Why enforce access rules in the database instead of the application?

Rules in the database apply to every request, including direct API calls, so a missed check in the interface cannot expose data. Application code also gets simpler, because it no longer carries access checks of its own. On this platform, Postgres row-level security keeps developers to their assigned projects and keeps clients, deals and invoices visible to admins only.

What happens if Stripe sends the same webhook twice?

Nothing changes the second time. Every state change carries an idempotency key, and a small state machine decides which transitions are allowed, so repeated, late or out-of-order events end on the same correct state. That includes a refund made by hand in the Stripe dashboard weeks after the invoice was paid.

Can Safock build an internal operations platform for our business?

Yes. For this agency, one app now runs deal, client, project, time, invoice and Stripe settlement in place of four SaaS subscriptions and a master spreadsheet. We start with discovery to map how work and money move through your business, then propose the architecture, timeline and a fixed price. You own the code and receive full documentation.

/ Next step

Need a system like this?

Tell us what you are working on. You will speak with a founder, and you will leave the call with a clear next step, even if that step is not us.

Prefer writing? Send us a brief and a founder will reply.

WhatsApp usBook a call