Vemphy

Engagement
Full build
Stage
Own venture
What I built
Registry · Verify site · Issuer dashboard · Staff console · Open toolkit
Status
Pilot
The Vemphy verify page with a code field, Check and Scan QR buttons, and a short explanation of the four answers
The Vemphy verify page on a phone, with the code field and Check button at the top
The Vemphy verify page with a code field, Check and Scan QR buttons, and a short explanation of the four answers

Story

Vemphy is a verification service for institutional documents in Ghana. Banks, universities, employers and insurers sign their letters and certificates into a registry. Anyone holding a copy checks the short code or QR printed on it and gets one of four answers: Valid, Revoked, Expired or Unknown. The tagline is "Proof that travels", meaning the proof stays attached to the document wherever it goes. It is in pilot. It is my own product, and I designed and built all of it: the visual identity, the architecture, the registry, the web apps and the open-source toolkit.

The problem

Documents like a bank reference letter or a degree certificate are asked for constantly: by landlords, HR clerks, loan officers and traders. Checking one usually means a phone call or a trip to the issuer, so most go unchecked. The person checking is not an engineer. They are standing at a counter, often on a mid-range Android phone, and they need one word they can act on.

What I built

The platform is four apps around one service, and the repository holds twelve decision records and eighteen database migrations.

  • The registry is a Go service. It holds issuers, their signing keys, claims, claim types and status lists, and it answers public verification requests.
  • The verify site at vemphy.com is where a clerk enters or scans a code. The result is decided on the server and works with JavaScript off. A code that fails its check character is caught in the browser and never reaches the registry.
  • The issuer dashboard is where a bank's staff sign in, set a password, register passkeys and see an overview. Its later screens are designed but not built yet.
  • The staff console is for Vemphy's own team: passkey sign-in, issuers, claim types, revocation approvals and an audit log across issuers.
  • The open toolkit, vemphy/vc, is the public repository that does the checking: @vemphy/vc in TypeScript and vc-go in Go, with a set of signed test claims that both must answer correctly.

I also designed the identity: a warm paper background, one sea-blue accent, a status palette that is never decorative, and a mark made of two separate strokes that read as a V.

Decisions that mattered

Four answers, and never "fake"

A public answer is Valid, Revoked, Expired or Unknown, and nothing else. Unknown covers every case where nothing can be confirmed and deliberately does not say why, because a mistyped code, an issuer that has not joined and an altered letter all look the same from here. Three accusatory words never appear anywhere in the product, and a script in the repository fails the build if they do. This matters because a clerk acting on a guess about a stranger's document is the harm the product exists to avoid.

Verification is public code

"Trust us, it's valid" is what a registry says when only it can check. The checking code is open source from its first commit, and the private registry consumes it only at published versions, so the registry and an outside verifier cannot disagree. Both languages must produce the recorded answer for every test claim, and a script cross-signs between Go and TypeScript with a key neither side has seen. Anyone can verify a claim offline without trusting Vemphy's servers.

Claim types are data, not code

A claim type is a small JSON schema saying which fields a letter has, what each is called and which may be shown to the person checking. A new kind of letter needs no release and no deploy. Core types such as bank reference letters, salary confirmations and degree certificates are curated by Vemphy. Issuers can define their own, which no other issuer can see. Once published, a type is frozen, and a change means publishing the next version, so a claim always displays with the labels it was issued under.

Keys never leave the key manager, and revoking one takes two people

An issuer's signing key is the issuer: whoever holds it can produce letters that verify as the bank's. Keys are created inside a key manager (Vault in development, with AWS KMS chosen for production) and are never exported, so no code of mine ever sees one. Revoking a key makes every letter it signed stop verifying, and that cannot be undone by doing the opposite. So it is a request that a second person must approve, and the requester can never approve their own.

One issuer can never learn what another holds

Every row that belongs to an issuer carries its issuer id from the first migration, and every query filters on it. Another issuer's data is not found, never forbidden, so a tenant cannot even learn that something exists. A test reads the query files and fails on any query that skips the filter. Vemphy's own staff are a separate population with passkeys only and no passwords.

No blockchain, no machine learning

The question is "did this bank sign this?", and a bank's public key plus a signature answers it. A ledger would add a network to depend on and data that cannot be removed. Image checks would produce probabilities, and a probability about a person's document becomes an accusation. Vemphy verifies a signature over data the issuer supplied.

Where it stands

Vemphy is in pilot. The public verify site is live and the registry, the staff console and the open toolkit are built. The issuer dashboard has sign-in, passkeys and an overview, and its remaining screens are the next work.

Stack

Go with chi, sqlc and PostgreSQL 16 for the registry, generated from an OpenAPI file that also generates the TypeScript client. React 19, TanStack and Tailwind v4 for the web apps. Credentials follow the W3C Verifiable Credentials model, with did:web issuer identities and bitstring status lists.

Stack

GoTypeScriptPostgreSQLReactTanStackTailwind CSSOpenAPIW3C Verifiable Credentials