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
Refusedsync arrives, or moves to archive ifCompleted. - 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
newStatus | Transition | When to use |
|---|---|---|
Completed | Active → Completed | Issue resolved, ticket closed, work done |
Refused | Active → Refused | Issue 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_syncedevent withactor_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_atis only set on the firstCompletedtransition; replays do not refresh it. - Row not found — if
(externalSystem, externalId, assigner)does not match any row, the endpoint returns404 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_Handshakerows. 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_atlands after the row'sdeadline, 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: Refusedon anActiverow 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.
Related concepts
Last updated: 22 June 2026