n8nvideo-processingapi

How to Fix n8n Timeout Errors When Processing Video (Webhooks + Polling Recipe)

·Javid Jamae·10 min read
How to Fix n8n Timeout Errors When Processing Video (Webhooks + Polling Recipe)

Your n8n workflow builds fine, runs on a short test clip, then dies the moment you feed it a real video. The execution turns red with a timeout, the render never finishes, and you have no idea whether the job actually ran. This is the classic wall n8n builders hit the first time they put video processing inside a workflow.

Quick answer: An n8n video processing timeout happens because n8n Cloud caps a single execution at roughly 2 to 5 minutes, and a real FFmpeg transcode or composition takes longer than that. The fix is to stop running the video work inside the execution: submit the job to a webhook-based video API that returns a job ID in under a second, then either poll for the result or have the API call an n8n webhook back when the render is done.

Why n8n times out on video jobs

The timeout isn't a bug in your workflow. It's n8n doing exactly what it's designed to do, killing any execution that runs too long so one stuck job doesn't hold a worker forever.

Two nodes create this problem, and they fail for different reasons.

The Execute Command node is the one people reach for to shell out to a local FFmpeg binary. It isn't available on n8n Cloud at all, so ffmpeg -i in.mp4 ... simply isn't an option there. Even on self-hosted n8n, the command blocks the execution thread until FFmpeg finishes, and a two-minute 1080p transcode will blow past the execution timeout while pinning your worker's CPU.

The HTTP Request node is the other trap. When you call a slow synchronous endpoint that does the encoding before it responds, n8n holds the connection open and waits. The node has its own request timeout, but the real killer is the workflow-level cap. n8n Cloud limits a single workflow execution to roughly 2 to 5 minutes depending on your plan tier, and self-hosted instances enforce the same thing through the EXECUTIONS_TIMEOUT environment variable. Any video job longer than that window gets terminated mid-render, and n8n marks the execution failed even though the work never had a chance to complete.

That's the mechanism. You're trying to run a long job inside a system built to run short ones.

The fix: offload the render, don't hold the execution open

The fix for an n8n video timeout isn't a longer timeout or a beefier worker. It's not running the render inside the n8n execution at all.

Instead of one node that waits for FFmpeg to finish, you split the work into two fast interactions:

  1. Submit the job to a video API. The API accepts the request, queues the render, and returns a job ID in well under a second. Your n8n execution finishes immediately, nowhere near the timeout.
  2. Collect the result later, either by polling the job status on a short interval or by having the API POST a webhook back to n8n when the output is ready.

FFmpeg Micro is built around exactly these job semantics: submit a job to api.ffmpeg-micro.com, then poll or receive a webhook, then download the output. One API call replaces the media microservice you'd otherwise babysit. The heavy FFmpeg work runs on managed infrastructure, so there's no binary to install, no server to scale, and nothing for n8n to wait on.

Here are both patterns as copy-paste recipes. Check /docs for the exact request body fields for your operation.

Recipe 1: the polling pattern

Use polling when you want everything in a single linear workflow and don't want to expose a public webhook. You submit the job, wait a few seconds, check status, and loop until it's done.

The node sequence: HTTP Request (create job) leads to Wait, then HTTP Request (get status), then IF (done? loop back or continue).

The create-job request sends a small JSON body. This is the payload the HTTP Request node posts to /jobs:

{
  "operation": "transcode",
  "input": "https://example.com/input.mp4"
}

In n8n, that maps to an HTTP Request node with sendBody enabled and the body set to JSON. Here is the full node export, with the input URL pulled from an incoming field:

{
  "name": "Create job",
  "type": "n8n-nodes-base.httpRequest",
  "parameters": {
    "method": "POST",
    "url": "https://api.ffmpeg-micro.com/jobs",
    "authentication": "genericCredentialType",
    "sendBody": true,
    "specifyBody": "json",
    "jsonBody": "={{ JSON.stringify({ operation: 'transcode', input: $json.videoUrl }) }}"
  }
}

Building the body with JSON.stringify inside the expression avoids hand-escaping quotes, which is where these nodes usually break. The rest of the loop is three more nodes:

{
  "nodes": [
    {
      "name": "Wait 5s",
      "type": "n8n-nodes-base.wait",
      "parameters": { "amount": 5, "unit": "seconds" }
    },
    {
      "name": "Get status",
      "type": "n8n-nodes-base.httpRequest",
      "parameters": {
        "method": "GET",
        "url": "=https://api.ffmpeg-micro.com/jobs/{{ $('Create job').item.json.id }}"
      }
    },
    {
      "name": "Done?",
      "type": "n8n-nodes-base.if",
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.status }}",
              "operation": "notEqual",
              "value2": "completed"
            }
          ]
        }
      }
    }
  ]
}

Wire the true branch of the IF node (status not completed) back into Wait 5s so it loops, and the false branch forward to your download or next step. Because each cycle is a sub-second HTTP call plus a short wait, the whole loop stays inside the execution window even for a five-minute render. Keep the wait interval sensible: 5 to 10 seconds is plenty, and hammering the status endpoint every 500 ms just burns executions.

Recipe 2: the webhook-return pattern

Use the webhook pattern when you don't want n8n sitting in a loop at all. You submit the job with a callback URL, the first workflow ends, and a second workflow wakes up when the render finishes. This is the cleaner option for long jobs and high volume.

Workflow A submits the job and points the callback at a Webhook trigger you own:

{
  "name": "Submit with callback",
  "type": "n8n-nodes-base.httpRequest",
  "parameters": {
    "method": "POST",
    "url": "https://api.ffmpeg-micro.com/jobs",
    "sendBody": true,
    "specifyBody": "json",
    "jsonBody": "={{ JSON.stringify({ operation: 'compose', input: $json.videoUrl, webhook: 'https://your-instance.app.n8n.cloud/webhook/render-done' }) }}"
  }
}

Workflow B starts with the Webhook node at path render-done and handles the finished output:

{
  "nodes": [
    {
      "name": "Render done",
      "type": "n8n-nodes-base.webhook",
      "parameters": { "path": "render-done", "httpMethod": "POST" }
    },
    {
      "name": "Download output",
      "type": "n8n-nodes-base.httpRequest",
      "parameters": {
        "method": "GET",
        "url": "={{ $json.outputUrl }}"
      }
    }
  ]
}

No polling, no loop, no held-open execution. The API does the waiting; n8n only runs when there's actual work to do. This is the same offload approach we use for assembling faceless-channel videos from assets and for concatenating clips programmatically, where a single workflow can kick off dozens of renders without ever touching the timeout.

Polling vs webhook: which to use

Both patterns solve the timeout. Pick based on volume and how much infrastructure you want to expose.

FactorPollingWebhook return
SetupSingle workflow, no public URLTwo workflows, public webhook node
Best forLow volume, quick jobsLong jobs, high volume, batches
n8n execution costOne execution that loopsTwo short executions
Failure visibilityEasy to see in one runSplit across two runs
Requires public endpointNoYes

If you're testing or running a handful of jobs a day, polling is less to set up. Once you're rendering dozens of videos or your jobs regularly run past a minute, the webhook pattern keeps your execution list clean and your workers free.

Common pitfalls

These are the mistakes that keep the timeout coming back even after you've split the work.

  • Still using a synchronous endpoint. If your "submit" call waits for the render before responding, you haven't offloaded anything. The create-job request must return a job ID immediately, not the finished file.
  • Hand-escaping the JSON body. Typing \"operation\": \"transcode\" into the jsonBody field by hand is where most of these nodes break. Build the body with JSON.stringify({ ... }) in an expression and let n8n produce valid JSON for you.
  • Polling too aggressively. A Wait of 500 ms turns one render into hundreds of status calls and can itself approach the execution limit. Use 5 to 10 second intervals.
  • No max-attempts guard. An infinite IF loop with no counter will run until the execution times out if a job gets stuck. Add a counter and bail after, say, 60 checks.
  • Webhook path not reachable. On n8n Cloud your production webhook URL is public by default, but a self-hosted instance behind a firewall won't receive the callback. Confirm the callback URL is reachable from the internet before relying on it.
  • Passing giant files through n8n. Don't pull a 500 MB output into an n8n binary field just to move it. Pass the output URL downstream and let the destination fetch it.

When not to offload (and when to choose something else)

Offloading to a video API is the right call when your job is long, bursty, or CPU-heavy. It's overkill for a two-second thumbnail grab on a tiny clip, where a self-hosted Execute Command node might finish inside the window just fine.

It's also not the answer if you need a human-facing video editor with a timeline UI, or if your workflow is really about live streaming rather than file-based rendering. FFmpeg Micro is an API for batch media processing, not an editing suite or a streaming server. For heavy in-house pipelines you already run on AWS, tools like AWS MediaConvert or a self-managed FFmpeg fleet may fit your billing better. The trade-off you accept by not offloading is the one you started with: n8n holding a worker open and a timeout waiting to happen. The same offload logic applies anywhere serverless kills long jobs, which is why Supabase Edge Functions can't run FFmpeg either.

FAQ

Why does my n8n workflow time out when processing video?

Because n8n Cloud caps a single execution at roughly 2 to 5 minutes and a real FFmpeg transcode or composition runs longer than that. The HTTP Request node holds the connection open while the encode runs, and the workflow-level timeout kills the execution before the render finishes.

Can I run FFmpeg directly in n8n?

Only on self-hosted n8n, using the Execute Command node to call a local FFmpeg binary. It's not available on n8n Cloud, and even self-hosted it blocks the execution thread and counts against your EXECUTIONS_TIMEOUT, so long jobs still fail. Calling a hosted FFmpeg API avoids both problems.

How do I handle a long-running video job in n8n?

Submit the job to a video API that returns a job ID immediately, then either poll the status endpoint on a 5 to 10 second Wait loop or have the API POST a webhook back to an n8n Webhook trigger when the render completes. Either way, no single execution stays open long enough to time out.

How do I send a JSON body from the n8n HTTP Request node without breaking it?

Enable sendBody, set the body type to JSON, and build the payload with an expression like ={{ JSON.stringify({ operation: 'transcode', input: $json.videoUrl }) }}. This produces valid, correctly escaped JSON instead of the malformed body you get from hand-typing escaped quotes.

Does polling or the webhook return use more n8n executions?

Polling uses one execution that loops with Wait and IF nodes, while the webhook pattern uses two short executions, one to submit and one to receive the callback. For high volume, the webhook pattern keeps each execution short and your execution log readable.

Spin up the polling recipe on a test video first, then switch to the webhook pattern once you're rendering at volume. You can grab an API key and run both against the free tier in a few minutes: sign up here.

About Javid Jamae

Founder & CEO at FFmpeg Micro

Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.

Software EngineeringVideo ProcessingFFmpegCloud ArchitectureAPI DesignAutomation

Ready to process videos at scale?

Start using FFmpeg Micro's simple API today. No infrastructure required.

Get Started Free