ShrubberyDocs
Sign in

Webhooks Overview

Two perspectives on webhooks live in Shrubbery:

  • Receiver webhooks — external systems POST to Shrubbery to mint commitments and drive state changes. Documented under this section.
  • Sender webhooks (outbound) — Shrubbery POSTs to external systems on Shrubbery-side state changes. Planned but not yet implemented; see Outbound Webhooks (planned).

This page covers the cross-cutting contract for both sides: authentication, retry semantics, signature posture, and idempotency.

Receiver-side contract

When you POST to /api/v1/inbox or /api/v1/sync:

  • Authentication. Authorization: Bearer <token> where the token was minted in Settings → API Tokens. The token owner becomes the implicit assigner / row owner; cross-user mutation is not possible from a single token. See API Getting Started for the mint flow.
  • Idempotency. /api/v1/inbox enforces uniqueness on (externalSystem, externalId, assigner). Replaying the same payload returns 409 duplicate_external_ref; the original Shrubbery is untouched. /api/v1/sync is idempotent by design — replaying a transition into the current status is a no-op 200 OK.
  • Retry guidance. Retry only on 5xx. 4xx failures are deterministic for the same payload; retrying without changes will fail identically. On 429 from /api/gather (not in scope here, but mentioned for completeness), honour the Retry-After header.
  • Signature verification. Not used. Shrubbery authenticates the request with the Bearer token, not a body signature. The token is the trust root; protect it accordingly. If you need request-body integrity over the wire, ensure TLS is enforced end-to-end (no proxy that re-signs or rewrites). Future per-token HMAC signing is tracked in the backlog under "Per-token IP allowlists" and "Per-token scopes" entries.
  • Transport. HTTPS in production. HTTP is accepted only on localhost during development.
  • Content type. application/json for both request and response. Any other type returns 400.

Sender-side contract (planned)

When Shrubbery POSTs to your endpoint (not implemented today):

  • Authentication. The plan is HMAC-signed payloads using a shared secret you register per endpoint, similar to GitHub webhooks. Header: X-Shrubbery-Signature: sha256=<hex-hmac>. Your endpoint computes the HMAC over the raw request body using the shared secret and constant-time-compares against the header.
  • Retry semantics. Backed by Inngest. On non-2xx response, exponential backoff up to 24 hours. Permanent failure after the back-off envelope expires.
  • Event types. First planned event is shrubbery.completed (mirror of inbound sync). Future events: shrubbery.refused, shrubbery.renegotiated. Each event carries the Shrubbery id, the new status, the (externalSystem, externalId) mapping, and an event timestamp.

See Outbound Webhooks (planned) for the proposed payload shapes and the backlog reference.

Audit attribution

Every state-changing webhook call (inbound) appends a row to public.shrubbery_events with:

  • actor_kind = 'api_token'
  • token_id = <the calling token's UUID>
  • user_id = <the token owner>
  • payload = <the relevant subset of the request body>

The Detail Sheet's Timeline surfaces this attribution; users see "via API token (Jira integration)" rather than "you" on the audit row. The distinction matters for the Dossier — a Knight whose work was auto-completed by an external system reads differently from a Knight who self-marked complete in the Accord.

CFS arithmetic does not distinguish actor kinds; whether you complete a Shrubbery via the UI or via /api/v1/sync, it counts the same way against Flow Score. See CFS Rules for the full inclusion logic.

Endpoint reference

For the full request/response surface with embedded Scalar, see API Reference Overview.

Last updated: 17 May 2026