Skip to content

Webhooks get a docs page with a live card preview (GRYT-1187) - #111

Merged
sivert-io merged 1 commit into
mainfrom
claude/GRYT-1187-webhook-docs
Sep 15, 2026
Merged

sivert-io merged 1 commit into
mainfrom
claude/GRYT-1187-webhook-docs

Conversation

@sivert-io

Copy link
Copy Markdown
Member

GRYT-1187. Webhooks had no page of their own, only six rows in the Server API table. This adds Build → Bots and webhooks → Webhooks at /docs/build/webhooks.

What's on the page

  • Making a webhook in the app, with a screenshot from a throwaway server
  • The URL format, curl examples and the answer you get back
  • The payload reference: message, card, author, field, footer
  • Pictures: fetched once by the server, stored, and never loaded from the original host by readers
  • Mentions only notify from text, with the [@name](mention:<id>) syntax and where to copy an ID
  • The line older clients show instead of cards
  • Every problem and warning code, the other refusals, and rate limits
  • Recipes: a GitHub Actions deploy notice, an uptime alert from cron, plain text from a shell script
  • A live card preview

The preview

A JSON editor with four presets (Minimal, Rich, Lots of fields, Alert) and the real WebhookCard from @gryt/ui/webhook-card 0.34 beside it. It stacks on phones. As you type, it shows the answer the server would give: 200 with any unknown_key warnings, or the 400 body with each problem's path and code. Copy as curl copies the payload with a placeholder URL. Pictures load from the typed URLs in your own browser, and the page says Gryt fetches them server-side instead.

The preview is compared with the real client. I posted the Rich preset to a throwaway server and screenshotted how the client drew it:

Docs preview Client, same payload
Light, lots of fields Invalid edit
Phone, dark Phone, light Payload reference

The screenshots live on a pr-assets/GRYT-1187 branch so they don't ship. Delete it whenever.

Generated, and checked against the server

  • The payload tables are read from src/components/webhooks/openapi.json, a copy of the server's openapi/webhooks.json, and drawn with fumadocs' TypeTable. The .md copy of the page gets the same tables as markdown, through source.config.ts.
  • The preview's checks are a small hand copy of the server's rules (validate.ts), not zod, to keep the page light.
  • scripts/check-webhook-spec.mjs (yarn test:webhook-spec, now a CI step) fetches server main and fails when:
    • the spec copy differs from the server's
    • WEBHOOK_LIMITS differ
    • a problem or warning code in the spec has no explanation in codes.ts
    • the preview and the server's own zod schema give a different answer for any of 3,000 generated payloads (seeded, and I also ran 30,000 with three other seeds)

I checked that it fails by changing one limit and one maxLength by hand.

JS

Every docs page is one route, so a plain import put the preview on all of them. It now loads through next/dynamic, and only this page fetches it: 17.3 KB gzipped. Shared JS on other pages is 267 KB gzipped, the same on /docs/build/bots as on this page. About 12.3 KB of the preview is @gryt/ui/webhook-card and what it pulls in. 8.5 KB of that is tailwind-merge (through cn), 1.9 KB the Phosphor icon, and roughly 2 KB the card itself.

global.css scans only dist/components/WebhookCard plus the one focus-ring class list, so the CSS doesn't pick up the rest of the library.

What to look at

  • validate.ts copies some zod quirks on purpose, because the server really answers that way. A string where cards belongs gets wrong_type and also too_long with limit 10. An array's too_many comes after its items' problems. empty_card is skipped when the card already has a type error. The fuzz check is what pins these.
  • The check needs the network in CI. It reads raw.githubusercontent.com. A server PR that changes the spec will turn this repo's CI red until the copy is updated here, which is the point, but it'll show up on an unrelated docs PR.
  • The page documents two server bugs as they are today. Bad JSON and bodies over 2 MB get 500 internal_error. The spec promises 413 over 256 KB, but a global express.json({ limit: "2mb" }) runs before the route's own parser. That's GRYT-1198.
  • The create-webhook screenshot shows a client bug. The row sticks out past the dialog's right edge. That's GRYT-1199, and the screenshot should be retaken once it's fixed.
  • The preview's markdown is a small inline renderer: bold, italics, strikethrough, code and links. The page says the app does more.
  • Preset pictures point at https://docs.gryt.chat/webhooks/…, added in this PR. They're neutral placeholders copied from the ui repo, so they 404 until this deploys.
  • @gryt/ui goes from ^0.32.0 to ^0.34.0, and zod is now a direct dev dependency for the check. It was already in the tree through fumadocs.

🤖 Generated with Claude Code

There was no page for webhooks, only six rows in the Server API table. The
new one is under Build, next to bots. It covers making a webhook in the
app, the URL, curl examples, the full payload, pictures, mentions, the
fallback line older clients show, errors, rate limits and three recipes.

The payload tables are read from a copy of the server's
openapi/webhooks.json and drawn with fumadocs' TypeTable, so nothing about
the fields is written by hand. The .md copy of the page gets the same
tables as markdown.

The preview is a JSON editor with four presets next to the real
WebhookCard from @gryt/ui 0.34. It checks the payload as you type and
shows the answer the server would give, and it can copy the payload as a
curl command. The checks are a small copy of the server's rules rather
than zod, to keep the page light.

scripts/check-webhook-spec.mjs keeps both honest. It fetches server main
and fails when the spec copy differs, when the limits differ, when a
problem or warning code has no explanation, or when the preview and the
server's own zod schema disagree about any of 3,000 generated payloads.
It runs in CI as yarn test:webhook-spec.

The preview loads through next/dynamic. Every docs page is one route, so
a plain import put its 17 KB of gzipped JS on all of them. Now only this
page loads it. About 12 KB of that is @gryt/ui's card, and 8.5 KB of the
card is tailwind-merge.

Two bugs found on the way have their own tasks: bad JSON and bodies over
2 MB get a 500 instead of a 4xx (GRYT-1198), and webhook rows overflow the
server settings dialog (GRYT-1199). The page describes what happens today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sivert-io
sivert-io merged commit 6fb03cd into main Sep 15, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant