Open To Close (OTC) Webhook Handler
Receives real-time transaction lifecycle events from Open To Close — creation, updates, stage changes, closings, overdue checklist items, and document uploads. Syncs transaction data to Supabase otc_transactions table and fires Discord alerts to #betterfiles. Mounted on the quo-handler-enhanced Express app via mountOtcRoutes(app).
Endpoint
| Path | Provider | Configure in |
|---|---|---|
https://discord-bot.reri.co/webhook/otc | Open To Close | OTC Dashboard → Settings → Integrations → Webhooks |
Port: Shares 18792 with quo-handler-enhanced.js (mounted via mountOtcRoutes(app))
Not a standalone service — routes are added to the OpenPhone/Quo handler Express app.
Events to register in OTC:
transaction.createdtransaction.updatedtransaction.stage_changedtransaction.closedchecklist.item_overduedocument.uploaded
Auth method
HMAC-SHA256 — HMAC(OTC_WEBHOOK_SECRET, rawBody) hex digest, compared against the OTC-provided signature header.
Fail-open: If OTC_WEBHOOK_SECRET is not configured OR the signature header is absent, verification is skipped (handler logs warning and continues). Set OTC_WEBHOOK_SECRET in master.env to enforce.
Sources verified: workspace/knowledge-base/opentoclose/API.md (2026-03-04)
Payload shape
OTC sends JSON transaction objects. Key fields extracted:
| Field | Source field | Description |
|---|---|---|
otc_property_id | transaction_id / property_id / id | OTC unique identifier |
property_address | property_address | Street address |
property_city | city / property_city | City |
property_state | state / property_state | State abbreviation |
property_zip | zip / property_zip | ZIP code |
county | county | County |
contract_title | contract_title | Transaction name/title |
contract_status | status / contract_status | Current status |
Raw payload also stored in raw_field_values JSONB column for agent access.
Downstream dispatch
otc-handler.js (mounted on quo-handler-enhanced)
├─ verifyOtcSignature(rawBody, signature) → HMAC check (fail-open)
├─ syncTransactionToSupabase(txnData) → supabase.from('otc_transactions').upsert()
└─ Discord notify → DISCORD_BOT_TOKEN → #betterfiles channel (1473827960677994559)
on: transaction.stage_changed, transaction.closed, document.uploaded
Supabase table: otc_transactions (upsert keyed on otc_property_id)
Dedup strategy
Upsert semantics on otc_property_id — repeated events for the same transaction overwrite the previous row rather than creating duplicates. For document/checklist events that don’t map to a transaction ID, events are inserted with a generated key.
Audit trail
- Supabase:
otc_transactionsupsert serves as the primary audit trail. - Discord:
#betterfileschannel (ID1473827960677994559) receives alerts on stage changes, closings, and document uploads. - Logging: Handled via the parent
quo-handler-enhancedapplication logging middleware. - Sources:
workspace/knowledge-base/opentoclose/API.md(field names verified 2026-03-04).
Related
- webhook-architecture — Cross-handler governance; OTC handler is mounted on quo-handler-enhanced (port 18792), not standalone
- _summary — BetterFiles agent consumes OTC transaction data for TC workflow and closing coordination
- hubspot — HubSpot deals are associated with OTC transaction records via property address matching