Webhooks

Archon can notify another system the moment something happens — a sample completes a stage, a notebook entry is finalized — instead of that system having to poll the API.

Setting one up

An owner or admin creates a webhook at Settings → Integrations → Webhooks: a destination URL, an optional signing secret, and which events it should fire on. A Send test button delivers a ping event immediately, so you can confirm the receiving end before anything real depends on it.

Events

EventFires when
sample.createdA new sample is logged.
sample.updatedA sample’s data or stage changes.
sample.completedA sample finishes its workflow.
notebook.createdA new notebook entry or folder is created.
notebook.finalizedAn entry is finalized and signed.
protocol.executedA protocol run against a sample completes.
sop.completedAn SOP acknowledgement or execution completes.
inventory.low_stockA stock item crosses its reorder threshold.

Payload

Every delivery is a JSON POST with the event name and timestamp folded into the body:

Headers
Content-Type: application/json
User-Agent: Archon-Webhook/1.0
X-Archon-Event: notebook.finalized
X-Archon-Signature: sha256=<hmac hex digest>
Body
{
  "event": "notebook.finalized",
  "timestamp": "2026-09-01T14:32:07.000Z",
  "notebookId": "b3d1...",
  "title": "Run 14 — extraction QC"
}

Verifying the signature

If you set a signing secret when creating the webhook, every delivery is signed with HMAC-SHA256 over the raw request body. Verify it before trusting the payload:

Node.js
import { createHmac, timingSafeEqual } from "crypto";

function isValidSignature(rawBody: string, header: string | null, secret: string): boolean {
  if (!header?.startsWith("sha256=")) return false;
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
  const given = header.slice(7);
  return (
    expected.length === given.length &&
    timingSafeEqual(Buffer.from(expected), Buffer.from(given))
  );
}

Compare against the raw request body, before any JSON parsing — re-serializing the parsed object is not guaranteed to produce byte-identical output, which would make a valid signature look wrong.

Delivery behaviour

  • 10-second timeout. A slow endpoint counts as a failed delivery rather than holding the request open.
  • No redirects followed. A 3xx response is treated as a failure — an attacker-controlled redirect could otherwise be used to reach an internal address.
  • Auto-pause after 5 consecutive failures. The webhook is marked error in the dashboard rather than retrying forever against a dead endpoint; re-enable it once the destination is fixed.
  • Delivery history is kept — status code and response body (capped at 2 KB) for each attempt, visible from the webhook’s row in Settings.
  • Deliveries for the same event fire in parallel to all active webhooks subscribed to it, not sequentially.

Outbound targets are re-validated on every single delivery, not just when the webhook is created — the destination’s hostname is resolved and checked against loopback, link-local, private and cloud-metadata address ranges each time, which defends against a hostname being re-pointed at an internal address after the webhook was approved (DNS rebinding). If your deployment restricts webhook destinations to an allowlist (WEBHOOK_ALLOWED_HOSTS), your endpoint’s host needs to be on it.