Skip to content

fix: webhook notifications fail with "[object Object] is not valid JSON"#2641

Open
aldoeliacim wants to merge 3 commits intoseerr-team:developfrom
aldoeliacim:fix/webhook-double-json-parse
Open

fix: webhook notifications fail with "[object Object] is not valid JSON"#2641
aldoeliacim wants to merge 3 commits intoseerr-team:developfrom
aldoeliacim:fix/webhook-double-json-parse

Conversation

@aldoeliacim
Copy link

@aldoeliacim aldoeliacim commented Mar 6, 2026

Description

Webhook notifications fail with "[object Object]" is not valid JSON for any webhook configuration saved via the API or UI.

buildPayload() in webhook.ts expects a double-encoded payload (base64 → JSON string → JSON object):

const parsedJSON = JSON.parse(JSON.parse(payloadString));

But the save routes (POST /webhook and POST /webhook/test) only single-encode the payload:

jsonPayload: Buffer.from(req.body.options.jsonPayload).toString("base64")

Saved payloads decode to raw JSON (not a JSON-wrapped string), so the first JSON.parse produces an object, and the second JSON.parse gets "[object Object]" instead of valid JSON. The default payload works because it was hardcoded as a pre-double-encoded base64 string.

The fix wraps the payload with JSON.stringify() before base64-encoding in both the save and test routes, producing the same double-encoded format that buildPayload() expects.

How Has This Been Tested?

  1. Save a custom webhook payload via the API or UI
  2. Trigger a test notification → succeeds (HTTP 204)
  3. Trigger a real notification (e.g., media request) → webhook fires correctly
  4. Verified the default payload still works (fresh install)

Screenshots / Logs (if applicable)

N/A

Checklist:

  • I have read and followed the contribution guidelines.
  • Disclosed any use of AI (see our policy)
  • I have updated the documentation accordingly.
  • All new and existing tests passed.
  • Successful build pnpm build
  • Translation keys pnpm i18n:extract
  • Database migration (if required)

AI 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
    • Fixed webhook payload encoding in notification settings so configured and test webhooks now send the correctly serialized JSON payload, preventing malformed or double-encoded data when delivering notifications.

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.
Copilot AI review requested due to automatic review settings March 6, 2026 07:06
@aldoeliacim aldoeliacim requested a review from a team as a code owner March 6, 2026 07:06
@coderabbitai
Copy link

coderabbitai bot commented Mar 6, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a0516c7c-0724-4b6d-a680-154df7f22de5

📥 Commits

Reviewing files that changed from the base of the PR and between 0285fee and f50c357.

📒 Files selected for processing (1)
  • src/components/Settings/Notifications/NotificationsWebhook/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/Settings/Notifications/NotificationsWebhook/index.tsx

📝 Walkthrough

Walkthrough

Server endpoints now JSON.stringify() the webhook JSON payload before base64-encoding and storing; the Settings UI stopped JSON.stringify-ing the payload before sending, so the server receives the raw value and performs the required double-encoding expected by the webhook agent.

Changes

Cohort / File(s) Summary
Server: webhook save endpoints
server/routes/settings/notifications.ts
Wrap req.body.options.jsonPayload with JSON.stringify() before Buffer.from(...).toString('base64') in POST /webhook and POST /webhook/test, aligning storage format with consumer expectations.
Client: webhook UI
src/components/Settings/Notifications/NotificationsWebhook/index.tsx
Removed JSON.stringify when building options.jsonPayload in onSubmit and testSettings; the UI now sends the raw value so the server performs stringify+base64 encoding.

Sequence Diagram(s)

mermaid
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

🐰 I hopped through code with a curious squeak,
Wrapped payloads in strings so webhooks won't freak,
UI sends the object, the server does the rest,
Now JSON's well-dressed and endpoints are blessed! 🥕

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically identifies the main issue being fixed (webhook double-JSON parsing error), which aligns with the primary change of adjusting JSON encoding in the save routes.
Linked Issues check ✅ Passed The PR changes fully address the requirements in #2640: the server routes now JSON.stringify() the payload before base64-encoding, matching buildPayload's double-parsing expectations, and the frontend was corrected to not redundantly stringify.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the webhook double-encoding issue: server-side JSON.stringify wrapping in save routes and frontend payload passing adjustments. No unrelated modifications were introduced.

✏️ 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.jsonPayload with JSON.stringify() before base64-encoding in the POST /webhook save route
  • Applies the same JSON.stringify() wrapping in the POST /webhook/test test 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.

Copy link
Member

@gauthier-th gauthier-th left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@aldoeliacim
Copy link
Author

Updated the PR description to follow the template. Also addressed Copilot's review — the frontend was redundantly calling JSON.stringify before sending, which combined with the server-side fix would cause triple-encoding. Removed the frontend stringify in 0285fee so the server handles all encoding normalization.

@aldoeliacim aldoeliacim requested a review from gauthier-th March 6, 2026 14:56
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.

Webhook notifications fail with "[object Object] is not valid JSON" after saving via API/UI

3 participants