Delivery through Ting#
Hook receives, verifies and keeps provider webhooks. Ting carries each verified event to the Silicon (and to Carbons who subscribed). The app that hosts the Silicon handles receiving; nobody configures Hook or Ting by hand.
Delivery is optional. Without it Hook still receives, verifies and stores every
event for 14 days, and Silicons read them with hook events or the history API.
Turning delivery on or off#
| Variable | Meaning |
|---|---|
HOOK_TING_URL |
Ting's origin. Unset: delivery is off. Plain http only for loopback. |
HOOK_TING_APP_ID |
Ting's app id at Silicon Accounts: every proof Hook presents to Ting is issued for this app (default ting; never Hook's own id). |
HOOK_TING_TIMEOUT_SECONDS |
Deadline for one call to Ting, 1 to 15 seconds (default 10). |
HOOK_TING_POLL_MILLISECONDS |
How often the publisher looks for queued sends, 100 to 30000 ms (default 1000). |
With HOOK_TING_URL unset, Hook queues nothing (so turning delivery on later
does not flood Silicons with old events), logs that delivery is off at startup,
and says so everywhere a client can look: /readyz (delivery.ting: disabled),
GET /api/v3/delivery, 409 delivery_disabled from enrolment and
subscriptions, and state: delivery_disabled in publication status.
Before setting HOOK_TING_URL, Ting must accept Silicon Accounts proofs from
Hook (Ting's own migration is outside Hook), and Ting's operator must register
the hook.webhook.received type. Until then keep it unset.
How Hook talks to Ting#
Hook signs every call to Ting with a Silicon Accounts proof made with Hook's own app credentials:
| Call | Proof | Scope |
|---|---|---|
| Send a notification, read its receipt | App verification (no user) | tings.send, sent.query |
| Enrol a recipient | User verification, from the recipient's own Hook access token | tings.subscribe |
App verification proofs are kept in memory and renewed one at a time; a User
verification proof is made for each enrolment while the recipient's request is
live. Hook stores no delegated tokens. Recipients are addressed by their Silicon
Accounts uuid and current id, {"uuid", "id"}.
When Silicon Accounts refuses or cannot answer a proof request (for example
because HOOK_TING_APP_ID names no app yet), Hook logs why once and waits before
asking again: 30 seconds, doubling up to 5 minutes while it keeps failing. Queued
sends stay queued meanwhile, with publication state pending and
last_error_code: proof_unavailable, so a long queue never becomes one proof
request per send.
Event and acknowledgment flow#
- Hook verifies the provider's signature, then stores the original request and
the exact outgoing Ting request in one transaction. The provider hears
webhook.okonly after the commit. - The publisher sends each queued notification with a fresh proof, retrying with
the same bytes and the same producer key
(
hook:{event id}:{sha256(recipient uuid)}) until Ting confirms it stored the send. An uncertain result never creates a second notification. - Ting receives the compact
new_eventenvelope: the provider name and a reference (event id,silicon: {uuid, id}, hook id, original sequence and time, and a summary with the provider and receipt time in the hook's IANA time zone). Provider bodies and headers stay in Hook. - The receiving app fetches
GET /api/v3/silicons/{silicon uuid}/events/{event id}with the recipient's own Hook token. Failed access checks and expired events must not become application work. - The app durably accepts and deduplicates the event before acknowledging Ting's local batch. Ting may replay or reorder; deduplicate on the event id.
Hook keeps events 14 days, even if Ting keeps the reference longer.
Recipients#
- The Silicon receives its own events. Its sends use Ting's required
(automation) mode, which the Silicon opts into through its own app; until then
they stay pending with
required_delivery_not_enabled. Hook never downgrades the mode. - Carbons with
view(or more) on a Silicon, and its custodian, can subscribe to copies of its future events:POST /api/v3/silicons/{silicon}/delivery/subscription(hook receiving subscribe). Copies use ordinary delivery and follow the Carbon's notification preferences. Hook checks the Carbon's access again before every send, and drops the subscription when access ends (grant revoked, custodian changed, Hook's access removed, account deleted). At most 100 observers per Silicon.
Enrolment (POST /api/v3/delivery/recipient, hook receiving register) grants
Hook's notifications to the caller in Ting. Attaching a destination is done in
Ting by the receiving app; Hook never stores the receiving URL.
Publication status#
GET /api/v3/silicons/{silicon}/events/{event}/publication reports
delivery_disabled, not_queued, pending (with last_error_code),
accepted_by_ting, accepted_silently (ordinary send while muted) or
not_delivered_legacy, plus delivery, silent, attempts and, when available,
Ting's receipt with per-destination delivery and read acknowledgments. A receipt
means a destination accepted the event, never that the Silicon finished its
work.
From Hook before 1.0#
Sends queued before 1.0 that Ting never accepted cannot be signed with Silicon
Accounts proofs; migration 0019 marks them legacy_identity and publication
shows not_delivered_legacy until the event expires. Earlier Carbon
subscriptions are not carried over: Carbons subscribe again. The publisher setup
and the separate Ting approval of earlier versions are gone.