No password column
Identity is your phone number. Session is OTP or biometric. Authorization is your PIN. No password_hash column exists — there is no password to steal, reset, or compromise.
Password-based auth is the single largest source of account takeovers. We eliminated it entirely. You authenticate with a one-time code to your registered number, then authorize sensitive actions with a PIN. Biometric unlock replaces both on mobile.
Card details never touch us
Raw card numbers, CVVs, and full PANs are tokenised by our card issuer before we ever see them. We store only the last four digits and a Stripe-issued token.
We operate under Stripe's PCI-DSS Level 1 umbrella. Raw PANs never traverse our network. If our database were fully exfiltrated, no complete card number would be recoverable.
Atomic ledger — every time
Every wallet debit and credit sits inside a single database transaction. There is no code path where money leaves one wallet without arriving in another. The ledger is always balanced.
We use PostgreSQL serialisable isolation on all balance mutations. If the transaction fails for any reason — network drop, application crash, database timeout — it rolls back entirely. Partial transfers are impossible by construction.
Idempotent payments
Every payment path is keyed on a client-supplied reference. Replaying an identical request returns the original result — no double-charge, no double-credit, regardless of retries.
Mobile networks drop connections. Users tap twice. Our idempotency layer stores the outcome of every payment operation keyed by the reference. Duplicate requests are deduplicated at the gateway before they reach the ledger.
Signed webhooks at the edge
Inbound webhooks from card networks, mobile money providers, and messaging platforms are HMAC-SHA256 verified at the gateway before they reach any service. Unsigned or tampered payloads never enter the system.
We use constant-time comparison to prevent timing attacks. Each provider has a dedicated signing secret, rotated on schedule. Any payload that fails verification is rejected with no processing and no state change.
KYC you can explain
Liberian national ID and passport reads go through an explainable AI pipeline. Every approval is a documented decision with a confidence score and a reason code. There is no black box.
Our KYC pipeline surfaces the specific fields it evaluated and the rules it applied. A declined application includes the reason in plain language. Manual review queues are available. No AI decision is irreversible.
No SMS OTP on WhatsApp
The WhatsApp channel uses WhatsApp Flows for PIN entry and confirmation prompts — never SMS OTP. SMS-based auth on a messaging channel is a phishing vector. We don't open it.
WhatsApp Flows are end-to-end encrypted forms that live inside the conversation. The user never leaves WhatsApp to enter a sensitive value. SIM-swap attacks that intercept SMS cannot affect a session that never used SMS.
Microservice data isolation
Services do not directly query other services' databases. Each service owns its data. Inter-service communication goes through defined API contracts — never raw SQL across a network boundary.
This eliminates lateral-movement attacks where a compromised service escalates by querying other databases. It also enforces schema ownership: the identity service is the sole authority on user records.
Rate limiting without exceptions
Every payment endpoint is rate-limited. Every authentication endpoint is rate-limited. No bypass headers, no internal IPs that skip the check, no admin paths that are exempt.
Credential-stuffing and payment-fuzzing attacks depend on volume. We apply sliding-window rate limits at the gateway and at the service layer. Exceeding the limit returns 429 with a Retry-After header — not a silent discard.
Secrets via environment only
No hardcoded credentials, keys, or tokens anywhere in the codebase. All secrets are injected at runtime via environment variables. The repository contains no live keys — not even in git history.
We run automated secret scanning on every commit. Any file pattern matching a known key format fails the build. AWS, Stripe, and Twilio credentials rotate on schedule.
Input validation at every boundary
Every API endpoint validates input before processing begins. Validation happens at the schema level, not in business logic. Malformed requests are rejected before they can affect state.
We use Zod schemas at all service entry points. Requests that fail schema validation return 422 with a structured error before any business logic executes. There is no 'validate later' path.
No sensitive data in logs
Wallet balances, transaction amounts, phone numbers, OTPs, PINs, and ID data are never written to logs. Structured logging includes correlation IDs and event types — not personal financial data.
Log aggregation is a common exfiltration path. We apply log scrubbing middleware at every service boundary. Fields matching PII patterns are replaced with a redacted marker before writing to any log sink.
Payment flows are always tested
No payment flow ships without tests. Every debit, credit, payout, and refund path has an integration test that runs against a real test-mode environment. Untested payment code does not merge.
Our CI runs payment flow tests against a live test-mode Stripe environment and a local Postgres instance that mirrors production schema. The pipeline blocks merges that remove payment test coverage.