You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Plugin route handlers receive the parsed body as ctx.input; the dispatcher consumes the request stream first, and ctx.request.text()/json() are guarded to throw an actionable error (#1293). That's the right default — but it makes one real use case impossible: verifying webhook signatures.
Providers like Stripe, GitHub, and Svix sign the exact raw bytes of the delivery (HMAC(rawBody, secret)). A signature computed over a re-serialized ctx.input never matches, because whitespace and key order don't survive JSON.parse → JSON.stringify. Today a plugin that wants to receive signed webhooks has no way to authenticate them — it can only fall back to workarounds like re-fetching the event from the provider's API by ID (extra round-trip, and not all providers support it).
A second, smaller gap: non-JSON payloads (form-encoded webhook deliveries, plain-text pings) currently parse to undefined and are silently lost.
Proposal
An opt-in flag on the route definition, additive and default-off:
The dispatcher already buffers the body to parse it; it reads text() once, parses ctx.input from the same buffer, and passes the string through to routes that opted in. No extra I/O, no behavior change for existing routes.
ctx.input keeps working exactly as before (including Zod validation).
For non-JSON bodies, ctx.input stays undefined but ctx.rawBody carries the payload.
Native format only for now; sandboxed plugins cross a serialization boundary and would need separate plumbing.
I have a working implementation with tests and docs ready to PR if this direction sounds right.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
Problem
Plugin route handlers receive the parsed body as
ctx.input; the dispatcher consumes the request stream first, andctx.request.text()/json()are guarded to throw an actionable error (#1293). That's the right default — but it makes one real use case impossible: verifying webhook signatures.Providers like Stripe, GitHub, and Svix sign the exact raw bytes of the delivery (
HMAC(rawBody, secret)). A signature computed over a re-serializedctx.inputnever matches, because whitespace and key order don't surviveJSON.parse→JSON.stringify. Today a plugin that wants to receive signed webhooks has no way to authenticate them — it can only fall back to workarounds like re-fetching the event from the provider's API by ID (extra round-trip, and not all providers support it).A second, smaller gap: non-JSON payloads (form-encoded webhook deliveries, plain-text pings) currently parse to
undefinedand are silently lost.Proposal
An opt-in flag on the route definition, additive and default-off:
text()once, parsesctx.inputfrom the same buffer, and passes the string through to routes that opted in. No extra I/O, no behavior change for existing routes.ctx.inputkeeps working exactly as before (including Zod validation).ctx.inputstaysundefinedbutctx.rawBodycarries the payload.I have a working implementation with tests and docs ready to PR if this direction sounds right.
Beta Was this translation helpful? Give feedback.
All reactions