DEV Community

Khaja Hussain
Khaja Hussain

Posted on

A free, high-performance backend for small projects — Cloudflare Workers in 10 minutes

Every developer wants the same thing: a free way to run small backend tasks. A webhook for a side project. A contact form handler. An OG image generator. A small proxy to hide an API key. Things that handle a few hundred requests a week. They do not need a $20/month server, an Azure Function App you will forget about next month, or a new Lambda that takes an hour to set up.

I have had this problem for years. Most of my work is in .NET shops — AWS for production, Azure for clients on the Microsoft stack. Both are great for real workloads. Both feel like overkill when the actual task is "send me an email when a Stripe customer pays."

Last weekend I needed exactly that webhook. Spinning up a new Function App for one endpoint felt silly. Adding it to an existing production API would mix two unrelated things — the next person reading the code would wonder why a billing handler was sitting inside the customer-facing app. Every option I knew was way too much setup for one tiny task.

So I finally tried something I had been avoiding for three years: Cloudflare Workers. It solved the problem in ten minutes. Free, no credit card, deployed to 300+ data centres. Here is how it went, and why I should have tried it sooner.

Why I had been ignoring Cloudflare Workers

Three reasons:

  1. No .NET story. I work in C#. Writing JavaScript at the edge felt like a whole new world just to deploy one small function.
  2. The marketing made it look like big-company infra. Every Workers post I had read mentioned Durable Objects, R2, KV, Queues, AI bindings. It looked like a big platform you commit to, not a tool you grab for one task.
  3. I thought the free tier was a trick. "100,000 requests per day" sounded like a number with hidden conditions and a credit-card requirement.

None of those were true.

The 10 minutes

Install Wrangler, the Cloudflare CLI:

npm install -g wrangler
wrangler login
Enter fullscreen mode Exit fullscreen mode

The login opens a browser. Click approve and you are done. No credit card asked. I checked twice — I have been burned before.

Scaffold a project:

npm create cloudflare@latest stripe-webhook
cd stripe-webhook
Enter fullscreen mode Exit fullscreen mode

Pick "Hello World" Worker, JavaScript, skip the deploy step. The generator puts everything in one folder. Open src/index.js and replace the default code with this:

export default {
  async fetch(request, env) {
    if (request.method !== 'POST') {
      return new Response('POST only', { status: 405 });
    }

    let event;
    try {
      event = await request.json();
    } catch {
      return new Response('Invalid JSON', { status: 400 });
    }

    if (event.type === 'checkout.session.completed') {
      const amount = event.data.object.amount_total / 100;
      const email = event.data.object.customer_email;

      await fetch('https://api.resend.com/emails', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${env.RESEND_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          from: 'alerts@mydomain.com',
          to: 'me@mydomain.com',
          subject: `New payment: $${amount}`,
          text: `${email} just paid $${amount}.`,
        }),
      });
    }

    return new Response('ok', { status: 200 });
  },
};
Enter fullscreen mode Exit fullscreen mode

Run it locally:

npm run dev
# Ready on http://localhost:8787
Enter fullscreen mode Exit fullscreen mode

Send a fake Stripe payload with curl. Worked on the first try.

Deploy it:

npx wrangler deploy
Enter fullscreen mode Exit fullscreen mode

Eight seconds later it printed a *.workers.dev URL. Total bundle size: 1.2 KB. I pointed Stripe's webhook config at that URL, sent a test event from the Stripe dashboard, and the email arrived in my inbox before the dashboard finished refreshing.

The part that surprised me

The free tier really is 100,000 requests a day. No credit card. The Worker runs in 300+ Cloudflare data centres at the same time. A Stripe webhook from San Francisco hits the San Jose data centre, my email goes out from Resend's nearest server, and the whole round trip takes under 200ms. No cold start. No Function App I have to remember to delete next month before it starts billing me.

If you store a webhook secret with wrangler secret put STRIPE_WEBHOOK_SECRET and verify the signature (you should — Stripe sends a Stripe-Signature header for this), the whole thing is still about 60 lines of code.

One gotcha worth knowing

If the request body is invalid JSON, request.json() throws a SyntaxError and Cloudflare returns a generic 500. That is bad for any user-facing endpoint, and it is worse for Stripe — Stripe retries on 5xx errors, so a broken parser quietly turns into duplicate retries while your dashboard looks fine. Wrap the parse in a try/catch and return a 400 with a clear message. Stripe webhook payloads are also big and hard to read; pretty-printing them with a JSON formatter makes debugging much faster. I learned the try/catch lesson the hard way — one panicked late-night Stripe dashboard refresh.

What I would do differently next time

Use Workers first for any small task that needs an HTTP endpoint. That is a much bigger category than just "webhook" — link shorteners, OG image generators, form handlers, edge auth, JSON proxies, internal Slack bots. Trying it is one npm create command away.

I feel a bit silly for waiting three years. The mental model is one sentence: a Worker is a function that takes a Request and returns a Response. That is the whole platform. Everything else (KV, Durable Objects, AI bindings) is optional. If you want the full step-by-step instead of the highlights here, this Cloudflare Workers deploy guide covers the whole setup with a real validating endpoint, the free-tier limits in detail, and a few more gotchas to know before you ship.

If you have been avoiding Workers like I was, try it on the next small task you hit. Fifteen minutes will tell you if it fits how you think. For me, it did.

Top comments (0)