Webhooks in n8n, explained properly (test URL vs production, and why your callback never arrives)
The wait-for-callback pattern powers every slow integration. Most tutorials skip the part that actually trips people up.
A webhook is just a URL that lets another service call into your workflow. That is really all it is. But n8n adds two details that trip up almost everyone, and most tutorials never mention them.
Why you need webhooks at all
Some jobs take minutes. An AI service clipping a video, a render queue, a bank running a check. Your workflow cannot just sit there waiting, because n8n runs are not meant to hang for twenty minutes. So the pattern is:
- Workflow A sends the job and includes a callback URL. Basically: call me here when you are done.
- Workflow A ends. Nothing is waiting, nothing is burning resources.
- Minutes later, the service POSTs to your webhook URL, which starts workflow B with the results.
I use this with Opus.pro for video clipping. The request goes out with a conclusionActions webhook, and when the clips are ready, Opus knocks on my n8n with the project ID.
Test URL vs Production URL
Every n8n Webhook node shows two URLs, and this is where most of the "my webhook does not work" problems live:
- The Test URL (
/webhook-test/…) only listens while you have the editor open and have clicked Listen for test event. Close the tab and it is dead. - The Production URL (
/webhook/…) only works when the workflow is toggled Active.
The classic failure: you test with the Test URL, everything works, you paste that Test URL into the external service, and go to bed. The callback arrives at 2 AM, nobody is listening, and the run silently disappears. Always put the Production URL in the external service, and keep the workflow active.
Respond fast, work later
Services that call webhooks usually expect a quick 200 OK. If your workflow does five minutes of work before responding, the caller may assume it failed and try again. Now you are processing the same payload twice. In n8n: set the Webhook node to use a Respond to Webhook node, place it early, return 200, and do the heavy lifting after.
Debugging in the right order
- Nothing arrives? Check that the workflow is Active, check that you used the Production URL, and check that your n8n can be reached from the internet. A webhook on
localhostis invisible to the outside world. - Something arrives but the workflow fails? Open the run, click the Webhook node, and read the actual payload. The field you expect at
{{ $json.projectId }}is probably at{{ $json.body.projectId }}. Payloads are almost always nested one level deeper than you think. - Works in test, dies in production? You have pinned test data or a hardcoded ID somewhere. Search the workflow for values that should be expressions.
Once this pattern clicks, a whole group of integrations opens up: anything slow, anything async, anything where another system decides when it is done.