Skip to content
SaaS

Zavier Invoices

zavierinvoices.com

Zavier automates the awkward, manual work of chasing unpaid invoices, escalating reminders through WhatsApp and SMS on a schedule the business sets once, with a payment link on every message so a client can pay instantly instead of being asked for account details.

The Outcome

The Shift

Payment collection runs on schedule without manual follow-up, safely, because every retried webhook and every retried reminder job is a guaranteed no-op rather than a risk. Separately, changing where a business's payments settle now requires proof of account ownership, while a brand-new account can be set up in one step, because those two situations carry genuinely different risk.

Key Outcomes
01

A retried Paystack or WhatsApp webhook can never cause a duplicate charge or a duplicate reminder, enforced by a database-level uniqueness guarantee, not just application logic.

02

First-time payout setup has no extra confirmation step, but any later change to where money settles requires an emailed confirmation before it takes effect.

03

Bank account details are encrypted at rest and only ever shown back as the last four digits, even to the business that owns them.

The Context

  • Manual collections don't scale past a handful of clients. Chasing overdue invoices by phone, DM, and email is slow, awkward, and easy to forget, and there's no consistent logic for what happens when a payment goes 30 days overdue.
  • Payment gateways deliver webhooks at least once, not exactly once. A retried delivery has to be a no-op, not a second reminder or a second attempt to process the same charge.
  • Getting payouts wrong has an unusually high cost. Once a business links its bank account so client payments settle directly, a mistake in that flow doesn't just create a bug, it can send someone else's money to the wrong account.

The Decision

The questions, trade-offs, and architectural reasoning behind how this product was built.

Decision Jul 2026

Two different risk profiles for one settings page

Each business needs a Paystack subaccount so client payments settle directly to their own bank account, and that detail can change later.

"Requiring heavy verification on every payout change would add friction exactly when a new user is most likely to abandon signup, but requiring none at all means a compromised or mistaken change could redirect real settlements."
  • One confirmation flow for both first-time setup and later changes
  • Two different flows depending on what's actually at risk

A fresh account has no assets to protect yet, so the risk of frictionless setup is low. A change to existing settlement details can redirect money away from the legitimate owner, which is worth the extra round-trip.

Chosen Approach

First-time setup completes immediately. Any later change requires confirming a time-limited, emailed token before it takes effect, and requesting a new one invalidates any earlier unconfirmed request from the same business.

Impact

The account number itself is encrypted (AES-256-GCM, not hashed) specifically because the confirmation step has to decrypt it to complete the change, and every change writes to an audit log that survives even if the business account is later deleted.

Takeaway

Not every action on the same page deserves the same amount of friction.

Decision Jul 2026

Making a remote API call and a database write safe together

Registering a payout account means calling Paystack's API first, since there's no way to wrap a remote HTTP call in a database transaction, then saving the returned account code to the business's own row.

"Two concurrent requests could both call Paystack and both try to save the result, and a naive check-then-update has a race window where both succeed."
  • A select-then-update pair guarded by application logic
  • A compare-and-swap write at the database level

A select-then-update pair only prevents the race if nothing runs between the two steps, which isn't guaranteed. A single conditional update either succeeds or tells you it didn't, atomically.

Chosen Approach

The account code is written with a first-write-wins conditional update; if a concurrent request already won, the loser's orphaned Paystack subaccount is logged for manual cleanup rather than silently retried.

Impact

Verifying this correctly required a specific fix in testing: spying on the database client directly, not on the transaction wrapper, since the transaction gives a distinct client instance that a naive spy doesn't intercept.

Takeaway

A race condition that "probably won't happen" still has to have a defined losing path.

The Execution

  • Deterministic, idempotent reminder scheduling. Each of the six scheduled reminders per invoice (from 7 days before the due date to 14 days after) has a job ID built from the invoice and the specific trigger, so calling the send logic twice, whether from a retry or a race, can never enqueue a duplicate reminder.
  • WhatsApp first, SMS only on confirmed failure. A reminder falls back to SMS only when Meta's delivery receipt explicitly marks the WhatsApp message as failed, never on a timeout, so a slow-but-successful delivery never triggers a redundant message on a second channel.
  • Webhook signatures verified against the raw request body. Both Paystack's and Meta's webhook signatures have to be computed before any JSON parsing touches the payload, which the framework's default body parser destroys; a custom parser captures the raw bytes first specifically so that verification can happen at all.
Zavier Invoices screenshot 1 Zavier Invoices screenshot 2 Zavier Invoices screenshot 3 Zavier Invoices screenshot 4 Zavier Invoices screenshot 5

Client Feedback

What clients say after shipping.

““Working with Damilare has been a pleasure. He brought care, creativity, and commitment to every part of Zavier Invoices, turning our vision into a product we’re proud to share with our customers. His work has made a real difference, and I’m grateful to have such a talented behind us.”
Ayobami Adebayo , Co-Founder, Zavier Invoices

Every project starts with a conversation about trade-offs, not technology.

Let's Talk