Partner integrations

Integration guide

Reference for vendors building against CondoHQ. Your property manager shares this guide with you along with a connection-specific signing secret (webhooks) and/or API key (read API). The legal relationship is governed by the property's Partner API Agreement.

Read API

Base URL
https://api.condohq.ai/api/partner/v1
Authentication
Authorization: Bearer chq_live_… or chq_test_…. Test keys return sandbox fixtures with livemode: false.
Scopes
Field visibility is exact-match on granted scopes; scopes never inherit. Omit unknown response fields; tolerate new enum values.

Outbound webhooks

Signature header
X-CondoHQ-Signature
Verification
HMAC-SHA256 over {timestamp}.{raw_body} using the signing secret from connection setup. Reject timestamps outside ±5 minutes.
Delivery semantics
At-least-once delivery; deduplicate on event_id. Return any 2xx to acknowledge.

Verify signatures

Always verify against the raw request body before parsing JSON, compare with a constant-time function, and reject timestamps older than 5 minutes. Respond 2xx quickly; do heavy work asynchronously.

const crypto = require("node:crypto");

const TOLERANCE_SECONDS = 5 * 60;

function verifyCondoHqSignature(secret, rawBody, header) {
  const parts = Object.fromEntries(
    header.split(",").map((p) => p.trim().split("=", 2)),
  );
  const timestamp = Number(parts.t);
  if (!Number.isInteger(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`, "utf8")
    .digest("hex");
  const given = Buffer.from(parts.v1 ?? "", "utf8");
  const want = Buffer.from(expected, "utf8");
  return given.length === want.length && crypto.timingSafeEqual(given, want);
}

// Express: use the RAW body, not the parsed JSON.
app.post("/webhooks/condohq", express.raw({ type: "*/*" }), (req, res) => {
  const ok = verifyCondoHqSignature(
    process.env.CONDOHQ_SIGNING_SECRET,
    req.body.toString("utf8"),
    req.header("X-CondoHQ-Signature") ?? "",
  );
  if (!ok) return res.status(401).end();
  const event = JSON.parse(req.body.toString("utf8"));
  // Deduplicate on event.event_id — delivery is at-least-once.
  res.status(200).end();
});

API reference

Generated from the authoritative OpenAPI contract. Webhook event payloads are documented under Webhooks in the sidebar.