DEV Community

Ronald Oloo
Ronald Oloo

Posted on

How to stop OpenAI API credit draining using Client-Side Proof of Work (Node + React)

I woke up last week to a $500 OpenAI bill.

My new SaaS wrapper had 0 active users, but 100,000 requests.

I checked the logs. It was a botnet spamming my signup form to test stolen credit cards and generate free text.

I tried adding Google reCAPTCHA v3, but my signup conversion rate dropped by 20%. Users hated clicking traffic lights, and the "Invisible" score was blocking legitimate users on VPNs.

I needed a way to verify requests were human without annoying them.

The Solution: Economic Deterrence

I realized I didn't need to check if the user was human. I just needed to make it expensive for them to spam me.

I built a system based on Proof of Work (PoW), similar to Hashcash (Bitcoin).

How it works

  1. Handshake: Before submitting a form, the client asks the server for a challenge.
  2. The Work: The browser must solve a cryptographic puzzle (Argon2 hash) that takes about 1–2 seconds of CPU time.
  3. Verification: The client sends the solution. The server verifies it instantly (~0ms).

For a human, 2 seconds is invisible background work.

For a bot trying to send 1 million requests? That costs thousands of dollars in electricity and CPU time. The attack becomes economically unviable.

The Implementation

I open-sourced the SDK I built for this. It's called IronWall.

Here is how you can add it to your React / Next.js app in 3 minutes.

1. Install the SDK

It's only 3KB gzipped (vs 200KB+ for reCAPTCHA).

npm install ironwall-sdk
Enter fullscreen mode Exit fullscreen mode

2. Configure it

Add this to your _app.tsx or main entry file.

import { IronWall } from 'ironwall-sdk';

IronWall.configure({
  apiKey: 'YOUR_PUBLIC_KEY', // Get this for free from ironwall-protocol.xyz
});
Enter fullscreen mode Exit fullscreen mode

3. Protect your API Call

Wrap your sensitive API call (Login, Register, Generate) with the guard.

const handleSubmit = async (e) => {
  e.preventDefault();

  try {
    // This pauses execution until the puzzle is solved
    // The browser calculates the hash in a Web Worker (doesn't freeze the UI)
    await IronWall.guard();

    // If we get here, the user paid the CPU tax.
    // Proceed to your backend.
    await axios.post('/api/generate', data);

  } catch (error) {
    alert("Bot detected or user cancelled");
  }
};
Enter fullscreen mode Exit fullscreen mode

Why this is better for AI apps

If you are paying per token (OpenAI / Anthropic), volume is your enemy.

  • Traditional IP-based rate limiting fails because bots rotate IPs using proxies.
  • Proof of Work makes the device pay. Proxies don't help because the CPU cost is local.

I've packaged the backend logic into a hosted service to handle verification and replay-attack protection (using Redis atomic locks).

You can grab a free key here:

https://ironwall-protocol.xyz

It stops the bill shock — and your users never see a puzzle.

Let me know if you have questions about the Argon2 parameters.

Top comments (0)