fix: normalize webhook payload encoding to support raw JSON inputs#2641
Conversation
The webhook `buildPayload` method correctly double-parses the stored
jsonPayload (`JSON.parse(JSON.parse(payloadString))`), expecting a
base64-encoded JSON string that wraps the actual JSON template.
However, the POST /webhook and POST /webhook/test routes only
single-encode the payload:
Buffer.from(req.body.options.jsonPayload).toString("base64")
This produces a base64 string that decodes directly to the JSON object
string. When buildPayload double-parses it, the first parse yields a
JS object, and the second parse calls toString() on that object,
producing "[object Object]" which is not valid JSON.
The fix wraps the payload with JSON.stringify() before base64-encoding,
matching the double-encoded format that buildPayload expects:
Buffer.from(JSON.stringify(req.body.options.jsonPayload)).toString("base64")
This is consistent with the default jsonPayload in settings/index.ts,
which is already stored in this double-encoded format.
Note: the default payload works because it was hardcoded as a
double-encoded base64 string. Only payloads saved via the API
(i.e., any user customization) trigger this bug.
📝 WalkthroughWalkthroughServer routes now JSON.stringify() the webhook JSON payload before base64-encoding and storing; the Settings UI no longer stringifies the payload before sending, so the server receives the raw value and normalizes encoding expected by the webhook agent. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as UI (Settings Webhook)
participant Server as Server (routes/settings/notifications)
participant DB as DB (stored config)
participant Agent as Webhook Agent (buildPayload)
participant Ext as External Webhook
UI->>Server: POST webhook payload (raw object/string)
Server->>Server: JSON.stringify(payload) -> base64 encode -> store
Server->>DB: save encoded payload
Note right of Agent: On notification trigger
Agent->>DB: read encoded payload
Agent->>Agent: base64 decode -> JSON.parse -> JSON.parse -> final object
Agent->>Ext: POST final JSON payload
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR attempts to fix issue #2640 where webhook notifications fail with "[object Object] is not valid JSON". The fix adds JSON.stringify() wrapping before base64-encoding the jsonPayload in both the save and test webhook routes, aiming to produce the double-encoded format that buildPayload() in webhook.ts expects.
Changes:
- Wraps
req.body.options.jsonPayloadwithJSON.stringify()before base64-encoding in thePOST /webhooksave route - Applies the same
JSON.stringify()wrapping in thePOST /webhook/testtest route
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
gauthier-th
left a comment
There was a problem hiding this comment.
Please edit the PR description according to the PR template in https://github.com/seerr-team/seerr/blob/develop/.github/PULL_REQUEST_TEMPLATE.md
The UI's onSubmit and testSettings handlers were wrapping jsonPayload with JSON.stringify() before sending to the API. Combined with the server-side JSON.stringify added in the previous commit, this produced triple-encoding (Level 3) that buildPayload()'s double-parse couldn't handle. Remove the frontend stringify so the server is the single point of encoding normalization — both UI and direct API callers now go through the same path.
|
Updated the PR description to follow the template. Also addressed Copilot's review — the frontend was redundantly calling |
fallenbagel
left a comment
There was a problem hiding this comment.
The description saying this affects "any webhook configuration saved via the API or UI" is inaccurate. The UI is not affected. The frontend already wraps with JSON.stringify(values.jsonPayload) before sending (line 227 and line 294, so saving and testing webhooks through the UI works fine. I can confirm this on my seerr instances on both dev branch and main branch and has always worked as well.
It only affects direct API callers because the server doesn't JSON.stringify before encoding in base64, so the payload ends up single-encoded instead of the double-encoded format that the buildPayload()'s JSON.parse(JSON.parse(...)) expect. The UI just so happens to compensate for this on the client side and is why it works.
That said, the fix is valid for ensuring direct API callers work correctly, and moving the encoding responsibility to the server is cleaner too imo, however, please update the PR description and issue #2640 to accurately reflect the actual scope.
|
Updated the PR description and issue #2640 to clarify the original scope: the broken path was direct API callers sending raw jsonPayload text, while the UI had been compensating client-side by pre-stringifying before submit. This PR keeps the UI working but moves encoding normalization to the server so both UI and API callers now go through the same path. |
…eerr-team#2641) Co-authored-by: Aldo <eliacim@aldo.pw>
Description
Webhook notifications fail with
"[object Object]" is not valid JSONfor direct API callers that save a webhook payload as raw JSON text.buildPayload()inwebhook.tsexpects a double-encoded payload (base64 -> JSON string -> JSON object):But the save routes (
POST /webhookandPOST /webhook/test) only single-encoded the payload:For direct API callers, that stores raw JSON in base64. On delivery, the first
JSON.parseproduces an object and the second parse fails with"[object Object]" is not valid JSON.The UI was previously compensating client-side by calling
JSON.stringify(values.jsonPayload)before sending, which is why the UI flow already worked. This PR moves that normalization to the server so both UI and direct API callers follow the same path.This change:
wraps
req.body.options.jsonPayloadwithJSON.stringify()before base64-encoding in both save and test routesremoves the redundant frontend
JSON.stringify()so the server is the single encoding normalization pointFixes Webhook notifications fail with "[object Object]" is not valid JSON after saving via API #2640
How Has This Been Tested?
Screenshots / Logs (if applicable)
N/A
Checklist:
pnpm buildpnpm i18n:extractAI Disclosure
This PR was authored by @aldoeliacim with the help of Claude Code. The bug was discovered while debugging a production Jellyseerr v3.1.0 webhook integration.
Summary by CodeRabbit
Bug Fixes