Partner integrations
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.
Authorization: Bearer chq_live_… or chq_test_…. Test keys return sandbox fixtures with livemode: false.{timestamp}.{raw_body} using the signing secret from connection setup. Reject timestamps outside ±5 minutes.event_id. Return any 2xx to acknowledge.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();
});Generated from the authoritative OpenAPI contract. Webhook event payloads are documented under Webhooks in the sidebar.