Describe, Deploy, Own
Build automations using natural language or our visual canvas. We generate raw TypeScript and deploy it directly to your Cloudflare account.
From canvas to cloud in minutes.
Describe it, or draw it. Attach Credentials. Deploy.
// Generated by N2C Compiler
// Workflow: "Refund approval" (ID: "refund_approval")
// Target: Cloudflare Workflow
import { WorkflowEntrypoint } from "cloudflare:workers";
export class N2CWorkflow extends WorkflowEntrypoint {
async run(event, step) {
const env = this.env;
const ctx = {
input: event.payload,
output: {},
nodes: {},
env,
execution: {
id: crypto.randomUUID(),
startedAt: new Date().toISOString(),
durationMs: 0,
retryCount: 0,
currentStep: "",
debugMode: false,
traceId: crypto.randomUUID(),
logger: { log: (msg) => console.log(msg), error: (msg) => console.error(msg) },
},
workflow: {
id: "refund_approval",
name: "Refund approval",
version: 1,
triggerType: "webhook",
settings: {},
},
node: {},
};
// === Node: hook (webhook_trigger) ===
ctx.nodes["hook"] = ctx.input;
// === Node: summarize (openai) ===
ctx.nodes["summarize"] = await step.do("summarize", async () => {
const summarize_resRes = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": "Bearer " + env.CREDENTIAL_SUMMARIZE,
"Content-Type": "application/json"
},
body: JSON.stringify({ model: "gpt-4o-mini", messages: [{ role: "user", content: `One line: why is ${ctx.input?.body?.customer} disputing?` }] }),
});
if (!summarize_resRes.ok) {
throw new Error("OpenAI API error " + summarize_resRes.status + ": " + (await summarize_resRes.text()));
}
const summarize_res = await summarize_resRes.json();
return summarize_res;
});
// === Node: ask (slack) ===
ctx.nodes["ask"] = await step.do("ask", async () => {
const ask_resRes = await fetch("https://slack.com/api/chat.postMessage", {
method: "POST",
headers: {
"Authorization": "Bearer " + env.CREDENTIAL_ASK,
"Content-Type": "application/json"
},
body: JSON.stringify({ channel: "#billing", text: `Approve refund? ${ctx.input?.summarize?.text}` }),
});
if (!ask_resRes.ok) {
throw new Error("Slack API error " + ask_resRes.status + ": " + (await ask_resRes.text()));
}
const ask_res = await ask_resRes.json();
return ask_res;
});
// === Node: approval (wait_for_event) ===
ctx.nodes["approval"] = { ...(await step.waitForEvent("approval", { type: "refund_decision", timeout: "24 hours" })), timedOut: false };
// === Node: gate (if) ===
if (ctx.input?.approval?.approved) {
// === Node: refund (http_request) ===
ctx.nodes["refund"] = await step.do("refund", async () => {
const refund_res = await fetch("https://api.stripe.com/v1/refunds", { method: "POST", signal: AbortSignal.timeout(30000) });
return { status: refund_res.status, body: await refund_res.text() };
});
} else {
}
}
}
export default {
async fetch(request, env, executionCtx) {
if (["/favicon.ico","/robots.txt","/apple-touch-icon.png"].includes(new URL(request.url).pathname)) {
return new Response(null, { status: 404 });
}
if (request.method === "POST" && new URL(request.url).pathname === "/__resume/approval") {
{
const legacyProvided = request.headers.get("X-CC-Webhook-Secret") ?? "";
const expected = env.CC_RESUME_SECRET_APPROVAL ?? "";
const legacyMaxLen = Math.max(legacyProvided.length, expected.length);
let legacyMismatch = legacyProvided.length === expected.length ? 0 : 1;
for (let i = 0; i < legacyMaxLen; i++) {
legacyMismatch |= (legacyProvided.charCodeAt(i) || 0) ^ (expected.charCodeAt(i) || 0);
}
let authOk = legacyMismatch === 0 && expected !== "";
const signatureHeader = request.headers.get("X-CC-Signature");
if (!authOk && signatureHeader && expected !== "") {
const sigMatch = /^t=(\d+),v1=([0-9a-f]+)$/.exec(signatureHeader);
if (sigMatch) {
const signedTimestamp = Number(sigMatch[1]);
const ageSeconds = Math.abs(Date.now() / 1000 - signedTimestamp);
if (Number.isFinite(ageSeconds) && ageSeconds <= 300) {
const rawBody = await request.clone().text();
const hmacKey = await crypto.subtle.importKey("raw", new TextEncoder().encode(expected), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
const signatureBytes = await crypto.subtle.sign("HMAC", hmacKey, new TextEncoder().encode(`${signedTimestamp}.${rawBody}`));
const computedSignature = [...new Uint8Array(signatureBytes)].map((b) => b.toString(16).padStart(2, "0")).join("");
const providedSignature = sigMatch[2];
const sigMaxLen = Math.max(providedSignature.length, computedSignature.length);
let sigMismatch = providedSignature.length === computedSignature.length ? 0 : 1;
for (let i = 0; i < sigMaxLen; i++) {
sigMismatch |= (providedSignature.charCodeAt(i) || 0) ^ (computedSignature.charCodeAt(i) || 0);
}
authOk = sigMismatch === 0;
}
}
}
if (!authOk) {
return new Response("Unauthorized", { status: 401 });
}
}
const resumeBody = await request.json().catch(() => ({}));
const instanceId = typeof resumeBody.instanceId === "string" ? resumeBody.instanceId : "";
if (instanceId === "") {
return Response.json({ error: "instanceId is required" }, { status: 400 });
}
let instance;
try {
instance = await env.WORKFLOW.get(instanceId);
} catch {
return Response.json({ error: "Unknown workflow instance" }, { status: 404 });
}
try {
await instance.sendEvent({ type: "refund_decision", payload: resumeBody.payload });
} catch (resumeErr) {
return Response.json({ error: String((resumeErr && resumeErr.message) || resumeErr) }, { status: 409 });
}
return Response.json({ ok: true, instanceId, type: "refund_decision" });
}
if (request.method !== "POST") {
return new Response("Method Not Allowed", { status: 405 });
}
{
const legacyProvided = request.headers.get("X-CC-Webhook-Secret") ?? "";
const expected = env.CC_WEBHOOK_SECRET_HOOK ?? "";
const legacyMaxLen = Math.max(legacyProvided.length, expected.length);
let legacyMismatch = legacyProvided.length === expected.length ? 0 : 1;
for (let i = 0; i < legacyMaxLen; i++) {
legacyMismatch |= (legacyProvided.charCodeAt(i) || 0) ^ (expected.charCodeAt(i) || 0);
}
let authOk = legacyMismatch === 0 && expected !== "";
const signatureHeader = request.headers.get("X-CC-Signature");
if (!authOk && signatureHeader && expected !== "") {
const sigMatch = /^t=(\d+),v1=([0-9a-f]+)$/.exec(signatureHeader);
if (sigMatch) {
const signedTimestamp = Number(sigMatch[1]);
const ageSeconds = Math.abs(Date.now() / 1000 - signedTimestamp);
if (Number.isFinite(ageSeconds) && ageSeconds <= 300) {
const rawBody = await request.clone().text();
const hmacKey = await crypto.subtle.importKey("raw", new TextEncoder().encode(expected), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
const signatureBytes = await crypto.subtle.sign("HMAC", hmacKey, new TextEncoder().encode(`${signedTimestamp}.${rawBody}`));
const computedSignature = [...new Uint8Array(signatureBytes)].map((b) => b.toString(16).padStart(2, "0")).join("");
const providedSignature = sigMatch[2];
const sigMaxLen = Math.max(providedSignature.length, computedSignature.length);
let sigMismatch = providedSignature.length === computedSignature.length ? 0 : 1;
for (let i = 0; i < sigMaxLen; i++) {
sigMismatch |= (providedSignature.charCodeAt(i) || 0) ^ (computedSignature.charCodeAt(i) || 0);
}
authOk = sigMismatch === 0;
}
}
}
if (!authOk) {
return new Response("Unauthorized", { status: 401 });
}
}
const payload = await request.json().catch(() => ({}));
const instance = await env.WORKFLOW.create({ params: payload });
return Response.json({ instanceId: instance.id, status: "started" });
},
};
Cloudflare runs 330+ cities. You pick none of them.
- Compiles to
- Cloudflare Workflow
- Bundle
- 8.3 KB gzipped
- Worker secrets
- 4 injected at deploy
- Per-run fee to us
- $0.00
Yes, it connects to that.
Connect to 100+ tools out of the box, or use native HTTP nodes to bypass our roadmap and connect to anything else.
Pre-wired for the modern stack.
Infrastructure-grade workflows.
Our analyzer reads your graph's shape and automatically assigns the correct Cloudflare compile target under the hood.
Fast. Light. your edge.
We deploy raw V8 isolates directly to your Cloudflare account. Workflows are distributed globally via Anycast. Executed with 0ms cold starts and zero idle costs.
- Runtime
- V8 isolates
- Cold start
- 0 ms
- Idle cost
- $0.00
- Region to pick
- none
We don't charge per run, because we don't run it.
Everyone else sells you a canvas and rents you their interpreter. This difference is not a pricing decision we could reverse next quarter — it falls out of where the code executes.
It runs in your account
The Worker executes in your Cloudflare tenant, billed by Cloudflare to you. Your payloads never reach our servers.
One disclosed exception: while a live debug session is open in the editor, your Worker streams its console output through us so we can show it to you. Nothing from that stream is written to disk or to a database, and it ends when you close the panel.
Credentials, sealed
Encrypted in our custody with AES-GCM, decrypted only at deploy, and injected straight into your Worker's secrets.
Zapier · Make · Workato
- Metered per run — the bill grows with your traffic
- Your payloads flow through their servers
- Delete the account and every workflow stops
- Cost scales with volume, forever
Nodes2Cloud
- 100,000 requests a day free. $5/mo covers 10 million.
- Payloads never reach us — the Worker runs in your account
- Delete the account and every workflow keeps running
- $18/mo flat, whatever your volume does
Leave whenever
Export the TypeScript and the wrangler config any time. Delete us tomorrow and every deployed workflow keeps running.
$0.00
Forever. We don't host the runtime, so there is no meter to run.
Build Once. The rest is Cloudflare's, on your bill.
Our figure never moves. Cloudflare's grows with the steps your workflow actually spends, at their published rate — and the total still lands two orders of magnitude under a per-task platform at any volume worth the name.
Edge compute reduces cost significantly
86×
less, at this volume — $2,330 (est.) a month against $27.
- Nodes2Cloud Pro
- $18flat and just once
- Cloudflare requests
- $5their bill, your account
- Cloudflare steps · 4/run
- $4not yet billed
Flat, and unrelated to volume.
You pay us for the builder. You pay Cloudflare for the compute, at their published rate, on your own bill.
Developer
Free forever$0
For side projects and trying the canvas out.
- Up to 3 workflows
- Runs on Cloudflare's free tier
- Full project export, any time
- All 162 nodes
Pro
Most popular$18
per month, or $180 a year
For production workflows you depend on.
- Unlimited workflows
- 1-click n8n import
- Encrypted credential management
- 500 AI credits and MCP support
- GitHub auto-push and drift detection
Team
WaitlistLet's talk
For one contract, one invoice, one person to call.
- Everything in Pro
- Shared credentials
- Real-time multiplayer editing
- Audit log
The questions you were going to ask anyway.
Including the two we would rather you did not, which is why they are here.
You need a Cloudflare account — that part is not optional, because the Worker runs in it. You do not need to know Workers. You connect the account once, and we create the databases, buckets and queues each workflow needs and push your API keys in as secrets. If you have never opened a terminal, this still works; if you have, you will recognise everything it made.
No. Cloudflare's free plan covers 100,000 Worker requests a day, D1 databases and Workflows, and the editor tells you how many runs a day your specific graph affords on it before you deploy. You move to a paid Cloudflare plan when your volume says so, and you pay Cloudflare directly — we never sit between you and that bill.
162 nodes ship today, 104of them talking to a third-party API, and the wall further up this page names every company we talk to rather than rounding it into a number — look for yours before you sign up. If it is not there, the HTTP Request node authenticates against any REST API and the Code node runs your own JavaScript, so a missing node is a bit more typing rather than a wall.
There is an importer in the editor for 108node types, and it gives you a per-node coverage report before anything is created. Anything it cannot map arrives as an explicit “unsupported” placeholder rather than quietly swapped for something that looks similar, so the gaps are yours to decide about. Credentials are dropped on the way in by design — we read their names, never their values — so you reconnect each account once here.
The Worker runs in your account, so the payloads it processes do not pass through us. Your API keys are encrypted under a key derived per user, with the master key held in Cloudflare's secret store rather than our database.
One disclosed exception: when you open the live debug panel in the editor, your Worker streams its per-node console output through one of our Workers so we can display it. Nothing from that stream is written to a database or to disk, it only exists while the panel is open, and it is a paid-plan feature you switch on deliberately. We would rather write that down than let you find it in a network tab.
Your deployed Workers keep running, because they are in your account and there is nothing of ours in them. What you lose is the editor. Export the project first and you have a normal repo — source, config, README, and the graph as JSON — that deploys from your own CI. Windmill's git-backed export is genuinely better than ours; ours is a compiled artifact rather than round-trippable source files.
Worth knowing before you commit: schedules run in UTC (the timezone picker is captured but does not do anything yet), there is no marketplace, and .xlsx and .zip file handling is deferred. We publish no uptime SLA and hold no compliance certification — if you need one contractually, we are not the right choice yet, and we would rather say so here than in a procurement call.
Not today, and we are not going to imply otherwise. Releasing the compiler, node library and shared packages under Apache-2.0 is a plan with real prerequisites still outstanding, not a thing you can go and clone. When it ships we will say so loudly; until then it is not a reason to choose us.
Take back ownership of your automations.
Your first workflow runs in your Cloudflare account, on your bill — and keeps running whatever happens to us.
No credit card · export the project whenever you like