Detecting Business-Logic Abuse with the Spans Your APM Already Has

Cart cycling, refund fraud, free-tier abuse, coupon stacking — none of these match a WAF rule, but all show up in trace data if you know what to query.

Lhoussine
May 9, 2026·7 min read

Detecting Business-Logic Abuse with the Spans Your APM Already Has

Most security tools are built to detect attacks that look obviously bad — SQL injection, XSS, malicious user agents. Business-logic abuse is the opposite. The requests are syntactically perfect, the user agents are real browsers, the IPs are residential proxies indistinguishable from your customers. Only the patterns are wrong.

This post walks through how to catch the four most common business-logic abuse patterns using trace data you already have. For broader context on the APM-as-security approach, see APM + security in one tool.

The four patterns

Cart cycling. A bot adds items to cart, then removes them, then adds them again, repeatedly. The goal is usually inventory probing (does this size still exist?) or competitive scraping. Hundreds of POST /cart/add followed by POST /cart/remove from the same session over a few hours.

Refund fraud. A customer (real or stolen identity) places orders, receives them, then submits refund requests claiming "item didn't arrive." Repeated across multiple accounts that share device fingerprint, payment method, or shipping address fragments.

Free-tier abuse. Mass account creation to consume free-tier resources at scale. Each individual signup is legitimate; the aggregate is abusive. Often correlated by email patterns (user1@gmail.com, user2@gmail.com), IP ranges, or signup timing.

Coupon stacking. Repeated checkout attempts with different coupon codes, looking for codes that aren't yet applied or are stackable. POST /checkout with coupon=FOO, then coupon=BAR, then coupon=BAZ, etc.

All four show up cleanly in trace data. Here's how.

Detection rule 1: cart cycling

SELECT
  resource_attributes['enduser.id'] AS user_id,
  COUNT() AS total_cart_actions,
  countIf(span_attributes['http.target'] = '/cart/add') AS adds,
  countIf(span_attributes['http.target'] = '/cart/remove') AS removes
FROM otel_traces
WHERE
  span_attributes['http.target'] IN ('/cart/add', '/cart/remove') AND
  timestamp > now() - INTERVAL 1 HOUR
GROUP BY user_id
HAVING adds > 30 AND removes > 20 AND total_cart_actions > 100
ORDER BY total_cart_actions DESC;

Adjust thresholds based on your normal cart behavior. Most legitimate users have under 20 cart actions per session.

Detection rule 2: refund fraud signals

Refund fraud is harder because the attack window is long (days to weeks). The detection has two parts: per-account anomaly and cross-account correlation.

Per-account: flag accounts where the refund-to-order ratio exceeds 30% over a 30-day window.

SELECT
  resource_attributes['enduser.id'] AS user_id,
  countIf(span_attributes['http.target'] = '/orders' AND span_attributes['http.method'] = 'POST') AS orders,
  countIf(span_attributes['http.target'] LIKE '/orders/%/refund' AND span_attributes['http.method'] = 'POST') AS refunds
FROM otel_traces
WHERE timestamp > now() - INTERVAL 30 DAY
GROUP BY user_id
HAVING orders > 5 AND refunds / orders > 0.3
ORDER BY refunds DESC;

Cross-account: find accounts that share device fingerprints or payment methods. This requires custom span attributes for the fingerprint — typically you'd add them in the checkout flow.

Detection rule 3: free-tier abuse

Look for signup bursts from one IP, ASN, or email pattern.

-- Burst signups from one IP
SELECT
  resource_attributes['client.address'] AS ip,
  COUNT() AS signups,
  groupArray(span_attributes['enduser.email']) AS emails
FROM otel_traces
WHERE
  span_attributes['http.target'] = '/api/auth/signup' AND
  span_attributes['http.status_code'] = '200' AND
  timestamp > now() - INTERVAL 1 HOUR
GROUP BY ip
HAVING signups > 10
ORDER BY signups DESC;

The groupArray(emails) is the giveaway — patterns like user1@gmail.com, user2@gmail.com, user3+test@gmail.com from one IP are not real customers.

Detection rule 4: coupon stacking

SELECT
  resource_attributes['enduser.id'] AS user_id,
  COUNT() AS coupon_attempts,
  COUNT(DISTINCT span_attributes['app.coupon_code']) AS distinct_codes
FROM otel_traces
WHERE
  span_attributes['http.target'] = '/checkout' AND
  span_attributes['app.coupon_code'] IS NOT NULL AND
  timestamp > now() - INTERVAL 30 MINUTE
GROUP BY user_id
HAVING coupon_attempts > 5 AND distinct_codes > 3
ORDER BY coupon_attempts DESC;

This requires you to attach the coupon code as a span attribute (app.coupon_code), which is a one-line change in your checkout handler. Once you do, the rule above catches stacking attempts almost immediately.

What custom instrumentation you need

The default OpenTelemetry auto-instrumentation captures HTTP method, path, and status. For business-logic detection you need to add:

  • enduser.id — your authenticated user identifier (most teams already have this in JWT claims)
  • enduser.email — for signup-pattern detection (mind the privacy implications, hash if needed)
  • Custom attributes specific to the action (app.coupon_code, app.order_id, app.cart_size)

These are one-line additions per handler:

import { trace } from '@opentelemetry/api';

app.post('/checkout', (req, res) => {
  const span = trace.getActiveSpan();
  span?.setAttribute('enduser.id', req.user.id);
  span?.setAttribute('app.coupon_code', req.body.coupon || null);
  span?.setAttribute('app.cart_size', req.body.items.length);
  // ... rest of handler
});

Once added, every span carries the attributes and your detection queries can pivot on them.

The pattern across all four

Three commonalities in business-logic detection:

  1. Group by user / session, not by endpoint. The endpoint is fine — that's why a WAF can't catch it. The same user doing too many of an action is the signal.
  2. Time-window thresholds. Per-minute or per-hour rates, not absolute counts. Threshold tuning is iterative.
  3. Multiple weak signals beat one strong signal. No single rule catches all abuse; combine cart cycling + free-tier abuse + coupon stacking detection and the false-positive rate drops.

What you need from your data layer

For these detections to be practical, the trace backend has to support:

  • Flexible GROUP BY and HAVING clauses (most do; Datadog's query language is awkward but workable)
  • Custom attributes preserved without sampling (some APM tools sample these out)
  • Reasonable retention (30+ days) for the cross-time correlations

OpenTelemetry-native tools like SecureNow and SigNoz handle this natively because the underlying storage is ClickHouse with full attribute preservation. Per-host APM tools sometimes sample attributes; check before assuming the data is there.

The honest limitation

These detection rules catch the obvious version of each pattern. Sophisticated attackers will adapt — distribute their cart cycling across multiple accounts, use IP rotation to evade signup bursts, write coupon-stacking attempts that each look like a normal session.

For sophisticated abuse, no SQL rule on its own is sufficient. The detection becomes ML-based: behavioral models that learn each user's baseline, then flag deviations. That's a separate stack.

For the 80% of business-logic abuse that's run by unsophisticated bots and competitors, the SQL rules above are enough. Deploy them tomorrow, tune the thresholds against your real traffic, iterate.

Related

Frequently Asked Questions

What is business-logic abuse?

Attacks that exploit valid application functionality in unintended ways — cart cycling, coupon stacking, mass account creation for free-tier abuse, refund fraud. The requests look syntactically legal; the patterns reveal abuse.

Why don't WAFs catch this?

WAFs match payload signatures (SQL injection, XSS) against rule sets. Business-logic abuse uses legitimate inputs — valid coupon codes, valid product IDs, valid user accounts. There's no malicious payload to match.

Can RASP detect it?

Some RASP products have basic business-logic rules but they're typically narrow. The flexibility of trace-data SQL queries beats canned RASP rules for this category.

What's the simplest detection rule?

User-aware aggregation. Group requests by user (or session) and count actions per minute. Anomalous users — way more cart additions, refund requests, free signups than peers — surface immediately. Threshold tuning is the work.

Recommended reading

What 1.2B Requests Look Like: Anomaly Patterns from the SecureNow Firewall Fleet

Aggregated, anonymized data from 1.2B requests across the SecureNow customer fleet. Top anomaly types, peak hours, and the day-of-week patterns nobody publishes.

May 9
10 Best Application Security Monitoring Tools in 2026

An honest, side-by-side comparison of the ten most-deployed application security monitoring tools — from enterprise platforms to free open-source options.

May 9
The 2026 npm Supply-Chain Attack Survey, Q2

A quarterly tally of malicious npm packages, the major incidents, and detection patterns. April 2026 set a new record at 847 confirmed malicious packages — here's what they did and how to detect them.

May 9