FoxCalc

Platform Overview

Everything FoxCalc does — architecture, features, integrations, customization, and security.

Table of Contents

  1. What Is FoxCalc
  2. How It Works
  3. Integration Path
  4. The Calculator Experience
  5. The Funder Portal
  6. API Reference Summary
  7. Customization & Configuration
  8. Formula Engine
  9. Prepayment Models
  10. Fee Configurability
  11. Webhooks & Notifications
  12. Attachment Handling
  13. Security Architecture
  14. Roles & Permissions
  15. Audit & Compliance
  16. Infrastructure & Reliability

What Is FoxCalc

FoxCalc is a multi-tenant MCA pricing calculator platform. Funders send deal parameters via API, FoxCalc hosts a branded calculator experience for ISOs (Independent Sales Organizations), and delivers the ISO's configured terms back to the funder via webhooks.

Key distinction: FoxCalc is a platform, not a single-client tool. Every funder gets their own isolated environment — their own brands, their own field labels, their own fee structures, their own callback endpoints, their own user roles. Nothing in the system is hardcoded to any single funder. Funder-specific behavior lives entirely in an adapter layer.

What Problem It Solves

Funders approve MCA deals with ranges — a purchase price between $20K and $50K, a term between 6 and 18 months, an upsell up to 5%. Today, communicating those ranges to ISOs is manual: spreadsheets, PDFs, phone calls. ISOs configure terms by guessing, back-and-forth emails follow, deals slow down.

FoxCalc replaces that with a live, interactive calculator the ISO opens in their browser. They drag sliders, see every number update in real time, and submit their configured terms with one click. The funder gets a structured callback within seconds.

Who Uses It

RoleWhat They Do
Funder (API)Sends deal parameters to FoxCalc via REST API
Funder (Portal)Manages deals, brands, users, settings, and monitors activity
ISOOpens the calculator link, configures terms, uploads stipulations, submits

How It Works

The Deal Lifecycle

Funder sends deal via API

    Offer created (draft or active)

    Underwriter reviews in Portal (optional)

    Deal released → signed calculator URL generated

    ISO opens calculator → configures terms interactively

    ISO submits → FoxCalc computes final terms

    Webhook fires → funder receives structured callback

    Offer status → submitted (terminal)

Offer Statuses

StatusMeaning
draftCreated but not yet released. ISO cannot access. Underwriter can preview.
activeReleased. Calculator URL is live. ISO can configure and submit.
submittedISO submitted terms. Terminal state.
expiredPast expiration date. ISO sees expiration notice.
revokedManually cancelled by funder. ISO sees revocation notice.

Valid transitions: draft → active → submitted, draft → revoked, active → expired, active → revoked


Integration Path

Phase 1 — Platform Setup (~1 day, FoxCalc team)

  1. Create funder record with slug (e.g., fox, forward)
  2. Create first admin user (sends invite email)
  3. Configure SendGrid email infrastructure

Phase 2 — Funder Self-Service (~1–3 days)

  1. Admin logs in, sets password via invite link
  2. Creates brand(s) — logo, colors, display name
  3. Adds DNS records for email domain (SPF/DKIM/DMARC)
  4. Generates API key(s) with scopes
  5. Configures field mapping (funder's field names → FoxCalc canonical schema)
  6. Sets organization defaults — fees, stipulations, prepayment model, guardrails, terminology
  7. Configures callback URL + secret
  8. Subscribes to webhook events
  9. Invites underwriter users

Phase 3 — Integration Testing (~1–5 days)

  1. Funder sends test deals via API (draft status)
  2. Underwriter reviews drafts, configures guardrails, previews calculator
  3. End-to-end validation — release, open calculator, submit, verify callback
  4. Funder confirms integration

Phase 4 — Go Live

  1. Funder starts sending production deals
  2. FoxCalc team monitors first batch

The Calculator Experience

The calculator is a React single-page application served from a signed URL. No login, no accounts, no sessions. The ISO clicks the link and starts configuring.

What the ISO Sees

Header:

  • Funder's brand logo and name
  • Merchant name, ISO name
  • Deal type badge, approval date
  • Expiration countdown (red when approaching)
  • Funder notes (expandable)

Left Panel — Interactive Controls:

  • Purchase price slider + text input (within funder-defined floor/ceiling)
  • Estimated payments slider + text input (within funder-defined range)
  • Frequency toggle — daily, weekly, monthly (available options set by funder)
  • Upsell slider (0% to funder's max)
  • Adjustable fees — each fee the funder marks as adjustable gets its own slider with floor/ceiling
  • ISO notes — free text field (when enabled by funder)

Right Panel — Computed Terms (read-only, updates in real time):

  • Purchase price, buy rate, total factor rate, amount sold
  • Payment amount with frequency label (e.g., "$3,650/week")
  • Number of payments, estimated term
  • Commission (rate + dollar amount)
  • Total fees, existing funding balance, net to merchant
  • Holdback percentage, position
  • Prepayment schedule table (when enabled)
  • Stipulations list with file upload

Three Dynamic Constraints

The calculator enforces three constraints simultaneously via slider range adjustments — no error modals, no blocked states:

ConstraintWhat It Does
Payment ceilingAs upsell increases or payments decrease, purchase price ceiling tightens to keep payment under cap
Net-to-merchant floorAs fees increase, purchase price floor rises to prevent negative net
Fee ceilingAt low purchase prices, adjustable fee ceilings tighten to keep net ≥ 0

Submission Flow

  1. ISO clicks "Request Contracts"
  2. Confirmation modal shows all configured terms (layout and visible fields customizable by funder)
  3. Pending file uploads complete automatically
  4. ISO clicks "Confirm & Submit"
  5. Server validates version (optimistic concurrency), computes final terms, stores submission
  6. Webhook fires to funder with complete structured data
  7. ISO sees success screen

Calculator States

StateWhat the ISO Sees
ActiveFull interactive calculator
PreviewYellow banner, all controls work, submit disabled
ExpiredExpiration notice with date
RevokedCancellation notice
Submitted"Already submitted" notice
Invalid linkSignature mismatch error

Brand Theming

Every calculator renders with the funder's brand identity:

  • Logo in header
  • Primary color on CTAs, slider accents, active states
  • Accent color on highlights
  • Custom field labels via terminology overrides
  • All applied as CSS custom properties at runtime — same layout, different skin

The Funder Portal

A Next.js admin application where funders manage everything. JWT session auth with role-based access control.

Deal Management

Deal List:

  • Paginated with search (merchant name, ISO name, deal ID)
  • Filter by status, brand, date range
  • Sort by created date, merchant name, status, expiration
  • Click through to deal detail

Deal Detail:

  • Full deal parameters — all editable while in draft/active status
  • Three-card layout: payment structure, pricing constraints, merchant data
  • Fee editor with adjustability toggles and ranges
  • Stipulations, notification emails, assigned users
  • Preview button (opens calculator in new tab with submit disabled)
  • Release button (draft → active, generates signed URL)
  • Revoke button (cancels deal)
  • Activity timeline — every lifecycle event with timestamps
  • Webhook delivery logs — every callback attempt with status codes
  • Submission details (if submitted) — all ISO selections, computed terms, attachments with download links

Manual Deal Creation:

  • Full form pre-populated with funder defaults
  • Create as draft or active
  • All fields from the canonical offer schema

API Simulator:

  • Build a deal payload using the same form
  • Live JSON preview (snake_case, ready to paste)
  • Copy as JSON or cURL
  • "Simulate" button — validates, shows defaults that would be applied, shows final payload
  • "Create This Deal" button — creates from simulation

Settings (13 Pages)

SettingWhat It Controls
Brand & AppearanceLogo, colors, display name, email identity. Multi-brand support.
CallbacksPer-brand callback URL + secret. Ping test button.
Fee TemplatesDefault fee structures (fixed/percentage, visible/hidden, adjustable/fixed, floor/ceiling)
Roles & PermissionsEditable role × permission matrix. Customized roles per funder.
Domains & DNSCustom calculator domains (CNAME), email DNS records (SPF/DKIM/DMARC)
WebhooksEvent subscriptions per brand. 13 event types. Active/paused toggle.
API KeysGenerate, revoke, search. Scopes and IP allowlist. Secret shown once.
Calculator DisplayToggle prepayment schedule, stipulations, fee breakdown visibility
GuardrailsDefault frequencies, payment ranges, max upsell, payment override behavior
Deal DefaultsExpiration policy, notification emails, assigned users, prepayment model, terminology overrides, rate limits
Submission FormConfirmation modal layout (1 or 2 column), header message (rich text with variables), field visibility/order/accent
UsersInvite, deactivate, role assignment, bulk CSV upload, login history
User DetailPer-user permission overrides, recent deals, login history

User Management

  • Invite by email (sends branded invite with 7-day expiration)
  • Bulk invite via CSV upload (up to 50 users, validation preview)
  • Six roles with 20 permissions across 7 categories
  • Per-user permission overrides (grants/revokes on top of role)
  • Login history per user (timestamp, IP, event type, success/failure)

API Reference Summary

Three Route Namespaces

Funder API (/api/v1/{funder}/...) — HMAC signature auth

MethodEndpointPurpose
POST/api/v1/{funder}/offersCreate new offer
PUT/api/v1/{funder}/offers/{deal_id}Replace offer (full overwrite)
PATCH/api/v1/{funder}/offers/{deal_id}Partial update
DELETE/api/v1/{funder}/offers/{deal_id}Revoke offer

Calculator API (/api/v1/offers/...) — Signed URL auth

MethodEndpointPurpose
GET/api/v1/offers/{deal_id}Fetch offer for calculator
POST/api/v1/offers/{deal_id}/submitSubmit configured terms
POST/api/v1/offers/{deal_id}/upload-urlsGet presigned S3 upload URLs
POST/api/v1/offers/{deal_id}/confirm-uploadConfirm file uploaded
POST/api/v1/offers/{deal_id}/delete-attachmentRemove attachment

Portal API (/portal/api/...) — JWT session auth, ~38 endpoints

Covers: authentication, deal CRUD, brand management, API keys, user management, webhook subscriptions, audit log, roles & permissions, callbacks, field mapping, funder config.

Authentication Mechanisms

SurfaceMethodDetails
Funder → APIHMAC-SHA256X-API-Key + X-Signature + X-Timestamp. 5-minute replay window. Per-key scopes and IP allowlist.
ISO → CalculatorSigned URLQuery-param HMAC signature + expiration. No login, no session.
PortalJWThttpOnly cookies, 15-min access + 7-day refresh. Role-based permissions.
Funder ← CallbackHMAC-SHA256X-FoxCalc-Signature + X-FoxCalc-Timestamp on outbound webhooks.

Optimistic Concurrency

Every offer has a version integer. ISO includes version in submission. Portal includes version in edits. Version mismatch → 409 Conflict with instruction to reload.


Customization & Configuration

Per-Funder Configuration

Every funder can customize:

AreaWhat's Configurable
BrandingMultiple brands per funder. Each brand has logo, primary/accent colors, display name, email identity, custom domain.
TerminologyEvery field label is overridable. "Purchase Price" can become "Funding Amount", "Amount Sold" can become "RTR". JSONB on brands/funders table.
FeesFixed or percentage. Visible or hidden. Adjustable or locked. Floor/ceiling on adjustable fees. Unlimited custom fee IDs.
GuardrailsPurchase price range, payment range, max upsell, payment override behavior (ceiling/fixed/none), base and available frequencies.
PrepaymentThree models: step-per-month (auto-generated), fixed windows, or funder-provided table. All configurable per deal.
StipulationsDefault stipulation list. Per-deal overrides. File upload with MIME validation and size limits.
CallbacksPer-brand default. Per-deal override. HMAC signing secret. 8-attempt retry with exponential backoff.
Webhooks13 event types. Per-brand or funder-wide subscriptions. HMAC signature verification.
DisplayToggle visibility: prepayment schedule, stipulations, fee breakdown.
Submission formLayout (1 or 2 column), header message (rich text with {{merchant_name}}), field visibility/order/accent for 15 fields.
Rate limitsPer-funder API limits (requests per minute/hour/day).
RolesCustomize permission matrix per role per funder. Per-user permission overrides.
ExpirationDefault expiration days. Per-deal override. Auto-expiration cron.
EmailPer-brand from name and from address. DNS records for deliverability.
AdapterInbound field mapping (funder's field names → canonical). Outbound payload format (per-funder adapter).

Defaults Hierarchy

Funder defaults apply when offer-level data is missing:

Offer data (highest priority)

Brand defaults

Funder defaults (lowest priority)

Five default categories: fees, stipulations, prepayment config, guardrails, display config. Offer-level always wins. Arrays merge only when offer array is empty.


Formula Engine

All calculations live in @foxcalc/shared — imported by both calculator (client-side) and portal (server-side) to guarantee identical math.

12 Computed Fields

#FieldFormula
1Total factor ratebuy_rate + upsell
2Amount soldpurchase_price × total_factor_rate
3Commission ratecommission_rate_override ?? upsell
4Commission dollarspurchase_price × commission_rate
5Resolved feeamount_type === "pct" ? purchase_price × amount : amount
6Total feesΣ resolved_fee(fee) for all fees
7Net to merchantpurchase_price - total_fees - existing_funding_balance
8Estimated termround(num_payments / paymentsPerMonth[base_frequency])
9Payment amount (base)amount_sold / num_payments
10Display conversionbase_payment × freq_divisor[selected_frequency]
11Holdback %(daily_payment) / (daily_receivables) — dynamic when monthly receivables provided
12Total interestamount_sold - purchase_price

Two Constant Sets

Term estimation (PAYMENTS_PER_MONTH):

  • Daily: 21.67 (260/12), Weekly: 4.33 (52/12), Monthly: 1

Display grouping (FREQUENCY_DIVISOR):

  • Daily: 1, Weekly: 5, Monthly: 22.5

Frequency switching regroups payments without changing economics. Total repayment and term stay identical.


Prepayment Models

Three models supported simultaneously. Funder chooses per deal.

Model A — Step-Per-Month

Auto-generates a tier for every month from base term down to a floor. Factor rate steps down by a fixed amount per month.

Example: Base term 12 months, factor rate 1.38, rate step 0.02, floor 3 months → generates 10 tiers, each month earlier saves 2 points on the factor rate.

Tiers regenerate when ISO adjusts term or upsell. Number of tiers changes with term.

Model B — Fixed Windows

Fixed prepayment windows measured in months after funding. Each window has a specific discount in factor rate points.

Example: Month 1: 10pts off, Month 2: 7pts off, Month 3: 5pts off. Windows don't change when ISO adjusts term — they're relative to funding date.

Model C — Funder-Provided

Pre-computed table from funder's system. Dollar amounts are static; factor rates recalculate with ISO adjustments.

Uniform Output

All models produce the same PrepaymentTier structure: label, term/window, factor rate, amount sold, savings, discount points. The calculator renders them identically regardless of model.


Fee Configurability

Each deal can have unlimited fees. Each fee has independent configuration:

PropertyOptionsEffect
IDAny string (origination, underwriting, wire, custom)Unique identifier
LabelAny stringDisplay name (subject to terminology overrides)
AmountDollar value (fixed) or decimal (pct)0.03 = 3% for percentage fees
Typefixed or pctFixed = dollars, Pct = percentage of purchase price
Visibletrue / falseHidden fees still deducted but not shown to ISO
Adjustabletrue / falseISO can change within floor/ceiling
Floor/CeilingSame unit as typeBounds for ISO adjustment

Dynamic ceiling enforcement: At low purchase prices, adjustable fee ceilings automatically tighten to prevent net-to-merchant going negative. The ISO never sees an error — the slider range adjusts.


Webhooks & Notifications

13 Webhook Event Types

EventWhen It Fires
offer.createdNew offer via API
offer.replacedFull overwrite via PUT
offer.patchedPartial update via PATCH
offer.revokedCancelled via DELETE
offer.expiredPast expiration date (cron)
calculator.openedISO loaded calculator
submission.receivedISO submitted terms
submission.callback_failedAll callback retries exhausted
email.bouncedNotification email bounced
email.openedISO opened notification email
attachment.deliveredPush delivery confirmed
attachment.delivery_failedPush delivery failed

Subscription Management

  • Per-brand or funder-wide subscriptions
  • Event type filtering (subscribe to any subset)
  • Active/paused toggle
  • HMAC-SHA256 signed payloads (X-Signature, X-Webhook-Event, X-Webhook-ID, X-Timestamp)
  • Portal UI for full CRUD

Callback Delivery

When an ISO submits, the primary callback fires immediately:

Payload includes: deal ID, offer version, timestamp, all ISO selections, all computed terms, prepayment schedule, attachment download URLs (presigned, 72-hour expiration).

Retry strategy (8 attempts):

AttemptDelay
1Immediate
210 seconds
330 seconds
41 minute
55 minutes
615 minutes
71 hour
86 hours

After 8 failures: marked as failed, notification email sent to all deal recipients.

Email Notifications

Five email templates, all programmatically generated with brand theming:

EmailTriggerRecipients
Submission confirmationISO submitsAll notification emails on the deal
Expiry warning24 hours before expirationAll notification emails on the deal
Callback failure8 failed callback attemptsAll notification emails on the deal
User inviteAdmin invites portal userInvited email address
Password resetUser requests resetUser's email address

Submission emails include a JSON summary attachment and (if under 10 MB total) all stipulation files.


Attachment Handling

Upload Flow

  1. ISO selects files in calculator (drag-and-drop or file picker)
  2. Calculator requests presigned S3 upload URLs from API
  3. Files upload directly from browser → S3 (never through API server)
  4. Calculator confirms each upload
  5. On submission, attachment metadata is included in the callback

Constraints

LimitValue
Max file size25 MB per file
Max files per stipulation5
Max total per submission100 MB
Allowed typesPDF, PNG, JPEG, HEIC, TIFF, DOC, DOCX, XLS, XLSX, CSV

MIME type validated on upload (extensions are not trusted).

Delivery to Funder

Pull model: Callback includes presigned download URLs (72-hour default expiration, configurable per funder). Funder downloads at their pace.

Push model (planned): Platform pushes files to funder's system — multipart POST, SFTP, or S3-to-S3 copy.

Storage

S3, encrypted at rest (AES-256). Bucket structure: {funder_id}/{deal_id}/{stipulation_id}/{filename}. Retention configurable per funder.


Security Architecture

Four Authentication Boundaries

BoundaryMethodProtection
Funder → APIHMAC-SHA256Request-level integrity. Timestamp replay protection (±5 min). Per-key scopes. Optional IP allowlist.
Calculator accessSigned URLsHMAC signature + expiration in query params. No session, no cookies.
PortalJWT sessionshttpOnly secure cookies. 15-min access token, 7-day refresh.
API → FunderHMAC-SHA256Signed outbound webhooks. Per-brand secrets.

Input Validation

  • All API inputs validated via Zod schemas
  • SQL injection prevented via Drizzle ORM (parameterized queries)
  • XSS prevented via React auto-escaping
  • File upload MIME validation (content-based, not extension-based)
  • Webhook URLs validated against private IP ranges (SSRF protection)

Security Headers

Every API response includes:

  • Strict-Transport-Security (HSTS with preload)
  • X-Frame-Options: DENY
  • X-Content-Type-Options: nosniff
  • Content-Security-Policy (dynamic, scoped to allowed origins)
  • Referrer-Policy: strict-origin-when-cross-origin

Secrets Management

  • AWS Secrets Manager for production credentials
  • No secrets in environment variables, config files, or code
  • API key secrets stored as SHA-256 hashes (never in plaintext)
  • Full key shown exactly once at creation

Rate Limiting

SurfaceDefault Limits
Funder API500 req/min, 50 req/sec (burst)
Calculator loads30/min per deal
Submissions5/hour per deal
Upload URLs30/hour per deal
Global1,000 req/sec

Per-funder overrides via rateLimits JSONB. Redis sliding windows with graceful degradation.


Roles & Permissions

Six Roles

RoleAccess Level
Platform AdminAll permissions. Immutable. FoxCalc team only.
AdminFull organization access — deals, settings, users, keys, webhooks, audit.
UnderwriterDeal management — view all, edit, release, revoke, assign. Read-only settings.
ISO RM ManagerView all deals + activity. Read-only.
ISO RMView own assigned deals only.
API OnlyNo portal access. API key usage only.

20 Permission Atoms

Organized across 7 categories: Deals (9), Settings (2), Users (3), API Keys (3), Webhooks (1), Audit (1), Reports (1).

Three-Layer Resolution

  1. Baseline — hardcoded per-role defaults
  2. Funder customization — editable matrix in portal (per funder)
  3. User overrides — per-user grants/revokes on top of role

Audit & Compliance

Audit Log

Every administrative action logged with:

  • Actor (portal user, API key, or system)
  • Action type (CRUD operations on any resource)
  • Resource type and ID
  • Request metadata (IP, user agent)
  • Sanitized payload (13 sensitive patterns scrubbed)
  • Timestamp (UTC)

Portal UI: paginated, filterable by action, actor, resource, date range.

Deal Activity Log

Every deal lifecycle event:

  • Calculator opened (IP, user agent)
  • Offer created, replaced, patched, revoked, expired
  • Submission received
  • Callback attempts and results

Login History

Per-user login tracking:

  • Login, logout, token refresh, failed login
  • IP address, user agent, session ID
  • Success/failure with reason
  • Portal UI with pagination and filters

Offer History

Full version history with before/after snapshots:

  • Every create, replace, patch, revoke
  • Changed field lists
  • Actor tracking (API key or portal user)

Compliance Engine (Planned)

Stubbed for V2: APR calculation, state-specific disclosures (NYDFS, CA SB 1235, Virginia, Utah). Funder-computed compliance data passthrough supported in V1.


Infrastructure & Reliability

Tech Stack

LayerTechnology
BackendNode.js, TypeScript, Fastify
CalculatorReact 19, Vite
PortalNext.js 15
DatabasePostgreSQL 16, Drizzle ORM
Cache/QueueRedis 7, BullMQ
StorageS3 (encrypted at rest)
EmailSendGrid
HostingAWS (ECS Fargate, RDS, ElastiCache, CloudFront, S3)
CI/CDGitHub Actions (4 workflows)

Architecture

Monorepo with 4 packages sharing a single formula engine:

  • @foxcalc/shared — types, formulas, constants (imported by all)
  • @foxcalc/api — Fastify backend
  • @foxcalc/calculator — React SPA
  • @foxcalc/portal — Next.js admin app

Calculator and API share a domain via CloudFront path-based routing: /* → S3 (calculator), /api/* → ALB (API). Zero CORS anywhere.

Database

15 tables, 19 migrations, optimistic concurrency via version integers. All timestamps UTC (ISO 8601). JSONB for flexible payloads alongside structured columns.

Background Workers

  • Callback delivery — BullMQ, 5 concurrent, 8 retries with exponential backoff
  • Webhook dispatch — BullMQ, 10 concurrent, fire-and-forget
  • Expiration cron — every 5 minutes, flips active offers past expiration date
  • Expiry warnings — every 5 minutes, emails for deals expiring within 24 hours

Testing

PackageTests
Shared108 unit tests (formulas, types, constants)
API422 unit + integration tests
Calculator22 unit tests
Portal239 E2E Playwright tests
Total791 tests

Adapter Pattern

All funder-specific behavior is isolated in three adapter boundaries:

AdapterResponsibility
InboundField mapping, auth, payload normalization
OutboundCallback format, delivery method, signing
NotificationEmail templates, branding, delivery channel

Adding a new funder means writing three adapter files. Zero changes to core platform code. "That's a config change" is the right answer to every funder request.