A Stripe signature header proves the bytes came from Stripe. It does not prove the bytes match the shape your handler expects. The same trap exists for every JSON crossing a runtime boundary: webhooks, queue payloads, third-party API responses, AI tool calls. TypeScript types are a compile-time annotation; the actual JSON drifts over time. Zod closes that gap by validating shape at runtime. The friction is hand-writing the schema. So I built a small in-browser tool that does it for you — paste a JSON sample, get a runnable z.object . This post is about the four non-obvious algorithm choices that came out of that build. The tool is here, free, no signup, pure client-side: json-to-ts-app.netlify.app (toggle the output mode to zod ). The shape walk Conceptually a JSON → Zod converter is a tree walk: visit every node, emit a Zod expression. Primitives map to z.string() / z.number() / z.boolean() / z.null() . Objects become z.object({...}) . Arrays become z.array(...) . Easy.…