#9403: feat(hooks): Support application/x-www-form-urlencoded content-type via webhooks
gateway
stale
### Summary
This PR adds support for parsing `application/x-www-form-urlencoded` request bodies alongside JSON.
### Motivation
Some third-party webhooks (e.g. Mailgun route webhooks) send payloads as `application/x-www-form-urlencoded`, and the content type cannot be configured by the client. Previously, these requests failed JSON parsing even though the payload was valid.
```ts
{
"body": {
"ok": false,
"error": "SyntaxError: No number after minus sign in JSON at position 1 (line 1 column 2)"
},
"status": 400
}
```
### Changes
- Introduces a form-urlencoded body parser with size limits and repeated-key handling
- Dispatches body parsing based on Content-Type
- Preserves existing JSON behavior and error handling
This allows webhook integrations to work correctly without requiring upstream control over request headers.
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR refactors hook request body parsing by extracting a generic `readBody()` (byte-limited raw string reader), then re-implementing `readJsonBody()` on top of it and adding a new `readFormUrlEncodedBody()` for `application/x-www-form-urlencoded`. `createHooksRequestHandler` in `src/gateway/server-http.ts` now dispatches between JSON vs form parsing based on the request `Content-Type`, preserving the existing JSON path and returning 413 on oversized payloads.
<h3>Confidence Score: 3/5</h3>
- This PR is mergeable after addressing a functional mismatch in how form-urlencoded bodies are consumed by the existing hook endpoints.
- Core parsing changes look contained and keep JSON behavior, but the current integration in the hook handler appears to drop/ignore the urlencoded fields for the built-in hook endpoints, which would make the feature ineffective for typical webhook payloads.
- src/gateway/server-http.ts, src/gateway/hooks.ts
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
**Context used:**
- Context from `dashboard` - CLAUDE.md ([source](https://app.greptile.com/review/custom-context?memory=fd949e91-5c3a-4ab5-90a1-cbe184fd6ce8))
- Context from `dashboard` - AGENTS.md ([source](https://app.greptile.com/review/custom-context?memory=0d0c8278-ef8e-4d6c-ab21-f5527e322f13))
<!-- /greptile_comment -->
Most Similar PRs
#7501: feat(hooks): expose rawBody in webhook transform context
by tunamitom · 2026-02-02
79.1%
#9914: fix(hooks): resolve bundled hook dist paths and packaging checks
by zimmra · 2026-02-05
74.9%
#23447: Gateway: harden hook ingress content-type validation
by bmendonca3 · 2026-02-22
74.9%
#10109: feat(plugins): invoke message_received and message_sent hooks
by nezovskii · 2026-02-06
74.7%
#6405: feat(security): Add HTTP API security hooks for plugin scanning
by masterfung · 2026-02-01
74.1%
#19785: fix(gateway): support query parameter tokens for webhooks
by cfdude · 2026-02-18
73.5%
#7545: feat(hooks): add message:received hook for pre-turn automation
by wangtian24 · 2026-02-02
73.3%
#11597: feat(hooks): implement message:received hook
by gnufoo · 2026-02-08
73.1%
#23765: Gateway hooks: enforce JSON content type and strict payload keys
by bmendonca3 · 2026-02-22
73.0%
#19922: feat(hooks): add message:received and message:sent hook events
by NOVA-Openclaw · 2026-02-18
72.4%