You’ve built something useful in n8n: it could be a document summariser, a lead qualifier, a content classifier, an invoice extractor, whatever. It works great.
Now a few people have asked if they can pay to use it, and you’re staring at the gap between “a working n8n workflow” and “an API with customers billed every month.”
The DIY version is possible but the process is pretty miserable. A keys table. A Stripe webhook handler. Quota logic hand-rolled in a Function node. Rate limiting you have little control over. A login form so customers can manage their own keys, glued to the rest with whatever you have time for.
In the end, your n8n workflow is 9 nodes but the billing pipeline around it is bigger than the workflow itself.
Zuplo sits in front of n8n and does this layer for you. You keep the workflow and get keys, quotas, metering, and Stripe implemented without writing any of the infrastructure.
- You have a working n8n AI workflow you want to sell access to
- You don't want to build a billing pipeline from scratch
- You need tiered plans (free / pro / enterprise) with real quota enforcement
Our example n8n workflow
The post uses a document summariser as a running example, but the pattern is the same for any workflow you want to charge for: a webhook in, some processing in the middle, structured JSON out. Swap nodes to match what your workflow actually does.
End-to-end the workflow runs like this. The Webhook node receives a document URL. Anthropic’s Analyze Document node fetches and summarises the file in one step. A Supabase node stores the result. An Edit Fields node shapes the response. A final Respond to Webhook node returns JSON with the summary and the row id. Five nodes, no glue code.

Pro tip:
This synchronous request/response shape works fine for workflows that finish
in seconds. For workflows that take longer than ~30 seconds, you’ll start
hitting timeouts somewhere between the caller and n8n. The fix is async
polling: return a job_id immediately and have the caller poll for
completion. The Zuplo and n8n wiring stays identical; only the workflow shape
and the response convention change.
Ultimately, the workflow is what you’re “selling”. n8n makes it easy to build, run, and iterate on, but it doesn’t turn the workflow into a product: no managed API keys, no quota enforcement, no Stripe integration, no customer portal. The Webhook node accepts anyone who sends an HTTP request. That’s where Zuplo comes in.
What Zuplo adds
Five things, all configured once in the Zuplo portal:
- Monetization: validates each caller’s API key, identifies them, then meters successful requests against a Stripe subscription plan that enforces quota and bills usage. Key validation comes built in, so you don’t add a separate API Key policy.
- Per-consumer rate limiting: each customer gets their own limit window, automatically tracked by their key.
- Secret masking (outbound): redacts credentials if your workflow ever echoes something sensitive in a response.
- Prompt injection detection (outbound): scans responses for injected instructions before they reach whoever called your API (especially relevant if the caller is another AI agent).
- Schema validation (optional): drop an OpenAPI validator if you want malformed payloads rejected before they reach n8n. The walkthrough below skips this, but it’s a one-policy add if you have an OpenAPI spec for your route.
You configure all of these by clicking policies onto a route in the Zuplo portal, a drag-and-drop policy chain editor. The portal saves every change and deploys it for you. Power users can edit the underlying JSON directly, and optionally wire the project up to a Git repo for version control. Pretty much everything you can do in code in Zuplo, you can also do through the UI, so pick whichever path fits how you like to build.
Here’s how a request flows once those policies are in place:

Inbound policies (key validation, rate limiting, metering) run before the request reaches n8n. Outbound policies (secret masking, prompt injection checks) run on the response on the way back to the caller. n8n stays focused on the actual work: fetch, summarise, store. Everything that turns it into a paid API lives at the gateway.
What you’ll click
Five steps, all in the Zuplo portal:
- Sign up at Zuplo and create a project. Point it at your n8n webhook URL as the upstream.
- Add the inbound policies. From the policy library, drop Rate Limit and Monetization onto your route. Monetization handles API key validation as part of its job, so you don’t need a separate auth policy.
- Add the outbound policies. Drop Secret Masking and Prompt Injection Detection on the outbound side of the same route.
- Connect Stripe. In Settings → Payment Providers, paste your Stripe secret key.
- Create your plans. In Monetization, define Free / Pro / Enterprise tiers with quotas and prices. Zuplo creates the matching Stripe products for you.
That’s the whole shape of it. The rest of the post is what those clicks produce, in case you ever want to read or edit the underlying config.

Monetization Quickstart
The complete Zuplo walkthrough for setting up monetization, Stripe, and plans.
How each policy works
A quick read of the four policies you just added:
- Rate limit: caps each customer at 60 requests per minute. Tracked per key, not per IP.
- Monetization: validates the API key on the way in (no separate auth policy
needed), then increments a meter called
api_requestsby 1 for every successful request. Errors and retries don’t burn the customer’s quota. - Secret masking: scrubs any credentials that accidentally end up in a response.
- Prompt injection detection: scans the response for injected instructions.
Default flags and logs. Set
strict: trueto block flagged responses outright.
Two things to set in the portal’s environment variables before any of this runs:
N8N_WEBHOOK_URL (the full URL of your n8n webhook) and OPENAI_API_KEY (used
by the prompt injection check).
One choice on the route is worth flagging: in the Request Handler, pick URL
Rewrite, not URL Forward. Forward appends the inbound path onto a base URL,
which is wrong for n8n: webhook URLs are complete endpoints, so the request
would land at /webhook/abc/v1/summarizer and 404. URL Rewrite uses a fixed
pattern, so the request goes to exactly the n8n endpoint you’ve set in
N8N_WEBHOOK_URL.
Common mistake:
Leaving the n8n webhook URL publicly reachable after you put Zuplo in front of it. A caller who finds the raw webhook can bypass the gateway entirely, which means no key, no quota, no billing. The fix is a shared secret header that Zuplo attaches on every forward and n8n requires on every webhook hit. The “Telling n8n who the caller is” section below shows the pattern. Or put n8n on a private network that only Zuplo can reach.
Telling n8n who the caller is
The policies above will get you authenticated, rate-limited, metered API calls. But they leave one thing on the table: the n8n workflow doesn’t know who the caller is. That’s fine for the processing step itself, but the moment you want to store results per customer (so each customer only sees their own data), n8n needs the consumer id.
Once the monetization policy validates a key, it attaches the subscription data
to the request context, including the customer id. You read it with
MonetizationInboundPolicy.getSubscriptionData(context) and forward it to n8n
on every request, alongside a shared secret that prevents anyone from calling
the n8n webhook directly.
Three steps in the Zuplo portal:
-
Write a small module that reads subscription data and sets headers. This is the one piece of code you write yourself. Create
modules/set-consumer-headers.tsin the portal’s file editor.
The full module:
typescript -
Drop a Custom Code Inbound policy onto the route and point it at the module. From the policy library, find Custom Code Inbound, drag it onto the route’s inbound chain, and set the module path to your new file.

The portal commits this to
config/policies.json:json -
Slot it at the bottom of the inbound chain. The custom policy reads subscription data from the context, which only exists once monetization has run, so as long as it sits below monetization the order works. Putting it last keeps the chain easy to scan.

The chain ends up as:
json
One more environment variable to add in the portal: GATEWAY_SECRET. Generate
any random string and set it in Settings → Environment Variables. n8n will
require the same value on every webhook hit, so the two sides match.
Then in n8n, require both headers in the Webhook node and use them in the workflow:
x-gateway-secretis checked by the Webhook node itself. Set Authentication to Header Auth and create a Header Auth credential with namex-gateway-secretand value matchingGATEWAY_SECRETin the Zuplo portal. n8n will reject any call with a missing or wrong value before the workflow runs, which closes the “raw webhook URL is reachable” loophole the earlier callout warned about.x-consumer-idis the value your workflow uses to scope data per customer. Pass it as thecustomer_id(oruser_id, or whatever you call it) on every write, and as the filter on every read. The example uses Supabase, but the same applies to Postgres, Airtable, Notion, or any storage node n8n connects to.

The response back to Zuplo is built in two steps. First, an Edit Fields
(Set) node assembles the fields the caller will receive: id, summary,
source_url, customer_id, and created_at. Then a Respond to Webhook
node at the end of the workflow sends them out.
There’s a subtle gotcha here worth knowing about. The Respond to Webhook node has a JSON body field where you can paste a template with expression placeholders, but n8n treats it as a string template. If your summary contains line breaks or quotes (and most LLM output does), the result is invalid JSON and the customer gets a broken response.
The Edit Fields node sidesteps this entirely. It builds a real object, field by field, and n8n serialises it correctly when it leaves the workflow.
To wire it up, set the entry Webhook node’s Respond option to Using ‘Respond to Webhook’ Node, then set the Respond to Webhook node’s Respond With option to First Incoming Item. Edit Fields hands its object straight through, and the caller gets clean JSON every time.

Where Stripe fits in
The monetization policy you added defines the meter. The bit that turns a meter into money is the plan configuration, which you set up in the Zuplo portal’s Monetization section. Connect a Stripe secret key once in the portal’s Stripe integration settings, then create plans in Zuplo that map onto Stripe products and prices for you. Example plan set:
- Free: 100
api_requestsper day, $0/month flat, hard quota. - Pro: 10,000
api_requestsper day, $49/month flat, soft quota with $0.005 per extra request billed as Stripe metered usage. - Enterprise: custom quota, negotiated price, optionally a private plan that isn’t visible on the public pricing page.
Zuplo supports both flat subscriptions with hard quotas and tiered usage-based billing where overage requests bill through Stripe’s metered pricing. You pick the shape per plan.
Customers subscribe through Zuplo’s managed developer portal, which is a built-in product. Wire in an auth provider like Auth0 or Clerk for signup, and the portal handles the pricing page, plan selection, Stripe Checkout, and subscription management out of the box.
Once a customer subscribes, Zuplo creates an API key for them and links the subscription to that key. From here on, every request they make carries that key.
On each request, the monetization policy looks up their plan and current usage, then either allows the call or rejects it with a quota error. Successful calls increment the meter, and Stripe reads the meter at invoice time.
At no point does your n8n workflow talk to Stripe. It knows the consumer id (so it can scope data per customer), but by default it doesn’t know which plan they’re on, doesn’t know how close to their quota they are, and never sees the API key. Unless you want it to: all the data is available for you to pass over to n8n if you require it for some aspect of your workflow. Get creative! Plans, quotas, and billing stay on Zuplo’s side; n8n just runs the workflow.
Zuplo Developer Portal
How to wire up signup, pricing, plan selection, and Stripe Checkout for your customers.
Your n8n workflow is now a paid API
Your n8n workflow now has paying customers. They sign up through a dev portal,
pick a plan, get an API key, and start making calls. Their usage is metered per
request, quotas are enforced at the edge, billing happens on Stripe’s normal
invoice cycle, errors don’t burn their quota, secrets in the response get
scrubbed on the way out, and injection attempts get flagged (or blocked, if you
set strict: true). The workflow itself didn’t change.
What you’ve actually built is a pattern, not a product. The five Zuplo policies, the small custom policy that forwards the consumer id, the Edit Fields → Respond to Webhook response shape: all of these apply to any n8n workflow you want to charge for. Swap the Anthropic node for whatever your workflow does. Swap Supabase for whatever you store in. The boundary between Zuplo and n8n stays the same.
If you have a workflow you’ve been meaning to charge for, spin up a free Zuplo project, point it at your n8n webhook URL, and walk the five steps. The free tier is enough to build and test all of this end-to-end; you’ll need a paid plan when you’re ready to take real money from real customers.
Monetization Policy Reference
Full reference for meters, meterOnStatusCodes, plan setup, and the Stripe integration.
