HomeAnalytics+
Developer integration guide

Turn property data into timely action.

Connect HomeAnalytics events to Oppy so real estate, lending, mortgage, insurance, and proptech teams can respond to meaningful property changes without manual monitoring.

HomeAnalytics supplies structured US property intelligence, while Oppy turns the events you choose into communications, work, and continuously refreshed analytics.

One-minute quickstart

Receive, authenticate, deduplicate, normalize, and create one sandbox Oppy event.

Start the quickstart
01

Secure the connection

Keep the HomeAnalytics key and Oppy credentials on your backend. Expose one versioned HTTPS receiver.

02

Ingest useful events

Normalize real property signals into stable, namespaced events in the user’s Oppy account.

03

Automate and verify

Recommend rules from observed events, then test every selected trigger and outcome end to end.

Implementation contract

This guide is vendor-neutral. A developer or coding agent can implement it in any backend that can call REST APIs, receive HTTPS requests, preserve raw request bytes, and store secrets securely.

The only manual secret step: the user creates or obtains their HomeAnalytics API key and supplies it through your secure setup flow. Do not request it in email, chat, logs, analytics, URLs, or client-side code.

Prerequisites

  • A HomeAnalytics account and API key.
  • An Oppy account authorized for the target user or organization.
  • A public, versioned HTTPS endpoint, such as /webhooks/homeanalytics/v1.
  • A secret manager, encrypted connection storage, and a durable queue.

HomeAnalytics publicly documents REST over HTTPS, JSON, Bearer authentication, five property-data endpoints, and 60 credits per call. Its public documentation mentions job-completion and property-update webhooks, but does not publish the complete webhook wire schema, signature header, retry policy, rate limits, or SLA. Confirm those details in the authenticated provider documentation before production. Sources: API reference and documentation.

1. One-minute quickstart

  1. Set WEBHOOK_SECRET and install one receiver below.
  2. Configure the upstream sender to sign the exact raw JSON body with HMAC-SHA256. Replace the placeholder header only after confirming HomeAnalytics' authenticated webhook documentation.
  3. Post the illustrative fixture to your receiver, verify one queue job, then use the MCP example to create one sandbox Oppy event.
  4. Create a disabled automation rule for homeanalytics.avm.changed, test it, then enable it after human approval.

Boundary: HomeAnalytics does not publish a complete webhook body or signature-header contract on its public pages. The receiver and source fixture below are implementation templates, not an official HomeAnalytics schema. Oppy's outbound header X-Webhook-Signature is official only when receiving a Send Webhook action from Oppy. Source: Oppy Send Webhook guide.

2. Run a secure webhook receiver

Both examples preserve the raw bytes and compare equal-length buffers in constant time. They default to X-Webhook-Signature only for an Oppy Send Webhook return path. For a HomeAnalytics-originated webhook, set SIGNATURE_HEADER to the authenticated provider's documented header before deployment.

Node.js + Express

npm i express

import express from "express";
import crypto from "node:crypto";

const app = express();
const secret = process.env.WEBHOOK_SECRET;
const header = (process.env.SIGNATURE_HEADER ||
  "x-webhook-signature").toLowerCase();
if (!secret) throw new Error("WEBHOOK_SECRET required");

app.post("/webhooks/homeanalytics/v1",
  express.raw({ type: "application/json", limit: "256kb" }),
  (req, res) => {
    const received = String(req.headers[header] || "");
    const expected = "sha256=" + crypto
      .createHmac("sha256", secret).update(req.body).digest("hex");
    const a = Buffer.from(received);
    const b = Buffer.from(expected);
    const valid = a.length === b.length &&
      crypto.timingSafeEqual(a, b);
    if (!valid) return res.status(401).json({ ok: false });

    let event;
    try { event = JSON.parse(req.body.toString("utf8")); }
    catch { return res.status(400).json({ ok: false }); }

    // Persist idempotency key, enqueue event, then acknowledge.
    console.log("accepted", event.id || "no-source-id");
    return res.status(202).json({ ok: true });
  });

app.listen(process.env.PORT || 3000);

Python + Flask

pip install flask

import hashlib, hmac, json, os
from flask import Flask, request, jsonify

app = Flask(__name__)
secret = os.environ["WEBHOOK_SECRET"].encode()
header = os.getenv("SIGNATURE_HEADER",
                   "X-Webhook-Signature")

@app.post("/webhooks/homeanalytics/v1")
def receive():
    raw = request.get_data(cache=False, as_text=False)
    received = request.headers.get(header, "")
    expected = "sha256=" + hmac.new(
        secret, raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(received, expected):
        return jsonify(ok=False), 401
    try:
        event = json.loads(raw)
    except json.JSONDecodeError:
        return jsonify(ok=False), 400

    # Persist idempotency key, enqueue event, then acknowledge.
    print("accepted", event.get("id", "no-source-id"))
    return jsonify(ok=True), 202

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=int(os.getenv("PORT", 3000)))

Oppy's Send Webhook action makes one request and performs no automatic retry. Its cited public page does not state a numeric timeout, so keep receiver work short and queue slow processing. It can add X-API-Key and X-Webhook-Signature: sha256=<hex>. Acknowledge quickly and queue slow work. Source: Send Webhook Action Developer Integration Guide.

3. Normalize the source event

Illustrative HomeAnalytics fixture, not an official webhook schema

{
  "id": "fixture_20261006_001",
  "type": "property.avm.updated",
  "occurred_at": "2026-10-06T15:00:00Z",
  "data": {
    "property_id": "ha_example_123",
    "previous_value": 742000,
    "value": 779000,
    "confidence": 0.94
  }
}

Map provider-specific fields into a stable internal shape. Do not code this mapping until you have a captured sandbox fixture or authenticated schema.

source.id              -> payload.source_event_id
source.occurred_at     -> payload.as_of
source.data.property_id -> payload.property_id
source.data.value      -> payload.current_value
calculated percent     -> payload.change_pct
connector constant     -> payload.schema_version

Normalized Oppy event

{
  "event_type": "homeanalytics.avm.changed",
  "source": "homeanalytics_connector",
  "message": "Material AVM change detected",
  "payload": {
    "schema_version": "1.0",
    "source_event_id": "fixture_20261006_001",
    "property_id": "ha_example_123",
    "as_of": "2026-10-06T15:00:00Z",
    "previous_value": 742000,
    "current_value": 779000,
    "change_pct": 4.99,
    "confidence": 0.94
  }
}

Use dotted event names. Oppy's public reference documents the domain.action convention and custom event creation through API or MCP: Oppy System Events Reference.

4. Create the event in Oppy

Concrete MCP manage_events call

{
  "action": "create",
  "event_type": "homeanalytics.avm.changed",
  "source": "homeanalytics_connector",
  "message": "Material AVM change detected",
  "description": "Sandbox connector event",
  "status": "active",
  "payload": {
    "schema_version": "1.0",
    "source_event_id": "fixture_20261006_001",
    "property_id": "ha_example_123",
    "as_of": "2026-10-06T15:00:00Z",
    "previous_value": 742000,
    "current_value": 779000,
    "change_pct": 4.99,
    "confidence": 0.94
  }
}

Connect an MCP-compatible client to https://mcp.oppy.pro/mcp using OAuth 2.1 or Authorization: Bearer <api-key>, then call manage_events with the arguments above. The MCP transport is Streamable HTTP over JSON-RPC 2.0. Source: Oppy MCP Connect Guide.

HTTP ingestion samples: this guide intentionally does not publish cURL, Node, or Python requests to a guessed REST event endpoint. The public sources cited here confirm API/MCP event creation but do not expose a complete, stable HTTP endpoint and request contract suitable for copy-paste code. Use the verified MCP call above or the current authenticated Oppy API documentation.

5. Complete the end-to-end recipe

  1. Receive: verify signature against raw bytes. Reject a missing or invalid signature with 401.
  2. Validate: reject malformed JSON with 400 and unsupported event types with 422.
  3. Deduplicate: insert a stable source key under a unique constraint. Treat a conflict as an already-accepted replay.
  4. Acknowledge: return 202 after durable enqueue, not after downstream completion.
  5. Normalize: map the captured provider fixture into homeanalytics.avm.changed.
  6. Ingest: call MCP manage_events with action: create.
  7. Automate: create a disabled rule for homeanalytics.avm.changed with conditions change_pct >= 5 and confidence >= 0.85. Start with a review item or notification, not automatic consumer outreach.
  8. Verify: one matching fixture creates exactly one event and action. A near-miss and duplicate replay create no action.
  9. Approve: have the user approve thresholds, owners, channels, quiet hours, suppression, and rollback before enabling.

Oppy describes automation rules as “When X happens, Oppy should do Y” and documents actions such as notifications, work items, workflow synchronization, and conversations. Source: Public Events & Automation Rules.

6. Make delivery reliable

Idempotency storage

CREATE TABLE webhook_receipts (
  id BIGSERIAL PRIMARY KEY,
  provider TEXT NOT NULL,
  idempotency_key TEXT NOT NULL,
  event_type TEXT NOT NULL,
  received_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  processed_at TIMESTAMPTZ,
  attempt_count INTEGER NOT NULL DEFAULT 0,
  last_error TEXT,
  UNIQUE (provider, idempotency_key)
);

-- Prefer provider event ID. Otherwise hash stable fields.
INSERT INTO webhook_receipts(provider,idempotency_key,event_type)
VALUES ('homeanalytics', :key, :type)
ON CONFLICT (provider,idempotency_key) DO NOTHING;

Retry and replay semantics

  • Assume at-least-once delivery only after the provider confirms it. Until then, make your own receiver replay-safe regardless of provider behavior.
  • Retry outbound Oppy ingestion on network errors, 408, 429, and 5xx with exponential backoff and jitter. Honor Retry-After when present.
  • Do not retry permanent 4xx responses except 408 or 429. Send exhausted jobs to a dead-letter queue with redacted context.
  • Use the same idempotency key for every retry. Provide a controlled replay tool that records actor, reason, timestamp, and original receipt.
  • Oppy's outbound Send Webhook action performs no retry, so a non-2xx receiver response is not automatically redelivered by that action. Source: Oppy Send Webhook guide.

Recommended status handling

  • 202: authenticated, durably stored, and queued.
  • 400: malformed JSON or body.
  • 401: missing or invalid authentication or signature.
  • 413: payload exceeds the configured limit.
  • 415: unsupported media type.
  • 422: syntactically valid but unsupported schema or event.
  • 429: local capacity protection, with Retry-After if retry is supported.
  • 500 or 503: transient receiver failure before durable acceptance.

Monitoring and alerts

  • Track accepted, rejected, duplicated, queued, processed, retried, dead-lettered, and replayed counts.
  • Measure ingress acknowledgment latency, queue age, normalization failures, Oppy ingestion latency, rule execution success, and end-to-end time.
  • Alert on signature failures above baseline, zero events during an expected activity window, growing queue age, repeated 401/429/5xx, dead-letter growth, and duplicate-rate spikes.
  • Correlate source_event_id, receipt ID, Oppy event ID, rule ID, and execution ID. Keep payload values redacted.

Capacity caveat: HomeAnalytics' public API page states 60 credits per call, but its public pages do not state request-rate limits or an SLA. The cited Oppy event and MCP pages also do not promise an event-ingestion SLA. Discover current limits during onboarding, cap concurrency, apply backpressure, and do not claim guaranteed throughput or uptime without a contract.

7. Production test matrix

Pass criteria: exactly one authenticated Oppy event and one intended action for the matching fixture; zero action for the near-miss, invalid signature, and duplicate replay.

  • Valid signed event returns 202 and enqueues once.
  • Invalid signature returns 401 before JSON parsing or work.
  • Malformed JSON, oversized body, and unsupported media type return the documented status.
  • Duplicate replay leaves one receipt, one Oppy event, and one action.
  • Threshold near-miss creates an event but no rule action.
  • Expired credential and downstream 429/5xx follow retry policy.
  • Dead-letter replay is authorized, audited, and idempotent.
  • Logs contain trace IDs and timings, not secrets, addresses, ownership, or mortgage details.
  • Rollback disables the rule and pauses ingestion without losing queued work.

Inspiration: useful rules and living analytics

Start with decision-ready signals, then pair each automation with a view that Oppy can refresh on a schedule. Connect through the public MCP endpoint at https://mcp.oppy.pro/mcp or use the current authenticated Oppy API documentation.

Valuation watch

Trigger: homeanalytics.avm.changed at 5% change and confidence at least 0.85. Action: create a human-review item before any consumer contact.

New comp review

Trigger: homeanalytics.comp.closed within the selected radius and similarity band. Action: alert the assigned agent and draft an update for approval.

Equity opportunity

Trigger: homeanalytics.mortgage.updated when modeled equity enters an approved band. Action: create a compliance-reviewed opportunity item.

Connector health

Trigger: homeanalytics.sync.failed after the connector's final retry. Action: notify operations and suppress duplicate incidents.