Victor Dickson
Victor Dickson

Statement of work

Lagos, Nigeria · B.Tech, FUTO

VictorDickson

Full-stack engineer — commerce, payments & AI sales agents

I build the systems Nigerian businesses sell on: payments that reconcile, bank transfers that confirm themselves, and AI agents that turn conversations into orders.

Status
Open to engineering roles
Based
Lagos, Nigeria. Open to remote
Building
Myshoplet · CTO at FavGrab
Depth
Payments, webhooks, LLM agents in production

Paystack retries when it doesn't hear back in time. In 2026 that retry paid an ambassador twice, because my payout write wasn't idempotent. This is what the fix does now.

Entries

The work

Everything below is live, and every figure can be checked from the repositories or the sites themselves.

01
2026 — now

Myshoplet

Founder & lead engineer

A commerce platform for Nigerian sellers. Storefronts, point of sale, Paystack checkout, dedicated virtual bank accounts with automatic transfer confirmation, subscription billing, payouts, KYC/KYB, delivery booking, and one AI sales agent that sells across WhatsApp and Instagram. Each store also ships with structured product data, sitemaps and an AI-readable catalogue. Designed, built and operated solo, including the deploy pipeline and incident response.

myshoplet.com ↗ Chat with the AI agent ↗ Seven incidents ↓
159,742lines 8,935commits
02
2023 — now

FavGrab

Chief technology officer

On-demand food and grocery delivery across Southeast Nigeria. I set the technical direction and architect the systems that connect restaurants and supermarkets to riders and customers: merchant onboarding, order routing and delivery tracking. I lead the team building them across web, mobile and backend.

Liveplatform Teamweb · mobile · api
03
2026

Hotel Ops

Client build · solo

Ordering, bookings and payments for a multi-department hotel in Lagos: front desk, restaurant, grill, bakery and lounge. Guests order and book by chatting with a Claude assistant on WhatsApp or the website, which reads the live menu and room list but never sets a price. Each order gets its own bank account number, so it turns paid the moment the transfer is verified, and a till card sale goes straight to the counter's Moniepoint machine for the exact amount. Every department watches it land on one live dashboard.

Case study & source ↗
126tests Claudemonnify · moniepoint
04
2026

shopeval

Solo build · open source

Test cases for AI sales agents, in Python. Myshoplet's agent made real mistakes in front of customers: it read "How much?" as size M, told a customer an order was placed when none existed, and sent a payment link in reply to "Hi". Each one is now a check that any agent, model or prompt has to pass before it talks to a customer. The checks are plain code rather than a model grading a model, so every failure points at a sentence you can read. A deliberately broken agent fails 21 of the 25 chats; the tests make sure it keeps failing.

Source ↗
25test chats Pythonllm evals
05
2026

Ledger

Solo build · open source

A double-entry ledger for marketplace payouts, in TypeScript on Postgres. I rebuilt the MongoDB ledger that runs in Myshoplet production to find out which of the safeguards I had written by hand the database could enforce instead. Most of them: a retried webhook becomes a unique constraint, platform fees and gateway costs become separate accounts rather than sibling columns kept apart by a comment, and a balance derived from its entries cannot drift from them. Every transaction must sum to zero, checked at commit even when the TypeScript is bypassed entirely.

Source ↗ The bug it answers ↓
20tests Postgrestypescript
06
2026

Shortlist

Solo build · open source

A daily funnel over roughly 3,400 engineering vacancies, pulled from public Greenhouse, Lever and Ashby board APIs and from my own LinkedIn alert emails read over IMAP. It scores each role against a structured profile, opens every shortlisted link to confirm the posting still exists, and emails a ranked short list each morning. The interesting part is what it refuses to do: it scrapes nothing that forbids it, and it never applies on my behalf.

Source ↗ Why it opens every link ↓
3,427scanned per run 143tests

Other work

  • Bloom A cycle and wellness tracker shipped to iOS, Android and web from one Flutter codebase. Live ↗ Source ↗
  • DeFi Dashboard A read-only Ethereum dashboard for any wallet or ENS name, running on keyless public APIs. Next.js and ethers.js. Live ↗ Source ↗
  • Data & ML, 2023 Where I started: a diabetes diagnostic model, a movie rating regressor and Tableau sales reporting, in Python and scikit-learn. Diabetes ↗ Ratings ↗ Sales ↗

Post-mortems

Seven things that broke

A feature list says what I built. These say how I think when it goes wrong, which is the part of the job that actually costs money.

INC-01 · money path Resolved

A retried webhook paid an ambassador twice

Symptom
One referral payout landed in the ledger twice for a single customer payment.
Cause
Paystack retries a webhook when it doesn't get a timely response. My handler treated each delivery as a new event, so the second delivery re-ran the payout write.
Fix
Made the write conditional and idempotent — a $ne-filtered updateOne, so the payout can only be applied once regardless of how many times the event arrives.
Learned
Never pair that filter with upsert: true. The filter stops matching, and upsert obligingly inserts a second document, reintroducing the exact bug you just fixed.
INC-02 · performance Resolved

A slow dashboard that was never a slow query

Symptom
Seller quick-stats took roughly four seconds to load.
Cause
Profiling showed about 1ms of real database work sitting behind 111ms of round-trip latency — repeated 43 times. The queries were fine; the number of trips was not.
Fix
Collapsed 43 round trips into 3 with a single $group using conditional accumulators.
Learned
I measured $facet as the obvious alternative and it was slower: it re-scans the collection per branch. Warm up before timing anything, or you'll optimise the cold start instead of the query.
INC-03 · delivery Resolved

Three quarters of every storefront was wasted bytes

Symptom
Storefronts were slow on mobile data, which is how nearly every Nigerian buyer arrives.
Cause
Product images were being served straight from Cloudinary at original size, untransformed.
Fix
A transformation helper at the delivery layer took a real storefront from 1,852KB to 467KB — a 74% cut, with no visible quality loss.
Learned
Here it beat next/image, and the largest image above the fold must stay eager. Lazy-loading your LCP element makes the number worse while looking like an optimisation.
INC-04 · billing Resolved

Sellers on a free trial were charged four times the published rate

Symptom
One live seller was billed ₦4,120 in platform fees on a ₦103,000 order. At the rate published in our own terms it should have been ₦1,030.
Cause
A store inside its 14-day trial is stored as plan: 'free'. The fee lookup read that stored value, but free isn't a rate we publish, so it fell through to the wrong one. The trial's effective plan is growth_trial.
Fix
Every fee lookup now keys on getEffectivePlan(sub) and takes the subscription document, never a plan string. Trial, expired and free all alias onto the published Growth rate.
Learned
Derived state needs one resolver and no second readers. The invariant is now a unit test: a store inside its trial bills at 1%, and reading sub.plan directly is asserted against, because that is precisely what broke it.
INC-05 · silent failure Resolved

Renewing sellers were locked out with no warning email

Symptom
Sellers who renewed still hit a hard lock at the next expiry, having received none of the warning emails that are supposed to precede it.
Cause
Reminder and dunning emails de-duplicate against remindersSent and dunningsSent. Renewal extended the subscription but left both arrays populated, so every subsequent reminder looked already-sent and was skipped.
Fix
Every renewal path clears both arrays. Six files touch those flags, and missing any one leaves the bug alive on that path, so I fixed them together and backfilled the sellers already stuck in the broken state.
Learned
A de-duplication flag becomes a lie the moment the thing it guards resets. And patching forward isn't enough once bad state is in the database. The repair script matters as much as the fix.
INC-06 · blast radius Resolved

Rotating one secret took out most of the payment reminders

Symptom
Payment nudges to customers stopped going out after a routine credential rotation.
Cause
The cron endpoint is gated on CRON_SECRET. An external scheduler had been calling it every thirty minutes for months, and nothing in the repository recorded that it existed. Rotating the secret locked it out.
Fix
Restored the consumer with the new credential, and documented it where the next person will actually look — next to the endpoint it calls.
Learned
Grep only finds the callers you own. Before rotating any shared credential I now inventory who authenticates with it, including things configured once in a browser years ago and never written down.
INC-07 · shortlist Resolved

A job board that answers 200 for a job that is gone

Symptom
Links in my morning digest opened on an empty page. The tool was recommending roles that no longer existed.
Cause
Ashby serves its board shell with HTTP 200 for a posting it cannot resolve. One company runs the posting API but leaves its hosted board dark, so the API hands out URLs that render nothing. No status code ever says so.
Fix
A verification stage that opens each shortlisted link and asks whether the page still names that job, reading the bootstrap payload, the title and the role's own words. It fails open: a timeout or a 429 marks nothing dead, so a flaky connection cannot silently empty the digest.
Learned
My first checker searched the HTML for 404 and declared six live jobs dead. The string was inside SVG path data (13.3404). A test now carries those exact coordinates.

Log

Also shipped

Smaller entries, same system. Written up briefly because the detail above already makes the point.

Schedule

What I work in

Listed by how often I actually reach for it, not by how it looks on a keyword scan.

Daily

  • JavaScript
  • React · Next.js 14
  • Node.js · Express
  • MongoDB · Mongoose
  • REST APIs · webhooks
  • Git · Vercel

Payments & money

  • Paystack integration
  • HMAC signature verification
  • Idempotent writes
  • Subscriptions · dunning
  • Payouts · clearance holds
  • Dedicated virtual accounts
  • KYC / KYB tiers

AI in production

  • Anthropic API
  • OpenAI · Gemini
  • WhatsApp Cloud API
  • Instagram messaging
  • Prompt guardrails
  • Per-tenant cost tracking

Also fluent

  • Redis · queues
  • React Native · Expo
  • Python · SQL
  • Scikit-learn · Tableau
  • Flutter · Dart
  • Solidity
Google AI Professional Certificate awarded to Victor C Dickson on October 7, 2026

Verified credential · 2026

Google AI Professional Certificate

Eight courses covering responsible AI use across research, communication, content creation, data analysis, coding, app building and deployment. The programme included more than 20 practical artefacts and a custom AI solution.

Identity and completion verified by Coursera
Provider
Google Career Certificates · Coursera
Issued
7 October 2026
Scope
8 courses · applied AI · responsible deployment
View the four supplied course certificates

Sign-off

Get in touch

I'm looking for full-stack, backend or applied-AI engineering work, remote or in Lagos. If you're building anything that moves money, we'll have plenty to talk about.

Replies usually within a day.