Every n8n workflow has two halves that are hard to test honestly. Outbound: HTTP Request nodes calling third-party APIs you can't make fail on demand โ so Retry On Fail, timeout settings, and error branches ship unexercised. Inbound: Webhook triggers that need realistic deliveries, ideally with real signature headers, to prove the workflow parses them.
n8n's built-in answer is data pinning โ pin a node's output and build downstream against it. It's genuinely useful, and the docs are plain about its boundary: it's a development feature, not available in production executions, and because the pinned node never runs, the HTTP layer is skipped entirely โ retries, timeouts, 429s, and error workflows never execute. This guide covers the other half: a live, stateful mock API your HTTP Request nodes can actually call, that fails exactly when you tell it to. Every curl below was verified against production before publishing.
The public demo project is a seeded e-commerce API. Drop an HTTP Request node in a workflow and set the URL to:
https://mockbird.mockbird.workers.dev/m/demo/products?limit=2
Execute the node โ you get two product records, and writes work too: POST /m/demo/products creates a record you can GET back from a later node. Same query conventions as json-server (_page/_limit/sortBy/filters), so pagination loops are testable. Want your own schema instead of ours? One curl or one click, below.
n8n's HTTP Request node has Settings โ Retry On Fail with Max Tries and Wait Between Tries (ms) (the editor caps them at 5 tries / 5,000 ms). The problem is proving it works: real APIs don't fail twice and then recover on cue. ?mock_seq does exactly that:
# 1st request โ 500, 2nd โ 500, 3rd and later โ real 200 response
curl -s -o /dev/null -w '%{http_code}\n' \
'https://mockbird.mockbird.workers.dev/m/demo/products?mock_seq=500,500,200&mock_seq_key=myflow'
# run it four times: 500 500 200 200
Point the HTTP Request node at that URL, enable Retry On Fail with Max Tries 3, execute once: tries one and two eat the 500s, try three succeeds, the node goes green. Every response carries x-mockbird-seq: pos/len so you can see which step of the sequence each attempt hit. Use a distinct mock_seq_key per workflow so parallel tests don't share a counter, and &mock_seq_reset=1 to restart a sequence. Failed writes are not applied โ a POST that draws a 500 inserts nothing, which is exactly the semantics your retry logic should assume.
Error workflows (an Error Trigger node, linked in Workflow Settings) usually get tested by hoping something breaks. Make it break on purpose:
https://mockbird.mockbird.workers.dev/m/demo/products?mock_status=500
With the node's On Error left at the default (stop workflow), that request fails the node, fails the execution, and fires your error workflow โ Slack alert, incident ticket, whatever it does. Remove the param and the same node returns 200. One URL parameter is the whole toggle, which makes it easy to keep a disabled "break glass" node in the workflow for drills. mock_status takes any code; combine with &mock_delay=2000 for slow-then-fail.
curl -s -o /dev/null -w '%{time_total}\n' \
'https://mockbird.mockbird.workers.dev/m/demo/products/1?mock_delay=3000'
# 3.17s
Set the HTTP Request node's Timeout below the delay and you can watch the timeout path fire โ then decide whether that should retry (ยง2) or fail into the error workflow (ยง3). mock_delay goes up to 5,000 ms; mock_jitter adds a random component if you want latency that isn't suspiciously constant.
n8n's own rate-limit guidance is Retry On Fail with a wait longer than the provider's window. Rehearse it against a simulated limit:
# allow 3 requests per 60s per client, then 429 with Retry-After
'https://mockbird.mockbird.workers.dev/m/demo/products?mock_ratelimit=3&limit=1'
# 4 rapid requests โ 200 200 200 429
Responses carry x-ratelimit-limit / x-ratelimit-remaining / x-ratelimit-reset, and the 429 includes Retry-After โ the headers your Loop Over Items + Wait pattern should be reading.
When a workflow POSTs to a partner API and the partner says "malformed payload", you want the request, verbatim. Point the node at a bin instead:
curl -s -X POST https://mockbird.mockbird.workers.dev/m/demo/hooks/order-created \
-H 'content-type: application/json' -d '{"orderId":123,"total":49.9}'
# โ {"ok":true,"caught":"POST /hooks/order-created","body":{...},"receivedAt":"โฆ"}
The demo's catch-all route echoes what it caught, and every hit โ method, path, headers, body โ lands in the public demo request inspector. On your own project, an empty project plus one ANY /* custom route is a private bin with a per-project inspector โ webhook.site without the 7-day expiry.
Mockbird projects can send outbound webhooks on every record change โ signed POSTs with x-mockbird-event, x-mockbird-delivery, and x-mockbird-signature: sha256=โฆ (HMAC-SHA256) headers. That's a realistic stand-in for any signed-webhook provider. Aim it at your n8n Webhook node:
# point your project's webhook at the n8n URL (returns the signing secret)
curl -s -X PUT https://mockbird.mockbird.workers.dev/api/projects/YOUR_ID/webhook \
-H "x-admin-key: YOUR_ADMIN_KEY" -H "content-type: application/json" \
-d '{"url":"https://YOURNAME.app.n8n.cloud/webhook-test/abc123"}'
# instant test delivery, no record edit needed:
curl -s -X POST https://mockbird.mockbird.workers.dev/api/projects/YOUR_ID/webhook/test \
-H "x-admin-key: YOUR_ADMIN_KEY"
For the Test URL, click Listen for Test Event in the editor first โ n8n registers it for 120 seconds, which is plenty for the webhook/test curl โ and the payload appears right in the editor. Switch to the Production URL once the workflow is Active. Then verify the signature in a Code node with the returned secret, and you've tested the part of webhook handling everyone skips. Delivery attempts (status, error, duration) are listed at GET โฆ/webhook/deliveries. One honest constraint: deliveries travel the public internet, so a self-hosted n8n on localhost needs tunnel mode or a public URL โ we refuse to POST to private addresses. More recipes: sending test webhooks.
One curl (or one click), no signup:
curl -s -X POST https://mockbird.mockbird.workers.dev/api/projects \
-H "content-type: application/json" \
-d '{"preset":"ecommerce"}'
# โ {"id":"abc123xyz9", "adminKey":"โฆ", ...}
Presets: blog / ecommerce / saas โ or define resources by hand, or import an OpenAPI spec, db.json, CSV, or HAR recording of the real API. Every simulation param above works on your project's URLs. Projects have a 10,000 requests/day cap โ workflow testing barely dents it.
Fair's fair: for shaping expressions and building downstream nodes against a known payload in the editor, data pinning is faster than any mock server โ no network, no setup, works offline. Use it for that. Reach for a hosted mock when the thing under test is the HTTP layer itself: retries, timeouts, 429s, error workflows, signature verification, or any execution that runs in production mode, where pinned data doesn't apply. They compose โ pin the happy path, mock the failure paths.
Related: the Make (Integromat) version of this guide, testing loading and error states, send test webhooks with real signatures, request bins compared, simulating rate limits, and mocking third-party APIs generally.
Verification: all demo curls on this page (seq 500โ500โ200โ200, mock_status 500, 3.17s delay, 200 200 200 429 rate-limit run, bin echo + inspector log) were run against production on 5 Sep 2026 before publishing. n8n behavior โ pinned data unavailable in production executions, Retry On Fail settings and editor caps, Error Trigger linking, 120-second test-webhook window, tunnel mode for localhost โ is from n8n's official docs. We don't run your n8n instance; if a step here doesn't work, that's a bug: tell us.