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

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.
Receive, authenticate, deduplicate, normalize, and create one sandbox Oppy event.
Keep the HomeAnalytics key and Oppy credentials on your backend. Expose one versioned HTTPS receiver.
Normalize real property signals into stable, namespaced events in the user’s Oppy account.
Recommend rules from observed events, then test every selected trigger and outcome end to end.
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.
/webhooks/homeanalytics/v1.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.
WEBHOOK_SECRET and install one receiver below.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.
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.
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);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.
{
"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{
"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.
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.
401.400 and unsupported event types with 422.202 after durable enqueue, not after downstream completion.homeanalytics.avm.changed.manage_events with action: create.homeanalytics.avm.changed with conditions change_pct >= 5 and confidence >= 0.85. Start with a review item or notification, not automatic consumer outreach.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.
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;408, 429, and 5xx with exponential backoff and jitter. Honor Retry-After when present.4xx responses except 408 or 429. Send exhausted jobs to a dead-letter queue with redacted context.2xx receiver response is not automatically redelivered by that action. Source: Oppy Send Webhook guide.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.401/429/5xx, dead-letter growth, and duplicate-rate spikes.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.
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.
202 and enqueues once.401 before JSON parsing or work.429/5xx follow retry policy.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.
Trigger: homeanalytics.avm.changed at 5% change and confidence at least 0.85. Action: create a human-review item before any consumer contact.
Trigger: homeanalytics.comp.closed within the selected radius and similarity band. Action: alert the assigned agent and draft an update for approval.
Trigger: homeanalytics.mortgage.updated when modeled equity enters an approved band. Action: create a compliance-reviewed opportunity item.
Trigger: homeanalytics.sync.failed after the connector's final retry. Action: notify operations and suppress duplicate incidents.