gitroomhq/postiz-app · error · BadBody

Failed to upload the media file

Error message

Failed to upload the media file

What it means

The PUT of media bytes to Whop's upload_url returned a non-ok status. Because a rejected PUT leaves the file 'pending' forever, it fails fast with BadBody instead of burning the ~9-minute status poll.

Source

Thrown at libraries/nestjs-libraries/src/integrations/social/whop.provider.ts:296

        });
        if (!fileResponse.ok || !fileResponse.body) {
          throw new Error(`Failed to fetch media: ${fileResponse.statusText}`);
        }
        const uploadResponse = await fetch(createFileResponse.upload_url, {
          method: 'PUT',
          headers: {
            // Whop's own upload_headers win if they ever include a length.
            'Content-Length': String(contentLength),
            ...(createFileResponse.upload_headers || {}),
          },
          body: fileResponse.body,
          // Required by undici when streaming a request body.
          duplex: 'half',
        } as any);
        // A rejected PUT leaves the file pending forever - fail fast instead
        // of burning the ~9 minute status poll below.
        if (!uploadResponse.ok) {
          throw new BadBody(
            this.identifier,
            await uploadResponse.text().catch(() => '{}'),
            '{}',
            'Failed to upload the media file'
          );
        }

        let uploadStatus = 'pending';
        let attempts = 0;
        const maxAttempts = 108; // ~9 minutes at 5s interval
        while (uploadStatus !== 'ready') {
          if (attempts++ >= maxAttempts) {
            throw new Error('File upload timed out');
          }

          const fileStatus = await (
            await this.fetch(
              `https://api.whop.com/api/v1/files/${createFileResponse.id}`,

View on GitHub (pinned to 0f1647f749)

Solutions

  1. Read the response text attached to the BadBody for Whop's rejection reason
  2. Ensure the Content-Length matches the actual byte stream being PUT (don't transcode/resize mid-flight)
  3. Retry the post to get a fresh upload_url
  4. Verify the file is within Whop's size/type limits
Defensive patterns

Strategy: retry

Validate before calling

if (contentLength !== actualBytes.length) { /* abort before PUT; sizes must match */ }

Try / catch

catch (e) { if (e instanceof BadBody && e.message.includes('upload the media file')) { /* retry post for a fresh upload_url; inspect attached response text */ } throw e; }

Prevention

When it happens

Trigger: Whop's file upload endpoint rejects the PUT: expired upload_url, wrong Content-Length header (does not match actual streamed bytes), content-type mismatch, or size exceeding Whop's limit.

Common situations: Media file changed size between HEAD and PUT; Whop upload headers requirements changed; slow networks letting the signed upload_url expire before the PUT starts.

Related errors


AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27). Data as JSON: /api/errors/2691067d1a07d636. Report an issue: GitHub.