2026-09-27·by Sijie Wang#standmeet#architecture#design#events

embed-update-hook

Embed update hook: the corpus tells its embeds

Parent: events

Status: released in v0.1.76 (2026-09-27) — design and as-built record in docs/design/event-bus-outbox-webhooks.md in the StandMeet repo. The standmeet.com side is built in its own repo and not deployed yet.

An embed's update hook is a webhook endpoint attached to the embed. Its scope is the embed's access code. When a note inside that scope changes, the consuming site learns it and re-reads only what moved.

Where the idea came from

standmeet.com shows the owner's corpus as a blog through an embed. It had no way to learn that the corpus changed: the inventory listed "embed consumers (standmeet.com)" with no mechanism at all. The update hook is Phase 3 of the event-bus plan, and standmeet.com is its first consumer.

One field, one attached endpoint

embeds.create and embeds.update take update_hook_url; the embed form has an Update hook URL field. Filling it creates or updates the endpoint attached to that embed (the upsert has its own UT):

  • event_types = ["corpus.note.changed"].
  • embed_id is set, so the scope is the embed's code scope: role globs minus the code's denials. The access domain answers it (EmbedAdmits) with the same entity.AllowsCorpusEntry predicate every read uses. A revoked or expired code, or a deleted embed, admits nothing. raw:// never leaves.
  • The response carries update_hook {endpoint_id, url}, and the signing secret only when the endpoint was just created.
  • The payload is thin. The receiver reads bodies through the same scoped API the embed already uses. For how an embed authenticates without carrying the code, see embed-credential-never-carries-the-code.
  • /api/v1/corpus-cards carries updated_at (second precision), so the receiver can tell which cards moved.

What standmeet.com does with it

  • Blog pages, RSS, the blog sitemap and "latest notes" render per request from Workers KV (Astro Cloudflare adapter). prerender = false is set only on the blog routes; "latest notes" on the landing page is a server island.
  • POST /api/corpus-hook verifies the Standard Webhooks signature with a ±5 min timestamp window, dedupes on webhook-id (kept 24 h in KV), re-lists the cards and fetches only entries whose updated_at moved, in both languages.
  • Links are resolved at request time against the current index, so a rename or delete does not leave stale links.
  • The first request fills an empty KV from the instance (bootstrap). There is no cron.
  • Deploy needs the KV namespace id and CORPUS_HOOK_SECRET.
  • The standmeet.com side lives in a separate repo (atmaxmoj/standmeet-landing).

No reconcile cron

The owner decided against any reconcile loop. Delivery is guaranteed or visibly failed: outbox, durable jobs and panel alerts cover it (message-loss-guarantees). A reconcile loop hides unreliable delivery; with reliable delivery it is not needed.

Acceptance

  • e2e embed-update-hook (passing): the embed form's hook → edit a note → the sink receives it; cards carry updated_at; an out-of-scope edit does not reach the hook (sentinel form).
  • Real environment (open): the owner edits a note on sijie.xyz → within 60 s the standmeet.com page shows the change, without a deploy. Recorded under docs/real-env-verification/ after the standmeet.com deploy. See events-roadmap · events-test-plan.
about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →