Urza’s Wallet / Engineering

Request and editorial flows

Costa Rica personal finance comparison platform.

lead capture

Lead capture

SendGridPostgreSQLFastAPI leads routerNext.js /api/leadsBrowser lead formSendGridPostgreSQLFastAPI leads routerNext.js /api/leadsBrowser lead formFailures are logged by type/lead ID and do not fail submissionVisitorOpen Solicitar, enter name/email, consentPOST payload + urza_session_id cookiePOST /api/v1/leads over INTERNAL_API_URLValidate payload and hash client IP with saltTransaction inserts leads + lead_consent_recordsPersisted lead201 id, status, created_atPreserve status and responseAsync best-effort internal notificationVisitor
Visitor lead capture and notification
deletion request

Deletion and anonymization

PostgreSQLFastAPI leads routerNext.js deletion proxyPostgreSQLFastAPI leads routerNext.js deletion proxyRequest email remains in deletion-request rowVisitorEditorPOST email to /api/leads/deletion-requestPOST /api/v1/leads/deletion-requestInsert lead_deletion_requestsGeneric 202 regardless of lead matchAuthenticated list of pending deletion requestsPOST admin deletion request process (anonymize)Find pending requestAnonymize all non-anonymized leads matching emailMark request processed with notesProcessed action and request idVisitorEditor
Requested deletion
PostgreSQLLeadRepositoryanonymize_expired_leadsRailway cron (manual schedule)PostgreSQLLeadRepositoryanonymize_expired_leadsRailway cron (manual schedule)Already anonymized leads are untouchedpython -m api.scheduled.anonymize_expired_leadsRead DATABASE_URL and LEAD_RETENTION_DAYS (365 default)anonymize_leads_older_than(cutoff)Clear name/email/notes and mark anonymizedNumber of rows changed
Scheduled retention
same origin proxy

Same-origin proxy request

FastAPI private serviceNext.js route handlerfetchXClient helperBrowser client componentFastAPI private serviceNext.js route handlerfetchXClient helperBrowser client componentBrowser never resolves api.railway.internalRefresh resourceRelative /api/resource requestRead INTERNAL_API_URL on server only/api/v1/resourceJSON response/statusSame-origin responseTyped resource data
Browser-to-private-API boundary
cta engagement

CTA engagement

cta_engagement_eventsFastAPI cta_events router/api/cta-events proxyNext.js middlewareBrowsercta_engagement_eventsFastAPI cta_events router/api/cta-events proxyNext.js middlewareBrowseralt[Existing valid cookie][New session]Attribution is session-scoped and carries no authentication authorityVisit credit-card pageIssue random urza_session_id if absent/invalidPOST event + session cookieForward payload and Cookie headerValidate event and use valid cookie or generate IDInsert session/product/surface/event metadata204 empty body201 session_id + Set-CookiePreserve status and Set-Cookie
Session-scoped CTA events
cms publish

CMS publish

Public read routePostgreSQLFastAPI admin routerNext.js admin UI and proxyPublic read routePostgreSQLFastAPI admin routerNext.js admin UI and proxyDynamic pages read fresh content, no revalidation hook is calledEditorCreate article/guide/product as draftAuthenticated admin create requestInsert content and admin_audit_log in transactionCreated draftPublish via status actionPATCH admin resource statusUpdate status and audit entryRead published contentPublished recordEditor
Draft to published content
product freshness

Product freshness

freshness.is_staleProduct read repositoryPostgreSQLAdmin product routefreshness.is_staleProduct read repositoryPostgreSQLAdmin product routeA patch changing only credit_cards fields does not update the base timestampEditorSave base product fields or publish statusUpdate financial_products and updated_at = NOW()Read product and updated_atProduct rowCompare now - updated_at with STALE_AFTER_DAYSis_stale booleanAPI product includes derived is_staleEditor
Freshness derived from editor update time

The default stale threshold is 45 days. Invalid STALE_AFTER_DAYS configuration fails startup. A product base-field update or status change refreshes updated_at. A partial API patch touching only credit_cards extension fields does not refresh that base timestamp; this existing limit is documented rather than concealed by an “every save” claim.

admin authentication

Admin authentication

Admin routerFastAPI admin middlewareAdmin page / route handlerNext.js middlewareAdmin routerFastAPI admin middlewareAdmin page / route handlerNext.js middlewarealt[Credentials absent or wrong][Configuration missing][Credentials accepted]Middleware checks /admin, not /api/adminSome CMS proxies inject configured credentials, others forward caller authEditor browserGET /admin with Authorization Basic401 + WWW-Authenticate503Render admin pageRequest /api/v1/admin resourceConstant-time compare configured credentialsSet request.state.admin_user and dispatchAdmin result via proxyEditor browser
Editor Basic-auth boundaries

The web middleware gates /admin/** and FastAPI gates /api/v1/admin/** independently. Lead proxies forward caller Authorization. Several CMS proxies instead inject configured server credentials; /api/admin/** is outside the web middleware’s isAdminPath check. Consequently API authentication alone does not prove caller authorization at those proxy endpoints. This existing boundary gap is recorded here; adding proxy authorization is separate runtime work.