Fibric. Docs fibric.io →
v1.0.0 ยท stable
Reference

Webhooks API

Webhooks push the loop to you as it happens: plan proposals, action dispositions, and connector status changes arrive at your HTTPS endpoint within seconds, each delivery signed so you can prove it came from Fibric. This page documents endpoint CRUD, subscription types, the delivery object, the retry schedule, and signature verification. Webhook management is part of the v1.0 surface; the delivery payloads are the same objects defined on the other reference pages.

Shared conventions, including authentication, pagination, the Idempotency-Key header, and the error envelope, are defined in the API overview. Error codes are catalogued in Errors.

Subscription event types

An endpoint subscribes to one or more delivery types. Each delivery wraps the full current API object named in the table, so a consumer needs no follow-up fetch for the common cases.

TypeFires whendata contains
plan.proposedAn operator proposes an execution plan.The plan object, verdicts included.
plan.disposedA plan finishes executing, is vetoed, or expires.The plan object in its final state.
action.disposedThe executor disposes one action: allowed, alerted, blocked, or deduplicated.The action object.
action.needs_approvalAn action's verdict is ALERT and a human must approve.The action object, plus its plan_id for the approval call.
connector.status_changedA connector transitions between connected, degraded, error, and pending_auth.The connector object.
receipt.writtenAny receipt is appended, refusals included.The receipt object. High volume; subscribe deliberately.

Types follow the API's noun.verb convention, and new types are additive within the major version; ignore types you do not recognize.

The webhook endpoint object

idstring

Unique identifier, prefixed whk_.

objectstring

Always webhook_endpoint.

urlstring

The HTTPS destination. Plain HTTP is rejected at creation.

typesstring[]

Subscribed delivery types from the table above.

statusstring

active, disabled (by you), or suspended (by Fibric, after sustained delivery failure; see retries).

secretstring

Signing secret, prefixed whsec_. Returned in full at creation and on rotation only; reads return a hint. Used to verify Fibric-Signature.

created_atstring

RFC 3339 timestamp of creation.

last_delivery_atstring or null

Timestamp of the most recent delivery attempt, successful or not.

POST

Create an endpoint

POST/v1/webhook_endpointsscope webhooks:write
urlrequiredstring · body

The HTTPS destination.

typesrequiredstring[] · body

One or more delivery types.

curl
curl -X POST https://api.fibric.io/v1/webhook_endpoints \
  -H "Authorization: Bearer $FIBRIC_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: whk-create-ops-consumer" \
  -d '{
    "url": "https://ops.example.com/fibric/webhook",
    "types": ["plan.proposed", "action.needs_approval", "connector.status_changed"]
  }'
201 Created Response
json
{
  "id": "whk_6b3e0d",
  "object": "webhook_endpoint",
  "url": "https://ops.example.com/fibric/webhook",
  "types": ["plan.proposed", "action.needs_approval", "connector.status_changed"],
  "status": "active",
  "secret": "whsec_5f2e8a1c9b0d7e4f6a3b",
  "created_at": "2026-07-02T15:30:00Z",
  "last_delivery_at": null
}

Error cases:

StatusCodeWhen
400invalid_parameterurl is not HTTPS, or a type is unknown.
409state_conflictAn active endpoint with this url already exists.
GET

List endpoints

GET/v1/webhook_endpointsscope webhooks:read

Returns the tenant's endpoints, newest first, cursor-paginated with the standard limit and cursor parameters. The secret reads as a hint.

PATCH

Update an endpoint

PATCH/v1/webhook_endpoints/{endpoint_id}scope webhooks:write

Updates url, types, or status (active / disabled). Setting status: "active" on a suspended endpoint re-enables it and resets the failure counter. Pass "rotate_secret": true to mint a new signing secret; the response carries the new secret in full, and the old secret remains valid for 24 hours so consumers can roll over without dropped verifications.

curl · rotate the signing secret
curl -X PATCH https://api.fibric.io/v1/webhook_endpoints/whk_6b3e0d \
  -H "Authorization: Bearer $FIBRIC_KEY" \
  -H "Content-Type: application/json" \
  -d '{"rotate_secret": true}'
DELETE

Delete an endpoint

DELETE/v1/webhook_endpoints/{endpoint_id}scope webhooks:write

Deletes the endpoint. Pending retries for it are dropped; delivery records remain readable for their retention window.

The delivery object

Every attempt to reach your endpoint is recorded as a delivery. The same shape is what arrives in the HTTP POST body.

idstring

Unique identifier, prefixed dlv_. Stable across retries of the same delivery: use it as your deduplication key.

objectstring

Always webhook_delivery.

endpoint_idstring

The endpoint this delivery targets.

typestring

The delivery type, for example plan.proposed.

dataobject

The full API object the type names, at the moment the delivery was created.

correlation_idstring

The envelope correlation id threading this delivery to its event, plan, and receipts.

attemptinteger

1-based attempt counter for this delivery.

statusstring

pending, succeeded, or failed (retries exhausted). Read over the API only; the POST body carries the delivery mid-flight.

created_atstring

RFC 3339 timestamp the delivery was created.

json · what arrives at your endpoint
POST /fibric/webhook HTTP/1.1
Content-Type: application/json
Fibric-Signature: t=1782064081,v1=6e8a2c91f0b34d7e5a1c9f82d4b60e3a7c5f9b1d8e2a4c6f0b3d5e7a9c1f4b68

{
  "id": "dlv_9c4f2e",
  "object": "webhook_delivery",
  "endpoint_id": "whk_6b3e0d",
  "type": "action.needs_approval",
  "correlation_id": "co_51d2e8",
  "attempt": 1,
  "created_at": "2026-07-02T15:02:10Z",
  "data": {
    "id": "act_91d1",
    "object": "action",
    "plan_id": "pl_7c1a",
    "tool": "order.notify",
    "entity_key": "order:SO-10884",
    "disposition": "ALERT"
  }
}
GET

List deliveries

GET/v1/webhook_endpoints/{endpoint_id}/deliveriesscope webhooks:read

Returns the endpoint's deliveries, newest first, cursor-paginated. Filter with status and type query parameters. Deliveries are retained for 30 days. To replay one, POST to /v1/webhook_endpoints/{endpoint_id}/deliveries/{delivery_id}/redeliver, which re-sends the original payload with a fresh signature.

Retries and backoff

A delivery succeeds when your endpoint returns any 2xx within 10 seconds. Anything else, a non-2xx, a timeout, a connection failure, schedules a retry with exponential backoff:

AttemptDelay after previousElapsed (approx.)
1immediate0
230 seconds30 s
32 minutes2.5 m
410 minutes13 m
51 hour1.2 h
66 hours7 h
7 (final)24 hours31 h

Delays carry jitter to avoid thundering herds. After the final attempt the delivery is marked failed and remains listable for the retention window. If every delivery to an endpoint has failed for 3 consecutive days, the endpoint is suspended and a notice goes to the tenant's owners; re-enable it with a PATCH once the consumer is healthy, then redeliver what you missed.

i
Deliveries are at-least-once and unordered

Retries and redelivery mean your consumer can see the same dlv_ id twice; deduplicate on it. Ordering across deliveries is not guaranteed, especially under retry. Treat each delivery as a pointer to current state: the data snapshot is convenient, but for decisions, re-read the object by id.

Verifying Fibric-Signature

Every delivery carries a Fibric-Signature header: a Unix timestamp and one or more HMAC-SHA256 signatures, hex-encoded.

the header format
Fibric-Signature: t=1782064081,v1=6e8a2c91f0b34d7e5a1c9f82d4b60e3a7c5f9b1d8e2a4c6f0b3d5e7a9c1f4b68

The signed message is the timestamp, a period, and the raw request body: {t}.{body}. To verify: compute HMAC-SHA256 over that message with your whsec_ secret, compare in constant time against each v1 value (two are present during a secret rotation), and reject if the timestamp is older than your tolerance, five minutes is conventional, to defeat replay.

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

function verifyFibricSignature(header: string, rawBody: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // 5-minute tolerance

  const expected = createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");

  const given = Buffer.from(String(parts.v1 ?? ""), "hex");
  const want = Buffer.from(expected, "hex");
  return given.length === want.length && timingSafeEqual(given, want);
}

Verify against the raw body bytes, before any JSON parsing or re-serialization; a re-stringified body will not match. Respond 2xx quickly and do the work asynchronously: the 10-second window includes your processing time.

Errors

StatusCodeWhen
400invalid_parameterA non-HTTPS url, an unknown delivery type, or an invalid status transition.
403insufficient_scopeThe key lacks the webhooks:read or webhooks:write scope the route requires.
404not_foundNo endpoint or delivery with this id exists for the authenticated tenant.
409state_conflictCreating a duplicate url, or redelivering a delivery that is still pending.