# HomeAnalytics + Oppy Event Automation: Developer Integration Guide

Published: 2026-10-06
Updated: 2026-10-06
Source: https://docs.oppy.pro/n/homeanalytics-oppy-event-automation-developer-integration-guide
Account: Oppy Inc

---

[Skip to implementation guide](#content)

HomeAnalytics+ ![Oppy wordmark](https://res.cloudinary.com/kw/image/upload/oppy/Brand%20Assets/oppy_yellow.png)
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](#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](https://homeanalytics.ai/api) and [documentation](https://homeanalytics.ai/docs).

## 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&#39; 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&#39;s outbound header `X-Webhook-Signature` is official only when receiving a Send Webhook action from Oppy. Source: [Oppy Send Webhook guide](https://docs.oppy.pro/n/automation-rules-send-webhook-developer-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&#39;s documented header before deployment.

### Node.js + Express

    npm i express
    
    import express from &quot;express&quot;;
    import crypto from &quot;node:crypto&quot;;
    
    const app = express();
    const secret = process.env.WEBHOOK_SECRET;
    const header = (process.env.SIGNATURE_HEADER ||
      &quot;x-webhook-signature&quot;).toLowerCase();
    if (!secret) throw new Error(&quot;WEBHOOK_SECRET required&quot;);
    
    app.post(&quot;/webhooks/homeanalytics/v1&quot;,
      express.raw({ type: &quot;application/json&quot;, limit: &quot;256kb&quot; }),
      (req, res) =&gt; {
        const received = String(req.headers[header] || &quot;&quot;);
        const expected = &quot;sha256=&quot; + crypto
          .createHmac(&quot;sha256&quot;, secret).update(req.body).digest(&quot;hex&quot;);
        const a = Buffer.from(received);
        const b = Buffer.from(expected);
        const valid = a.length === b.length &amp;&amp;
          crypto.timingSafeEqual(a, b);
        if (!valid) return res.status(401).json({ ok: false });
    
        let event;
        try { event = JSON.parse(req.body.toString(&quot;utf8&quot;)); }
        catch { return res.status(400).json({ ok: false }); }
    
        // Persist idempotency key, enqueue event, then acknowledge.
        console.log(&quot;accepted&quot;, event.id || &quot;no-source-id&quot;);
        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[&quot;WEBHOOK_SECRET&quot;].encode()
    header = os.getenv(&quot;SIGNATURE_HEADER&quot;,
                       &quot;X-Webhook-Signature&quot;)
    
    @app.post(&quot;/webhooks/homeanalytics/v1&quot;)
    def receive():
        raw = request.get_data(cache=False, as_text=False)
        received = request.headers.get(header, &quot;&quot;)
        expected = &quot;sha256=&quot; + 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(&quot;accepted&quot;, event.get(&quot;id&quot;, &quot;no-source-id&quot;))
        return jsonify(ok=True), 202
    
    if __name__ == &quot; __main__&quot;:
        app.run(host=&quot;0.0.0.0&quot;, port=int(os.getenv(&quot;PORT&quot;, 3000)))

Oppy&#39;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=&lt;hex&gt;`. Acknowledge quickly and queue slow work. Source: [Send Webhook Action Developer Integration Guide](https://docs.oppy.pro/n/automation-rules-send-webhook-developer-guide).

## 3. Normalize the source event

### Illustrative HomeAnalytics fixture, not an official webhook schema

    {
      &quot;id&quot;: &quot;fixture_20261006_001&quot;,
      &quot;type&quot;: &quot;property.avm.updated&quot;,
      &quot;occurred_at&quot;: &quot;2026-10-06T15:00:00Z&quot;,
      &quot;data&quot;: {
        &quot;property_id&quot;: &quot;ha_example_123&quot;,
        &quot;previous_value&quot;: 742000,
        &quot;value&quot;: 779000,
        &quot;confidence&quot;: 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 -&gt; payload.source_event_id
    source.occurred_at -&gt; payload.as_of
    source.data.property_id -&gt; payload.property_id
    source.data.value -&gt; payload.current_value
    calculated percent -&gt; payload.change_pct
    connector constant -&gt; payload.schema_version

### Normalized Oppy event

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

Use dotted event names. Oppy&#39;s public reference documents the `domain.action` convention and custom event creation through API or MCP: [Oppy System Events Reference](https://docs.oppy.pro/n/oppy-system-events-reference).

## 4. Create the event in Oppy

### Concrete MCP `manage_events` call

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

Connect an MCP-compatible client to [https://mcp.oppy.pro/mcp](https://mcp.oppy.pro/mcp) using OAuth 2.1 or `Authorization: Bearer &lt;api-key&gt;`, then call `manage_events` with the arguments above. The MCP transport is Streamable HTTP over JSON-RPC 2.0. Source: [Oppy MCP Connect Guide](https://docs.oppy.pro/n/oppy-mcp-oauth-setup).

**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 &gt;= 5` and `confidence &gt;= 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 &amp; Automation Rules](https://docs.oppy.pro/n/public-events-automation-rules-customer-guide).

## 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 (&#39;homeanalytics&#39;, :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&#39;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](https://docs.oppy.pro/n/automation-rules-send-webhook-developer-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&#39; 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](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&#39;s final retry. **Action:** notify operations and suppress duplicate incidents.

## Sources

- [HomeAnalytics API Reference](https://homeanalytics.ai/api)
- [HomeAnalytics Documentation](https://homeanalytics.ai/docs)
- [Oppy Send Webhook Action Developer Integration Guide](https://docs.oppy.pro/n/automation-rules-send-webhook-developer-guide)
- [Oppy System Events Reference](https://docs.oppy.pro/n/oppy-system-events-reference)
- [Public Events &amp; Automation Rules](https://docs.oppy.pro/n/public-events-automation-rules-customer-guide)
- [Oppy MCP Connect Guide](https://docs.oppy.pro/n/oppy-mcp-oauth-setup)

Security and compliance requirements vary by product, jurisdiction, data source, and communication channel. Treat automated valuations and generated recommendations as decision support, not a substitute for required professional, legal, compliance, or appraisal review.


