Octopus
Octopus Developersgateway.octopusoperations.co.za/v1

Guides

Webhooks

If you would rather Octopus called you than hold a socket open, register an endpoint. Same changes, delivered as a signed HTTPS POST.

Delivery and verification

What Octopus sends, and what you must check
Drawing…

Steps 2 and 3 explain the header's shape. The timestamp is signed together with the body, not alongside it. A signature over the body alone would let anyone who captured one real POST replay it forever; because the time is inside the signed string, a replay carries an old t that you reject at step 5.

Step 7 must be a constant-time comparison. A plain === on hex strings leaks, through timing, how many leading characters were right, which is enough to forge a signature given patience.

Headers on every delivery

http
x-octopus-signature: t=1789125548,v1=9f2c8ab1...
x-octopus-delivery:  b754d5dc-f1a5-45cc-9220-7571e992dd1f
x-octopus-event:     TASK_UPDATED

The scheme is versioned, v1 sits in the header so a v2 can be added later without breaking every receiver at once.

Verifying

Node.js
import crypto from "node:crypto";

export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(
    header.split(",").map((p) => p.split("="))
  );
  const t = Number(parts.t);

  // Two-sided window: rejects replays AND clocks in the future.
  if (Math.abs(Date.now() / 1000 - t) > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)   // the RAW body, byte for byte
    .digest("hex");

  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(parts.v1, "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Sign the raw body, not the parsed one

Verify before your JSON middleware touches the request. Parsing and re-serialising changes key order and whitespace, and the signature will never match. This is the single most common reason a correct-looking implementation still fails.

Answer fast, work later

Return 2xx as soon as you have verified and queued the work, then process out of the request. A slow endpoint looks like a failing one and will be retried, deduplicate on x-octopus-delivery, which is stable across retries.

Proving an endpoint before you rely on it

Settings → Integrations has a ping that sends a synthetic event, and a delivery log showing recent attempts with the response each one got. That log is the only way to debug a receiver that is silently refusing.

HTTPS only, and no internal addresses

Octopus resolves your hostname before every delivery and refuses private and loopback addresses. A webhook pointed at an internal service would otherwise turn the dispatcher into a way to reach inside our network.