# docs\(connect\): held webhooks are kept per managed link · PR \#23612 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/23612)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: open

Source repository: pingdotgg/t3code

Source branch: fix/issue-16995

Target branch: main

Head commit: 25691d06ee693ee00459f1be834dc9ad6439165b

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/23612)

[Commits](https://git.cafe/pingdotgg/t3code/pull/23612/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/23612/diffs?format=markdown)

## Description

---

## Problem

The held-webhooks paragraphs of `docs/internals/t3-connect.md` say the relay stores offline webhook requests in one Durable Object per environment, deletes them "when no user has the environment linked", and orders and caps them per environment. The relay actually keeps one `HookInboxObject` per managed link, named by that link's endpoint key (`infra/relay/src/worker.ts` `getByName(endpointKey)`). Unlinking clears only that link's inbox, and turning `hold_webhooks_while_offline` off clears the inboxes of every link the environment's key proves. The doc never mentioned that second case.

## Change

Rewrite those paragraphs to match the relay. Held requests are stored, ordered and capped per managed link (endpoint key), so each account that links an environment gets its own inbox. Wake delivers to each of the environment's links. Requests are deleted once answered, after 24 hours, when their link is unlinked, or when the environment turns holding off. The Durable Object rationale now says per-link. Only docs change.

## Scope and approval

Fixes #16995
GitCafe: https://git.cafe/pingdotgg/t3code/issues/16995
Triaged bug via the automated issue flow. A maintainer confirmed the mismatch on the issue. The separate path-segment error on lines 7-8 of the same doc is https://git.cafe/pingdotgg/t3code/issues/16988 and is not changed here.

## Verification

- Checked every new sentence against `infra/relay/src/worker.ts`, `hooks/HeldHooks.ts` (`ownEndpointKeys`, `setHoldWhileOffline`, `wake`), `hooks/HookInboxStore.ts` (caps and TTL), `http/Api.ts` (unlink clear) and `environments/EnvironmentLinks.ts` (`setHoldWebhooksWhileOffline`).
- `vp test run src/hooks/HeldHooks.test.ts src/http/Api.test.ts` in `infra/relay`: 2 files, 28 tests passed, including `HeldHooks > opting out clears only the caller's own endpoints` and `relay environment unlink > drops the unlinked endpoint's held webhooks, even if clearing fails`.
- Nothing visible changes, so there are no screenshots. A deployed relay with two accounts linking one environment was not exercised.

🤖 Generated with [Claude Code](https://claude.com/claude-code) (Claude Opus 5.5, T3 Code)

<details><summary>Verification log</summary>

- 2026-10-22, docs fact-check at `25691d06ee`, read-only (T3 Code, Claude Code):
  - A, claude-sonnet-5-5: **VERDICT: MERGE**. Every changed sentence is true against `infra/relay/src` (`worker.ts` `getByName(endpointKey)`; `HookInboxStore.ts` caps of 1,000 requests, 100 per hook and 50 MiB per object, 24 h TTL, `ORDER BY seq`; `HeldHooks.ts` `wake` and `setHoldWhileOffline`; `Api.ts` unlink clear). Nothing true was dropped: "when no user has the environment linked" was stale and "when their link is unlinked" replaces it. Low: "which clears every link it proved" uses internal jargon. Nit: "bodies of up to 1 MiB" is a per-request limit, separate from the 50 MiB per-object total.
  - B, claude-opus-5-5: **VERDICT: MERGE**. No false claims. One object per link is confirmed, and so are per-object caps, oldest-first delivery with backoff, wake to each of the environment's own endpoint keys, 1 MiB `RELAY_HOOK_MAX_BODY_BYTES`, and all four deletion triggers. Low: "proved" is unexplained. Informational: the unlink clear is best effort (logged and swallowed, with the 24 h TTL as backstop), and the TTL only takes effect on the next alarm run. The fit with the docs/internals rules is fine. The Queues 128 KB claim is external and was not checked by either reviewer.

</details>

