ShrubberyDocs
Sign in

Sync Webhook Integration

Overview

The Sync webhook lets an external system push a status update — Completed or Refused — back to a Shrubbery row it previously created via the Inbox. This closes the loop without any human action in-app: when a Jira ticket closes, the linked Shrubbery closes too. This guide covers setting up the Sync integration.

For the raw API reference, see POST /api/v1/sync.

Actors

  • Lead — mints the API Token and optionally monitors the Garden for synced state changes.
  • External System — sends the HTTP call when a tracked issue reaches a terminal state.
  • Knight — not directly involved; their Accord row disappears if a Refused sync arrives, or moves to archive if Completed.
  • Squire — not involved.

Flow

1. Reuse the existing API Token

Use the same token minted for the Inbox integration. One token covers both /api/v1/inbox and /api/v1/sync. See Managing API Tokens if you need to mint a new one.

2. Configure the close-event webhook

Point the external system's close/resolve event at POST https://shrubbery.eu/api/v1/sync:

POST /api/v1/sync HTTP/1.1
Authorization: Bearer <your-token>
Content-Type: application/json
 
{
  "externalSystem": "jira",
  "externalId": "PROJ-123",
  "newStatus": "Completed"
}

externalSystem and externalId must match the values sent in the original Inbox call. The endpoint resolves the row by (externalSystem, externalId, assigner) where assigner = token owner.

3. Accepted newStatus values

newStatusTransitionWhen to use
CompletedActiveCompletedIssue resolved, ticket closed, work done
RefusedActiveRefusedIssue cancelled, out-of-scope, rejected by product

Pending_Handshake and Draft are not valid targets for Sync — Sync only transitions rows that are already Active.

4. Verify the integration

A successful call returns 200 OK with the Shrubbery's id and the applied newStatus. Confirm by checking:

  • The Garden card moved to the archive (Completed or Refused).
  • The Audit Trail shows a status_synced event with actor_kind = api_token.
  • The Commitment Flow Score updated (on-time Completed rows enter the Flow Score numerator).

Edge cases

  • Idempotency — replaying the same Sync call is safe. The second call applies the already-current status and returns 200. completed_at is only set on the first Completed transition; replays do not refresh it.
  • Row not found — if (externalSystem, externalId, assigner) does not match any row, the endpoint returns 404 not_found. This is the same response for "no such row" and "row belongs to a different assigner" — the ambiguity is intentional to avoid leaking existence.
  • Row not yet Active — Sync does not accept Pending_Handshake rows. If the Knight has not yet Handshaked when a Sync call arrives, the call returns an error. The external system should handle this by retrying after a delay or alerting the Lead.
  • Late completions — if completed_at lands after the row's deadline, the row still completes but the Flow Score numerator does not increment (late delivery). The CFS reflects the miss accurately.
  • Refused via Sync — using newStatus: Refused on an Active row is valid but unusual. It represents an external system cancelling a previously-accepted commitment (e.g. a ticket marked "won't fix"). The row enters the archive and the event is attributed to the API token.

Last updated: 22 June 2026