Developer portal

Build on Blue Oceans Business OS

Tenant-scoped REST API · HMAC-signed webhooks · OpenAPI 3.1 · sandbox with synthetic data

Webhooks & signatures

Blue Oceans posts JSON events to your HTTPS endpoint. Each delivery is signed with your endpoint secret so you can prove it came from us and has not been altered or replayed.

Signature scheme

HeaderBO-Signature: t=<unix seconds>,v1=<hex>
AlgorithmHMAC-SHA256 over the string `${t}.${rawBody}` using your endpoint secret
Replay windowReject if the timestamp is more than 300 seconds from your clock
ComparisonConstant-time (timingSafeEqual / hmac.compare_digest)
De-duplicationBO-Event-Id header — store processed IDs; deliveries are at-least-once
RetriesNon-2xx or timeout (10 s) → exponential backoff (1 min, 5 min, 30 min, 2 h, 6 h …) for up to 24 hours, then the endpoint is flagged in your admin console
SecretsShown once on creation, rotatable with a 24-hour overlap; stored encrypted, only the last four characters are displayed

Verify in Node.js

import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyBlueOceans(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=")));
  const t = Number(parts.t);
  if (!t || !parts.v1) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; // replay window
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1, "hex");
  return given.length === expected.length && timingSafeEqual(given, expected);
}

// Express: use the RAW body, not re-serialised JSON
app.post("/hooks/blue-oceans", express.raw({ type: "application/json" }), (req, res) => {
  if (!verifyBlueOceans(req.body.toString("utf8"), req.get("BO-Signature") ?? "", process.env.BO_WEBHOOK_SECRET)) {
    return res.sendStatus(400);
  }
  const eventId = req.get("BO-Event-Id");      // de-duplicate on this
  queue.add(eventId, JSON.parse(req.body));    // acknowledge fast, process async
  res.sendStatus(204);
});

Verify in Python

import hmac, hashlib, time

def verify_blue_oceans(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
    try:
        t = int(parts["t"])
    except (KeyError, ValueError):
        return False
    if abs(time.time() - t) > tolerance:
        return False  # replay window
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

Try it

Try editing one character of the body after signing — verification fails with mismatch. Signatures older than five minutes fail with expired. Never paste production secrets here.

Event catalogue

EVENTdeliverable.accepted

A deliverable passed evidence-gated acceptance by a second person.

{
  "id": "evt_01",
  "type": "deliverable.accepted",
  "tenant": "kusasa",
  "occurredAt": "2026-10-20T07:58:00Z",
  "data": {
    "deliverableId": "DLV-1042",
    "acceptedBy": "w-02",
    "evidenceCount": 2
  }
}
EVENTtenant.activated

A new workspace finished the 24-hour activation and is workspace-ready.

{
  "id": "evt_02",
  "type": "tenant.activated",
  "tenant": "sample-courier",
  "occurredAt": "2026-10-20T07:31:00Z",
  "data": {
    "plan": "growth",
    "modules": [
      "serviceos",
      "peopleos"
    ],
    "hoursToActivate": 6.4
  }
}
EVENTinvoice.issued

A subscription or implementation invoice was issued (billing switches are OFF in the demo).

{
  "id": "evt_03",
  "type": "invoice.issued",
  "tenant": "kusasa",
  "occurredAt": "2026-10-01T00:05:00Z",
  "data": {
    "invoiceId": "INV-S-0042",
    "amountExclVat": 3999,
    "currency": "ZAR"
  }
}
EVENTobligation.due

A compliance obligation reached a 90/60/30-day alert threshold.

{
  "id": "evt_04",
  "type": "obligation.due",
  "tenant": "kusasa",
  "occurredAt": "2026-10-20T06:00:00Z",
  "data": {
    "obligationId": "OB-03",
    "daysRemaining": 8
  }
}

Machine-readable list: /api/v1/webhooks/events.