blog · 10 September 2026 · 8 minutes
Five layers of protection for payments and AI requests: a client case study
Ask an assistant to "build payment processing" and you get code that works. Click the button, pay, get access. Tests pass, the demo looks convincing to the client. Then someone opens devtools and pays a dollar instead of a hundred. Here is what an AI assistant leaves broken in payment handling by default, on a real client project, and what we build around payments and AI requests so it does not happen.
Hole one: the price comes from the client
Here is what gets generated by default if you simply ask for "payment processing":
// do not do this
app.post('/checkout', async (req, res) => {
const { productId, amount } = req.body; // amount came from the browser
const session = await psp.createSession({ amount, currency: 'usd' });
res.json({ url: session.url });
});
It looks logical enough: the frontend knows the price, so it sends it.
But the frontend runs on the buyer's machine, and it takes one line in the
browser console to edit. amount: 10000 becomes
amount: 100, the payment provider happily charges a dollar, and
the product ships.
The fix: the client sends only the id of what it is buying, and the server looks up the price itself.
app.post('/checkout', requireAuth, async (req, res) => {
const product = await products.get(req.body.product_id);
if (!product) return res.sendStatus(404);
// record the order before calling the PSP: we reconcile the webhook against it later
const order = await orders.create({
user_id: req.user.id,
product_id: product.id,
amount_cents: product.price_cents, // price comes from the database only
currency: product.currency,
status: 'pending',
});
const session = await psp.createSession({
amount: order.amount_cents,
currency: order.currency,
metadata: { order_id: order.id },
});
res.json({ url: session.url });
});
Three lines of difference. But as long as the amount comes from
req.body, any checks further down the code are pointless.
Hole two: the "thank you" page confirms the payment
The second common pattern: the user returns from the payment provider to
/success, and access is granted right there. This breaks both
ways. Close the tab right after the charge and the money is gone but access
never gets granted, and you never find out. Or open /success
directly, skip the payment entirely, and get access for free.
The only source of truth for a payment is the provider's webhook. And receiving it is not enough — you have to verify it: signature, timestamp, idempotency, and the order amount. By default an assistant does, at best, the first item — signature verification. The rest has to be asked for explicitly, one item at a time. Neither hole gets caught by ordinary testing: tests check "paid → got access" and "did not pay → no access", and an attacker is interested in the third path nobody on the review side thought to check.
How we catch these before the client does
On one project with a payment integration, the code was written, tested, and formally worked: money came in, the product shipped. That was enough to hand the project off. We ran the attacker's scenario ourselves instead: a sharp user forges a response from the payment gateway and the server believes it, the product ships for free. Dig deeper, and there is a way to top up a balance without any charge going through, then reach other people's accounts.
The process we now build into projects like this runs in three steps. First, we go through the integration docs and the common vulnerabilities in plain conversation, no code yet — it is easier to keep track of what to look for that way. Then we hand the finished code to an AI agent for a first audit: it checks the logic against current API documentation, and we go through every item in the report and close what it found. Last is a stress test in a clean session, with no memory of earlier chats: the agents get one blunt task — break the code, bypass the payment. On this project they found a gap the first audit had missed.
The clean session matters. An agent that knows "how it was meant to work" defends the design and explains why it is fine. An agent that only sees the code looks for a way to fool it — a completely different role.
Five layers of protection around payments and AI requests
Full protection does not exist. The goal is different: make an attack cost more than whatever it could gain. On the project where we are currently building this out, it comes in five layers.
1. Log everything
Every request, every operation. Looks paranoid right up until the first incident review — without logs there is nothing to review.
2. Block suspicious IPs
Bots, spammers, odd request patterns get cut off automatically, without a manual review of every case.
3. Rate-limit spend per account
A sliding window on request count and on money spent, tracked separately — because 10 expensive AI calls hit the budget harder than 1,000 cheap ones.
4. Real-time alerts
Not "find out from the logs next week" — see the anomaly now, while it can still be stopped.
5. A kill switch
A script that cuts every outbound connection: no data, no API, and a notification lands with us. For the attack the other four layers did not anticipate.
The third layer — spend control — looks like this in practice:
// Count money as well as requests: 10 expensive calls
// hit the wallet harder than 1,000 cheap ones
async function guard(accountId, costCents) {
const WINDOW = 3600;
const now = Math.floor(Date.now() / 1000);
const key = `spend:${accountId}`;
await redis.zremrangebyscore(key, 0, now - WINDOW);
await redis.zadd(key, now, `${now}:${crypto.randomUUID()}:${costCents}`);
await redis.expire(key, WINDOW);
const entries = await redis.zrange(key, 0, -1);
const requests = entries.length;
const spent = entries.reduce((s, e) => s + Number(e.split(':')[2]), 0);
const limit = await limits.get(accountId);
if (requests > limit.requests_per_hour || spent > limit.cents_per_hour) {
await accounts.block(accountId, 'anomaly');
await alerts.send('account_blocked', { accountId, requests, spent });
return false;
}
return true;
}
This fires in two cases: either your client's own customer found a vulnerability, or a stranger is running requests against a paid AI API on someone else's account — they found an endpoint that proxies calls to the model past the interface and its limits, and is using it as a free ride on the model. Either way the response is the same: stop first, investigate after.
A related habit is backups. We take an infrastructure snapshot before any work starts on a server, not after an incident. The reason is simple: a small config change in production can take down a client's live site, and the only thing that turns hours of recovery into minutes is a snapshot taken in advance.
The honest part
AI agents do not replace a pentest. They do not see the full infrastructure, do not know the business context, and confidently say "no vulnerabilities" when they simply found none — every finding still needs a human check. It is a cheap first filter, not a security audit. And the five layers above do not make a product unbreakable. They close the specific places where mistakes happen most often — they do not guarantee the absence of others.
When you do not need this
- The project has no payments and no access to other people's data. A brochure site, a landing page with no account system, an internal tool with no money at stake — five layers of protection are overkill there; basic hygiene is enough.
- Turnover is still symbolic. If the product processes cents rather than thousands a month, it makes more sense to close the two essentials first — server-side amount checks and webhook verification — and build the rest up as volume grows, not all at once.
- There is no capacity to watch the monitoring. Alerts and a kill switch are useless if nobody is looking at them. In that case, a regular manual audit is a more honest arrangement than automation nobody follows up on.
What to ask a vendor about payment security
- Is the order amount checked on the server, or does it trust the frontend?
- How is the webhook verified: signature, timestamp, idempotency, amount reconciliation?
- Was an "attacker tries to bypass the payment" scenario run before handover?
- Is there logging of operations, and who sees an anomaly first?
- What happens on unusual activity: account block, alert, manual review?
- Was an infrastructure backup taken before any server work started?
Building a product with payments or paid AI features?
A 3–5 day discovery for $170 walks through the architecture and flags the risk before the main build starts. It counts toward the project. AI agents and protective mechanisms start from $1,400.
Telegram · @juzubiyyah