← All reports

Process map — acquisition to exit

How a person becomes a lead, an applicant, a resident, and eventually a former resident — and every system, role, and trigger in between.

How to read this page: Every arrow is a real handoff in the platform. Mermaid charts show data flow + actors. Tables under each chart describe what kicks off each step + which roles are accountable. Drill-throughs go to the underlying ADRs and dashboards.
Sections

1. Lead source map

Every funnel-top channel funnels into the same Postgres lead_inbound table and emits a crm_sync_event mirrored to Dataverse. Ad-network pixels (Meta CAPI, TikTok, Google Ads, Plausible) fan out client-side + server-side, deduped by event_id.

flowchart LR
  subgraph PaidChannels["Paid channels"]
    fb[Facebook Lead Ad]
    google[Google Ads search]
    tiktok[TikTok Lead Ad]
  end
  subgraph OrganicChannels["Organic + offline"]
    driveby[Drive-by sign]
    referral[Resident referral]
    walkin[Walk-in tablet]
    phone[Inbound phone]
  end
  subgraph LandingForms["crosbyheights.com / willislakes.com"]
    inq[/inquire form/]
    tour[/tour form/]
    call[/schedule-call form/]
    apply[Apply Now]
  end
  fb-->|FB Leadgen webhook| api
  google-->|landing visit| LandingForms
  tiktok-->|landing visit| LandingForms
  driveby-->LandingForms
  referral-->LandingForms
  walkin-->|tablet POST| api
  phone-->|operator logs| api
  inq-->|POST /api/intake/inquire| api
  tour-->|POST /api/intake/tour| api
  call-->|POST /api/intake/schedule-call| api
  apply-->|POST /api/apply/inquire| api[(portal-api)]
  api-->|insert| li[(lead_inbound)]
  api-->|emit| sync[(crm_sync_event)]
  sync-->|drain worker| dv[(Dataverse · lead)]
  api-.->|fan out| pixels[Meta CAPI / TikTok / Google / Plausible]
ChannelPathWhat's required to wire
Landing inquire/tour/callCF Function → portal-api intakeLive — shipped 2026-05-15
Apply-form inquireportal-api /api/apply/inquireLive
Facebook LeadgenFB webhook → portal-apiPending Meta App registration + page subscription (task #175)
Google Lead FormGoogle webhook → portal-apiNot started — needs Google Ads campaign with lead form
TikTok Lead AdTikTok Pixel API → portal-apiNot started
Walk-in tabletPOST /api/intake/walkinEndpoint stubbed; UI is task #67
Phone call logPOST /api/intake/phone-logEndpoint stubbed; UI is task #66

2. Applicant flow (ADR-0030 v3)

Mobile-first form, OTP-gated, with Plaid IDV up front for both primary and co-applicant before the application fee — so we screen out bad data before paying $25/applicant for Kredifi background checks.

sequenceDiagram
  participant App as Applicant
  participant Form as apply.<site>.com
  participant API as portal-api
  participant Plaid
  participant ACS as ACS SMS
  participant AN as AuthNet
  participant Kr as Kredifi
  App->>Form: visits apply
  Form->>API: POST /apply/signup (email, phone)
  API->>ACS: send OTP
  ACS-->>App: SMS code
  App->>Form: enters code
  Form->>API: POST /apply/otp/verify
  API-->>Form: applicant_id + session
  Form->>API: POST /plaid/link-token (product=idv)
  API->>Plaid: create IDV session
  Plaid-->>API: link_token
  API-->>Form: link_token
  Form->>App: Plaid Link iframe — gov-ID + selfie
  Form->>API: POST /plaid/idv/result
  Note over Form,API: ↓ if co-applicant
  Form->>API: POST /apply/coapp/invite (email, phone)
  API->>ACS: SMS deep link to co-app
  Note over App: Co-app does the same flow on their device
  Form->>API: POST /payment/charge (Accept.js opaque_data)
  API->>AN: createTransaction
  AN-->>API: trans_id
  API->>API: emit application.fee_paid sync event
  API->>Plaid: trigger income link_token (both applicants)
  API->>Kr: createOrder (background check, both)
  Kr-->>API: order_id + status
  API->>ACS: SMS decision to primary
StepTriggerRecords written
SignupOTP verifiedapplicant_credential + application + application.started sync
Primary IDVPlaid Link onSuccessplaid_session(idv, success) + application.idv_completed sync
Co-applicant invited"Yes I have a co-app"applicant_invitation + application.coapplicant_invited sync + ACS SMS
Co-app IDVCo-app completes via deep linkplaid_session + application.idv_completed sync
Fee paidAuthNet capture succeedspayment + application.status='paid' + application.fee_paid sync
Income verifiedPlaid income complete (each applicant)plaid_session(income, success) + application.income_verified sync
Screening orderedBoth IDV+income donekredifi_screening + application.screening_ordered sync
Screening resultKredifi poll or webhookkredifi_screening.recommendation + application.screening_complete sync

3. Payment + screening (autofire chain)

flowchart TB
  pay[application.fee_paid] -->|cron 60s| adv[apply_state_advancer]
  adv -->|both IDV success?| inc[fire Plaid income link tokens]
  inc -->|both income success?| krfan[fire Kredifi for both applicants]
  krfan -->|poll status / webhook| dec[decision rules]
  dec -->|score >= threshold| ok[approve]
  dec -->|score < threshold| cond[conditional / manual review]
  dec -->|disqualified| deny[deny → adverse action letter]
  ok --> docs[lease envelope via Documenso]
  cond --> pm[PM review queue]
  deny --> fcra[FCRA adverse action notice + 30d dispute hold]

4. Move-in prep

flowchart LR
  signed[lease signed in Documenso] -->|webhook| portal[portal-api]
  portal -->|emit| lc[lease.signed sync event]
  portal --> inspect[inspection.scheduled]
  inspect --> tech[Tech does inspection]
  tech -->|inspection cleared| ppsk[PPSK code generation]
  tech -->|inspection has holds| holds[blockers queue · PM review]
  ppsk -->|Assa Abloy API| gate[Gate code programmed]
  gate --> flip[Portal mode flip → resident]
  flip --> welcome[Welcome email + first invoice]

5. Resident steady state

flowchart LR
  cron[weekly cron] --> inv[invoice.created · rent + utilities]
  inv -->|AuthNet ARB or ACH| capture[payment.captured]
  inv -->|NSF / decline| failed[payment.failed → arrears flag]
  resident[Resident submits ticket] --> maint[maintenance.requested]
  maint -->|PM assigns| tech[Technician]
  tech -->|completes| done[maintenance.completed]
  cron2[lease renewal cron · 60/30/14d before end] --> offer[lease.renewal_offered]
  offer -->|sign| ren[lease.renewed → chained lease row]

6. Exit + SODA + refund

flowchart LR
  ntv[Resident NTV] -->|portal/PM| ntvd[lease.ntv_given]
  ntvd --> mout[move-out inspection scheduled]
  mout -->|tech submits| moutc[inspection.move_out_completed]
  moutc --> drft[soda.drafted — auto from move-out form]
  drft -->|PM reviews| fin[soda.finalized — 7-day rule]
  fin --> dmg[damages.invoiced if applicable]
  fin --> ref[refund.issued — AuthNet first, check fallback]
  ref -->|unclaimed 90d| esc[refund.escheated]
  fin --> mo[resident.moved_out → lease.closed]

7. Role responsibilities at a glance

StepOwnerWhat they do
Application reviewPMDecide approve/conditional/deny on borderline Kredifi scores via /funnel
Tour schedulingApplicant + PMApplicant self-books on landing; PM gets .ics calendar invite to property mailbox
Send contractPMClick "Send lease" in /funnel; portal fires Documenso envelope
Move-in inspectionTechWalks pad, photos, signs off via inspection app; clears or holds
PPSK + gate codeSystemAuto-fires on inspection cleared; SMS code to resident
Rent captureSystemWeekly AuthNet ARB pulls + Plaid balance pre-flight
Bank recAccountantBC reconciliation, Zelle match, salesreceipt verification
SODA finalizationPMWithin 7 days of move-out per ADR-SODA
Refund disbursementSystemAccountantAuthNet refund first; if fails, check via accountant; escheatment timer

8. System of record matrix

What lives wherePostgres (portal)Dataverse (CRM)BC (Business Central)R2Vault
Applicants + stateSoRmirror
Leads (PM read surface)SoR for read
Invoices + paymentsSoRmirrorbook of record
LeasesSoRmirrorsigned PDFs
Inspection photos / docsmetadata onlySoR
Secrets / credsSoR (gr-portal-kv)
Bank statementsSoRarchive

9. External integrations

ServiceWhat it does for usStatus
PlaidIdentity verification + income verification + balance pre-flight before ACHWired (sandbox) · prod creds in vault
KredifiFCRA background screening (credit + criminal + eviction)Stub built · UAT creds pending (task #185)
Authorize.NetCard + ACH (eCheck.Net) charges; ARB for autopayLogin + transaction keys in vault · signature keys pending (task #190)
DocumensoE-signature for leases, NTV, adverse-action letters, renewalsNot yet wired
PPSKProperty-wide WiFi passkey + gate accessNot yet wired · GH repo gated-rentals/ppsk
Assa AbloyGate hardware code programmingNot yet wired · manual today
ACS SMSOTP + transactional SMS · per-tenant toll-freeCrosby +1 (866) 366-5403 · Willis +1 (866) 366-5727 · toll-free verification pending
ACS EmailTour .ics + decision notices + transactional emailConnection-string slot exists; wrapper shipped 2026-05-15
Microsoft Bookings(Replaced by /api/intake/tour with .ics)Deprecated 2026-05-15

10. Failure paths + escalations

What goes wrongHow we catch itWhat happens next
Application deniedKredifi score below thresholdFCRA adverse-action letter via Documenso · 30-day dispute hold on file · automatic destroy of report at retention end
Card declinedAuthNet sync response code != 1UI shows reason; applicant re-tries with different card; no state change
ACH returned (NSF)AuthNet eCheck webhook · or daily statement scrapearrears flag · late notice cron · returned-payment fee from rate sheet
Sync response lost but charge succeededAuthNet webhook + reconciler cronIdempotent: webhook fills in the missing payment row via authnet_webhook_event.notification_id
Move-out without SODADaily cron · finrep cross-check30/60/90d reminders to PM · escalation to accounting
Refund unclaimedrefund.issued timer90d → escheatment workflow to Texas Comptroller
Webhook signature mismatchHMAC verification failsLogged as dropped_unverified in authnet_webhook_event · alert on rate
CRM sync failurecrm_sync_event.status='failed' with last_errorExponential backoff (2^attempts seconds capped at 1h, max 8) · PM dashboard shows stuck events
Plaid IDV failPlaid Link onSuccess with status='failed'Applicant offered a second attempt; if 2nd fails, manual review queue for PM
Sources of truth backing this page: ADR-0021 (portal-first / intentional sync) · ADR-0028 (applicant state machine) · ADR-0029 (applicant flow v2) · ADR-0030 (applicant flow v3, IDV up front) · ADR-0031 (CRM sync event catalog). Open docs/decisions/ in the platform repo for full text.

Generated 2026-05-15 · Updates when ADRs change · Mermaid renders via cdn.jsdelivr.net