# How Inbound Captação Webhooks and the AI Dispatcher Are Rate-Limited in DeskcommCRM

> Discover how DeskcommCRM rate-limits inbound captação webhooks and the AI dispatcher. Learn about per-IP, per-identifier, and per-tenant throttling strategies.

- Repository: [Rafael Melgaço/DeskcommCRM](https://github.com/melgarafael/DeskcommCRM)
- Tags: how-to-guide
- Published: 2026-09-12

---

**DeskcommCRM implements dual-layer rate limiting that protects public lead capture endpoints with per-IP and per-identifier throttling, while the AI dispatcher enforces per-tenant run caps using a shared Redis-backed counter system.**

DeskcommCRM relies on precise rate-limiting mechanisms to prevent brute-force attacks on public webhooks and runaway AI processing costs. The open-source CRM isolates two critical traffic surfaces—inbound captação webhooks and the AI dispatcher—applying distinct quota strategies that are enforced through a common core utility in the codebase.

## Inbound Captação Webhook Rate Limiting

Public lead capture endpoints are protected by the `authRateLimited` helper, which applies dual constraints before processing any payload.

### Dual-Key Protection Strategy

The webhook handler applies two independent limits to prevent distributed abuse:

- **Per-IP rate limiting**: Each client IP address is limited to **60 requests per 5-minute window**, preventing a single source from brute-forcing lead submissions.
- **Per-identifier rate limiting**: Each unique token or hashed email is limited to **5 requests per 5-minute window**, protecting individual accounts from distributed enumeration attacks.

In `app/api/v1/webhooks/in/[token]/route.ts`, the handler resolves the client IP via `clientIp()` and hashes both the IP and the identifier using the `opaque()` utility before checking limits.

### Implementation in the Webhook Handler

The route invokes `authRateLimited` from [`lib/auth/rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/auth/rate-limit.ts) before executing business logic:

```typescript
import { authRateLimited, AUTH_LIMITS } from "@/lib/auth/rate-limit";

export async function POST(req: Request) {
  const token = req.headers.get("x-token") ?? "";
  
  const limited = await authRateLimited(
    "capture",                     // action label
    token,                         // identifier (secret token in URL)
    AUTH_LIMITS.signup,           // limits for this surface
  );
  
  if (limited) {
    return Response.json(
      { error: "Rate limit exceeded" }, 
      { status: 429 }
    );
  }
  // ...process the lead
}

```

The `authRateLimited` function constructs Redis keys using the patterns `auth:login:ip:<opaque-ip>` and `auth:login:id:<opaque-identifier>`, then delegates the actual counter logic to the shared `checkRateLimit` utility.

## AI Dispatcher Rate Limiting

The AI dispatcher governs background agent execution to prevent tenant-level resource exhaustion.

### Per-Tenant Run Caps

Before scheduling an AI run, the dispatcher in [`lib/ai/dispatcher/index.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/ai/dispatcher/index.ts) validates quota availability:

```typescript
import { checkRateLimit } from "./rate-limit";

const RATE_LIMIT_PER_MIN = 60;
const RATE_LIMIT_WINDOW_SEC = 60;

const rateResult = await checkRateLimit(
  `ai-runs:${orgId}`,
  RATE_LIMIT_PER_MIN,
  RATE_LIMIT_WINDOW_SEC,
);

if (!rateResult.allowed) {
  return { 
    status: "rate_limited", 
    count: rateResult.count, 
    limit: rateResult.limit 
  };
}

```

The default configuration allows **60 AI runs per minute per organization** (`orgId`), with the threshold configurable via the `RATE_LIMIT_PER_MIN` environment variable.

### Re-queuing and Backoff Behavior

When `checkRateLimit` returns `allowed: false`, the dispatcher returns a `rate_limited` status object. The surrounding retry logic in [`lib/event-log/dispatcher.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/event-log/dispatcher.ts) interprets this status and re-queues the event with a 5-second delay (`next_attempt_at = now() + 5s`), creating automatic backoff without dropping the task.

## The Shared Rate-Limiting Engine

Both surfaces rely on a single source of truth for counter operations to guarantee consistent semantics across the application.

### Core checkRateLimit Implementation

The generic limiter resides in [`lib/ai/dispatcher/rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/ai/dispatcher/rate-limit.ts) and exports the `checkRateLimit` function:

```typescript
export async function checkRateLimit(
  key: string,
  limit: number,
  windowSec: number,
): Promise<{ allowed: boolean; count: number; limit: number }> {
  // Attempts Redis INCR + EXPIRE pipeline
  // Falls back to in-memory Map if Upstash Redis unavailable
}

```

### Redis Key Structure and Fallback Logic

The function builds tenant-specific keys such as `ai-runs:<orgId>` for AI processing and `auth:login:ip:<hash>` for webhooks. It atomically increments counters and sets expiration windows using Redis pipelines. When the Upstash Redis client is not configured, the utility transparently falls back to an in-memory `Map` structure, ensuring that rate limiting remains functional in development or degraded production environments.

The result object returns the current `count`, the configured `limit`, and a boolean `allowed` flag, allowing callers to implement conditional logic without managing counter state.

## Summary

- **Inbound captação webhooks** are protected by `authRateLimited` in [`lib/auth/rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/auth/rate-limit.ts), enforcing 60 requests per 5 minutes per IP and 5 requests per 5 minutes per identifier.
- **AI dispatcher** checks use `checkRateLimit` directly in [`lib/ai/dispatcher/index.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/ai/dispatcher/index.ts), defaulting to 60 runs per minute per tenant with automatic 5-second re-queuing on limit violations.
- **Shared infrastructure** in [`lib/ai/dispatcher/rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/ai/dispatcher/rate-limit.ts) handles all counter logic, using Redis when available and falling back to in-memory storage when necessary.

## Frequently Asked Questions

### What are the default rate limits for inbound captação webhooks?

The default configuration allows 60 requests per 5-minute window per IP address and 5 requests per 5-minute window per unique identifier (hashed token or email). These values are defined in `AUTH_LIMITS` within [`lib/auth/rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/auth/rate-limit.ts).

### How does the AI dispatcher handle rate-limited events?

When the per-tenant limit is exceeded, the dispatcher returns a `rate_limited` status. The event-log dispatcher in [`lib/event-log/dispatcher.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/event-log/dispatcher.ts) catches this status and re-queues the event with a 5-second delay, enabling automatic retry without manual intervention.

### Can rate limits be configured without modifying the source code?

Yes. The AI dispatcher reads the `RATE_LIMIT_PER_MIN` variable to determine per-tenant caps, while webhook limits are controlled through the `AUTH_LIMITS` configuration object. However, changing the hardcoded window durations (5 minutes for webhooks, 60 seconds for AI) requires editing the respective constants in the source files.

### What happens if the Redis connection fails?

The `checkRateLimit` function in [`lib/ai/dispatcher/rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/ai/dispatcher/rate-limit.ts) automatically falls back to an in-memory `Map` implementation when Redis is unavailable. This ensures that rate limiting continues to function locally, though counts will not persist across server restarts or scale horizontally across multiple instances without Redis.