Greenbox Capital Goodwood Consulting
Box ↔ HubSpot API Sync
Project Plan 2026

Box ↔ HubSpot API Sync — Project Plan 2026

Goodwood-owned middleware connecting Greenbox’s Box-side data to HubSpot via the API M gateway. 4-week fixed-fee POC, mock-first design, real-time-ready architecture. Outcome: a working pipeline across all four object types with audit, drift detection, and production cutover plan.
v1.0 · Sprint kickoff 05/26/2026 · Demo 06/22/2026

Snapshot

4 wks
Sprint length
4
Object types in scope
13
Deliverables
~real-time
Sync cadence target

Project goal

Build a Goodwood-owned middleware that consumes Greenbox’s API M gateway endpoints (Merchants, Owners, Loan Requests, Brokers) and lands data into HubSpot using canonical-ID lookup at every write — no hs_object_id stored on the Box side, no dangling pointers after HubSpot merges. The pipeline ships against mock endpoints first so Greenbox-side endpoint work can proceed in parallel; the mock spec doubles as the API contract Greenbox implements to.

Alignment meeting (05/13/2026)

Decisions confirmed at the kickoff alignment meeting with Sarah Lackey, Kris Glaittli, Brian LaMure, Kusha Kapoor, Nova Guliyev, and Steven Lentz.

Meeting artifact

AI Summary →

Decisions, action items, attendees, notable quotes. Cleanest one-page reference.

Meeting artifact

Full Transcript →

Verbatim Teams-generated transcript, 47 min, color-coded by speaker.

Meeting artifact

Recording

Hosted on Greenbox Teams — access via Sarah’s calendar event for 05/13/2026.

DecisionOutcome
Middleware ownershipGoodwood owns the integration end-to-end — transformer, reconciler, audit, drift detection.
Source-of-truth read pathGreenbox exposes REST endpoints via an API M gateway for Merchants, Owners, Loan Requests, Brokers.
Sync cadenceNear real-time: event-driven preferred, 15-min polling as fallback.
Round-trip hs_object_idRetired. HubSpot lookup is by canonical Greenbox-side ID with unique constraints.
ID schemePrefixed-string IDsMID-12345, MOID-67890, SUB-..., BID-... — per Brian’s proposal. Eliminates collision risk across object types.
Hosting stackFirebase assumed for this sprint — final Firebase vs Databricks call rests with Sarah + Brian. A flip mid-sprint requires re-pricing.

Architecture

GREENBOX SIDE Box 1.0 SQL Merchants · Owners Loan Requests · Brokers API M Gateway GET /merchants?since=… GET /merchant-owners?since=… GET /loan-requests?since=… GET /brokers?since=… GOODWOOD MIDDLEWARE (FIREBASE) boxDeltaPoller Cloud Scheduler · 15 min boxWebhookReceiver HTTPS · HMAC-signed transformer Normalize · filter · map reconciler Canonical-ID lookup Firestore mirror · box_records · box_sync_runs · cursors · drift_alerts Hash-based idempotency · per-record audit driftReconciler · daily Cloud Monitoring SYSTEM OF RECORD HubSpot v4 Portal 5840146 Companies (Merchants) key: merchant_id Contacts (Owners) key: merchantsownersid Deals (Loan Requests) key: loan_request_id Brokers (Companies) key: broker_id drift read

Tech stack

Greenbox side

API M Gateway

  • REST + JSON
  • Auth: API key / HMAC
  • OpenAPI 3.0 spec
  • Cursor-based pagination
  • Prefixed-string IDs
Goodwood middleware

Firebase / GCP

  • Cloud Functions Gen 2
  • Firestore (canonical mirror + audit)
  • Cloud Scheduler
  • Secret Manager
  • Cloud Monitoring
  • Node 20 ESM
System of record

HubSpot v4

  • CRM v3 + v4 APIs
  • Sandbox + Production
  • Private App auth
  • Canonical-ID upsert
  • v4 batch associations

Scope — 13 deliverables

#DeliverableDescription
1Firebase foundationCloud Functions Gen 2, Firestore, Secret Manager, deploy safety with explicit project-flag enforcement.
2Mock endpoints + OpenAPI specGoodwood-hosted mocks for all four endpoints. The spec becomes the contract Greenbox builds to.
3End-to-end pipeline (4 object types)Poller → transformer → reconciler → HubSpot, covering Merchants, Owners, Loan Requests, Brokers.
4v4 batch associationsDeal↔Merchant, Deal↔Broker, Contact↔Merchant. Idempotent.
5Core transformationsDeclineReasons comma→semicolon, Title Case, phone normalization, invalid-email filter, picklist value-to-null fallback.
6Audit logEvery input → transform → HubSpot response logged to box_sync_runs/{batchId}/{type}/{recordId}. Per-record lineage.
7Drift reconcilerDaily diff job. Firestore mirror vs HubSpot canonical-ID lookup. Drift entries written to drift_alerts.
8Webhook receiverHTTPS Cloud Function for real-time push mode alongside polling. HMAC-signed payloads.
9Unique-constraint migrationBackfill _unique properties on Contact & Company; enable hasUniqueValue: true post-dedup.
10Cloud Monitoring dashboardsPer-function error rate, latency, throughput. Uptime checks on the webhook receiver.
11HubSpot Sandbox + Private App provisioningGoodwood-handled. Includes a separate read-only Private App for high-volume drift reads.
12Production cutover + parallel-runPipeline runs alongside the legacy Box Update Service (source 51841) for validation; retire on sign-off.
13Sprint demo + runbookEnd-to-end walkthrough; operational handoff doc; cutover recommendation.

Timeline

WeekFocusMilestone
1
05/26 – 05/29
Firebase foundation. Mock endpoints stood up. OpenAPI spec published. Reconciler skeleton with canonical-ID lookup. API contract review with Brian / Kusha / Nova. Contract accepted · Greenbox endpoint team unblocked
2
06/01 – 06/05
Pipeline for all four objects. Transformer with core mappings + edge cases. v4 batch associations. Audit log to Firestore. Cursor management. End-to-end mock-to-Sandbox demo
3
06/08 – 06/12
Drift reconciler. Webhook receiver. Cloud Monitoring dashboards. Unique-constraint migration prep. If Greenbox endpoints live → swap mocks for real endpoints. Full pipeline with observability live
4
06/15 – 06/19
Parallel-run alongside legacy Box Update Service. Cutover plan. Runbook. Sprint demo. Demo accepted · cutover decision

API contract — the four endpoints

Greenbox implements the API M gateway to match this contract. Mock endpoints Goodwood hosts during Week 1 are the exact spec.

EndpointReturnsNotes
GET /api/merchants?since={iso}&limit=1000 Merchant rows (HubSpot Companies) Cursor-paginated. Prefixed string IDs (MID-...). Includes status, qualification, balance, SIC, address fields.
GET /api/merchant-owners?since={iso}&limit=1000 Merchant owner rows (HubSpot Contacts) Primary + secondary owners arrive as separate rows. Resolves the multi-entity case at source.
GET /api/loan-requests?since={iso}&limit=1000 Submission / loan-request rows (HubSpot Deals) Includes DeclineReasons raw — transformer applies comma→semicolon.
GET /api/brokers?since={iso}&limit=1000 Broker rows (HubSpot Broker Companies) Prefixed string IDs (BID-...). Includes broker contact info, RAM assignment.
POST {goodwood-webhook} OPTIONAL Push delta on every Box write Real-time mode. Goodwood exposes the webhook URL. Either-or with polling endpoints.

All endpoints: API-key or HMAC auth. Idempotent (same since + no new changes = same response). OpenAPI 3.0 spec published in Week 1.

HubSpot field mapping

The complete CREATE + UPDATE field contract. These are the fields the new endpoints must return per object type. Three tables: Company (Merchant + Broker), Contact (Owner), Deal (Loan Request). CREATE + UPDATE = field flows on both initial create and every change. CREATE only = set once on creation. Confirmed via 90-day property-history audit of the legacy Box Update Service (source 51841); slow-cadence fields marked schema-inferred pending follow-up history audit.

Company (Merchant + Broker)

HubSpot propertyBox source fieldTypePhaseNotes
merchant_idMerchant IdnumberCREATE + UPDATECanonical key. Prefixed-string form MID-....
merchant_statusMerchantStatusstringCREATE + UPDATE
merchant_payment_statusPaymentStatusStringstringCREATE + UPDATE
last_box_updatelast_box_updatedatetimeCREATE + UPDATEIntegration heartbeat
dbaDBAstringCREATE + UPDATETitle Case
business_emailEmailAddressstringCREATE + UPDATE
business_faxBusinessFaxstringCREATE + UPDATE
do_they_have_a_business_bank_accountDo they have a business bank account?enumCREATE + UPDATEYes/No
do_they_meet_minimum_revenue_requirements_Do they meet minimum revenue requirements?enumCREATE + UPDATEYes/No
have_they_met_time_in_business_requirement_Have they met time in business requirement?enumCREATE + UPDATEYes/No
is_the_business_open_Is the business open?enumCREATE + UPDATEYes/No
company_structurecompany_structurestringCREATE + UPDATE
date_establishedBusinessEstablisheddateCREATE + UPDATEISO date
home_based_flagIsHomeBasedboolCREATE + UPDATETRUE/FALSE
is_business_seasonal_IsSeasonalboolCREATE + UPDATETRUE/FALSE
length_of_ownershipLengthOfOwnershipnumberCREATE + UPDATEdecimal years
not_for_profit_flagIsNotProfitOrgboolCREATE + UPDATETRUE/FALSE
products_and_services_soldProductsServicesSoldstringCREATE + UPDATE
sic_codeSicCodestringCREATE + UPDATE
sic_code_level_2SicCodeLevelTwostringCREATE + UPDATE
sic_code_level_3SicCodeLevelThreestringCREATE + UPDATE
outstanding_balanceCurrentBalancenumberCREATE + UPDATE
percent_balance_remainingPerccentBalanceRemainingnumberCREATE + UPDATEnote CSV header typo
province_from_the_boxProvincestringCREATE + UPDATE
merchant_status_change_dateMerchantStatusChangeDatedateCREATE + UPDATEschema-confirmed
pay_off_datePayOffDatedateCREATE + UPDATEschema-confirmed
renewal_eligible_dateFundingEligibleDatedateCREATE + UPDATEdrives renewal cadences
nameNamestringCREATE onlyHS-native; Title Case
addressStreet AddressstringCREATE onlyHS-native; Title Case
address2ApartmentstringCREATE onlyHS-native; Title Case
cityCitystringCREATE onlyHS-native; Title Case
stateStatestringCREATE onlyUPPER if 2-letter
zipPostal CodestringCREATE onlyHS-native
countryCountryNamestringCREATE onlyHS-native
phonePhone NumberstringCREATE onlyXXX-XXX-XXXX
websiteWebsite URLstringCREATE onlyHS-native
hs_lead_statusLead statusenumCREATE onlyHS-native
company_type(derived)enumUPDATE onlyDistinguishes Merchant vs Broker
main_lead_source / sub_lead_source(policy)stringUPDATE onlySource-attribution rules
Candidate Box-sourced fields below — schema-inferred (label / description / code reference). Verify via property-history audit during Week 1.
ram_email_address_box(Box: RAM assignment)stringschema-inferredRenewal Account Manager email. Drives renewal deal-owner routing.
broker_idBroker Idnumberschema-inferredPrimary key on Broker Companies (prefixed BID-...)
box_broker_id__uniqueBroker Id (string)stringschema-inferredAlready hasUniqueValue: true
merchant_balance_remaining(Box: Advance balance API)numberschema-inferred“Set by API from the Box” per schema description
merchant_box_url(Box: deep link)stringschema-inferredDeep-link URL into Box UI
broker_of_record___greenbox(Box flag)enumschema-inferredFlag: Greenbox is broker of record

Contact (Merchant Owner)

HubSpot propertyBox source fieldTypePhaseNotes
merchantsownersidMerchantOwnerIdstringCREATE + UPDATECanonical key. Prefixed-string form MOID-.... Top-frequency property in the legacy audit.
associated_merchant_idMerchant IdstringCREATE + UPDATEForeign key to Company
associated_broker_id(derived)stringCREATE + UPDATEForeign key to Broker
last_box_updatelast_box_updatedatetimeCREATE + UPDATEIntegration heartbeat
contact_type(derived from MOID presence)enumUPDATE only
state_region(derived from address)stringUPDATE only
state_abbreviation(derived from address)stringUPDATE only
province(derived from address)stringUPDATE only
zip_code(derived from address)stringUPDATE only
firstnameFirst NamestringCREATE onlyHS-native; Title Case
lastnameLast NamestringCREATE onlyHS-native; Title Case
phonePhone NumberstringCREATE onlyXXX-XXX-XXXX
emailEmailstringCREATE only⚠️ HubSpot auto-merges on collision — endpoint must resolve multi-entity at source
mobilephoneHomePhonestringCREATE onlyConfirm canonical property name
main_lead_source / sub_lead_source(policy)stringUPDATE onlyLead-source workflow

Deal (Loan Request / Submission)

HubSpot propertyBox source fieldTypePhaseNotes
loan_request_idSubmission IdnumberCREATE + UPDATECanonical key. Prefixed-string form SUB-....
associated_merchantidMerchant IdstringCREATE + UPDATEForeign key to Company
associated_brokeridPartnerIdstringCREATE + UPDATEForeign key to Broker
associated_merchantownersidMerchantOwnerIdstringCREATE + UPDATEForeign key to Contact
brokerageBrokeragestringCREATE + UPDATEPartner Legal Name
received_offer_tsReceived DatedateCREATE + UPDATE
funded_byFunded BystringCREATE + UPDATE
greenbox_repGreenboxRepenumCREATE + UPDATEMust match approved picklist
merchant_payment_statusMerchant payment statusstringCREATE + UPDATE
merchant_statusMerchant statusstringCREATE + UPDATE
outstanding_balanceCurrentBalancenumberCREATE + UPDATE
is_active_IsActiveboolCREATE + UPDATETRUE/FALSE
box_processing_statusLuProcessingStatusIdstringCREATE + UPDATENumeric pipeline status code
decline_reasonDeclineReasonsstringCREATE + UPDATE⚠️ comma→semicolon transform required
decline_date__cDeclineDatedateCREATE + UPDATE
deactivation_date__cDeactivationDatedateCREATE + UPDATE
inactive_reasonDeactivationReasonsstringCREATE + UPDATESame comma→semicolon transform
last_box_updatelast_box_updatedatetimeCREATE + UPDATEIntegration heartbeat
amountAmountnumberCREATE onlyHS-native
dealnameDeal namestringCREATE onlyHS-native
closedateClose datedateCREATE onlyHS-native

Why canonical-ID lookup (no hs_object_id round-trip)

The legacy pattern stored HubSpot’s hs_object_id back in Box on every create so future Box writes could PATCH directly. That pattern is no longer viable — when HubSpot merges two records, the merged record gets a new canonical id and the old one is retired. Any hs_object_id Box has stored becomes a dangling pointer the next time it’s used.

The modern pattern is the inverse: look up records by canonical Greenbox-side IDs every time, with HubSpot uniqueness constraints making the lookup O(1):

Per Box delta record:
  → reconciler reads canonical ID (MID-... / MOID-... / SUB-... / BID-...)
  → HubSpot v4: GET /crm/v3/objects/{type}/{canonical_id}?idProperty={canonical_prop}
  → 200 → PATCH that record    | 404 → POST new record
  → no Box-side ID storage, no callback required
  → HubSpot merges are invisible to Box (canonical ID survives the merge)

Prerequisite: canonical Greenbox-side ID properties on Contact and Company set to hasUniqueValue: true in HubSpot schema. Migration is in scope (deliverable #9) and runs after the dedup pre-cleanup pass.

Greenbox responsibilities

ItemOwnerWindow
API contract review & sign-off on OpenAPI specBrian + Kusha + NovaWeek 1
Prefixed-string ID scheme confirmation (MID- / MOID- / SUB- / BID-)Brian + KushaWeek 1
Tech stack decision — Firebase vs DatabricksSarah + BrianPre-kickoff
HubSpot API daily-limit increase (request to HubSpot for build / cutover window)SarahBefore Week 3
API M endpoints implemented & reachable from Goodwood FirebaseBrian + Kusha + NovaWeek 3 target (Week 4 latest)
PII classification per field exposed through the gatewayGreenbox security reviewBefore production cutover
Sprint demo attendance (1 hour)Sarah + Kris + BrianEnd of Week 4

Risks

RiskLikelihoodMitigation
Greenbox endpoint slip past Week 3 Medium Mock-first design absorbs this. Demo runs on mocks if real endpoints not ready; cutover deferred to a short follow-on.
HubSpot API daily-limit pressure Medium Goodwood provisions a separate read-only Private App for drift reconciler reads. Greenbox to request HubSpot quota increase before Week 3 cutover load.
Tech-stack flip to Databricks mid-sprint Low SOW pricing anchored to Firebase. Transformer logic ports cleanly (pure functions); orchestration re-platform would be a change order.
Auth scheme negotiation Medium Resolved Week 1 contract review. Sprint budget covers one variant (API key, HMAC, or JWT); multi-auth = scope change.
Duplicate canonical IDs block unique-constraint migration Medium Dedup pre-cleanup pass in Week 3. If material duplicate volume remains, migration deferred to a separate engagement.

Definition of done

Stakeholders

PersonRole
Sarah LackeyGreenbox — VP of Technology · project sponsor
Kris GlaittliGreenbox — VP of Sales · business owner
Brian LaMureGreenbox — architect, API M gateway + prefixed-string ID scheme
Kusha KapoorGreenbox — developer team lead, Box-side data owner
Nova GuliyevGreenbox — endpoint co-implementer
Steven LentzGreenbox — SQL / data
Ryan ThibodeauxGoodwood — lead architect, middleware owner
Source of truth: this plan doc is published from public/greenbox/reports/box-hubspot-api-sync-plan-2026.html in the greenbox-core-apps repo. Project pricing & commercial terms are in the companion Statement of Work.