How I finally built dynamic, multi-tenant workflows in n8n

Hey everyone,

I see a lot of questions pop up here about how to manage credentials for multiple clients without either A) paying for the Enterprise tier to isolate user environments, or B) cloning the same workflow 50 times and creating a DevOps nightmare.

I recently hit this exact wall. I was trying to build a automation setup, but n8n’s native service nodes (like Google Sheets, Gmail, OpenAI) kept forcing me to select a static credential at design time.

After a lot of trial and error, I ended up building a completely “headless” architecture to bypass this limitation. I wanted to share the exact setup here in case anyone else is stuck in the workflow-cloning trap.

The Solution: Headless n8n + Dynamic HTTP Nodes

Instead of forcing multi-tenancy inside n8n, I moved all the user management to a custom frontend and external database.

The workflow starts with the Client Dashboard: Clients log into a custom web app and input their API keys or complete an OAuth flow (like Google SSO). My app stores their access_token and refresh_token securely in an external database mapped to their Client ID.

When an action is triggered, the frontend fires a webhook to n8n, passing both the execution instructions and the specific client’s authentication tokens directly in the JSON payload.

Bypassing Native Nodes

To make this work, you have to completely ditch native platform nodes (like the “OpenAI” or “Google Sheets” nodes) because they lock you into static credentials. Instead, you use standard HTTP Request nodes.

Here is how you handle the credentials dynamically using **The Golden Rule of Absolute Referencing:**If you just use standard n8n $json expressions, your workflow will break if you add formatting nodes or timers between the webhook and the HTTP node. To make it bulletproof, you must use absolute referencing to point directly back to the Webhook trigger.

**Example 1: OpenAI (Bearer Token)**Instead of the native node, you use an HTTP Request pointing to https://api.openai.com/v1/chat/completions. Under the Headers section, you set:

  • Name: Authorization

  • Value: =Bearer {{ $('Webhook').item.json.body.openAiKey }} (This pulls the exact client’s key sent from your frontend webhook).

**Example 2: Google Sheets (OAuth Token & Custom Body)**Google is slightly trickier because it requires an OAuth token and specific body formatting.

  • URL: https://sheets.googleapis.com/v4/spreadsheets/{{ $('Webhook').item.json.body.spreadsheetId }}/values/Sheet1!A1:append?valueInputOption=USER_ENTERED

  • Headers: Pass Authorization: Bearer {{ $('Webhook').item.json.body.googleToken }} and Content-Type: application/json.

  • Body: Send the data payload dynamically (e.g., pulling lead data from an earlier step into Google’s required “array of arrays” format).

Why this matters

By decoupling the credential data from n8n’s vault, I now have one single master workflow blueprint per task.

Whether I have 5 clients or 5,000, they all trigger the exact same n8n canvas. The HTTP nodes dynamically adapt and execute using whatever credentials the webhook just handed them. If a bug needs fixing, I update one master workflow, and every single client instantly benefits. It keeps infrastructure costs totally flat.

Since this took a bit of head-scratching to map out initially, I put together a full YouTube video showing exactly how I built it. It covers the database schema and walks through setting up the dynamic HTTP modules for Google Sheets and OpenRouter as real-world examples.

If you are trying to build your own SaaS wrapper around n8n, you can check out my video walkthrough here: https://www.youtube.com/watch?v=u1MJ06N46uQ

Update: I just published a companion blog post as well. If you want to grab the exact expressions and JSON examples to copy-paste into your own setup: Dynamic Credential Management in n8n: One Workflow, Many Clients | Five

1 Like

This is a well-thought-out pattern - passing credentials via webhook payload and using HTTP Request nodes instead of native nodes is exactly the right call for multi-tenant setups where static credentials don’t work. The absolute referencing tip ($(‘Webhook’).item.json.body.openAiKey) is something a lot of people miss and it’s what kills their workflows when adding Merge or Wait nodes mid-flow. One thing worth noting: make sure your webhook endpoint is HTTPS-only and that you’re validating the payload signature or using a short-lived token per request, since credentials in transit via webhook body are a risk vector if not secured properly.

1 Like

that is a crucial caveat for anyone building this. Because the credentials live in the payload, the transport layer has to be bulletproof. In my setups, HTTPS is obviously mandatory, but I also lock down the n8n Webhook node using its built-in Header Authentication so it explicitly rejects any requests that don’t originate directly from my backend server. Pairing that with short-lived access tokens (like Google’s 1-hour OAuth tokens) keeps the blast radius incredibly small compared to passing around permanent, non-expiring API keys.

Curious how you usually handle the payload validation side for your setups—do you prefer generating HMAC signatures for your webhooks, or do you mostly stick to strict header/bearer auth between your app and n8n?