44 documents
Architecture Decision Records (ADRs)Status: Accepted📅2026-07-30⏱️2 min read

8. Multi-Tenant Lead Routing & API Rate Limiting

JSGlobtech's foundation needs to serve as a white-label, multi-tenant lead generation and GEO engine for multiple clients. Previously, lead capture endpoints (`POST /api/leads/subm...

8. Multi-Tenant Lead Routing & API Rate Limiting

  • Status: Accepted
  • Date: 2026-07-30
  • Context: PR #11

Context & Problem Statement

JSGlobtech's foundation needs to serve as a white-label, multi-tenant lead generation and GEO engine for multiple clients. Previously, lead capture endpoints (POST /api/leads/submit and MCP tool request_dev_consultation) routed leads exclusively to a single global CRM_WEBHOOK_URL environment variable. Furthermore, public endpoints lacked active rate limiting to prevent spam or webhook abuse.

Decision Drivers

  1. Multi-Tenancy: Enable routing leads to client-specific Google Sheets or CRM webhooks based on incoming client_id parameter or X-Client-ID / X-API-Key headers without duplicating code.
  2. Security & Anti-Spam: Protect public API routes with sliding-window rate limiting to prevent DDOS, automated spam, and upstream webhook quota exhaustion.
  3. Backward Compatibility: Single-tenant setups must continue working seamlessly by falling back to the default CRM_WEBHOOK_URL.

Considered Options

  • Option 1: Hardcoded per-tenant routes (e.g. /api/leads/client-a/submit, /api/leads/client-b/submit). Rejected: Causes route proliferation and code duplication.
  • Option 2: Dynamic client_id resolution with environment mapping (Chosen). Accepted: Resolves webhooks dynamically via CLIENT_WEBHOOK_MAP JSON or CLIENT_WEBHOOK_<CLIENT_ID> env vars.

Decision Outcome

Chosen Option: Option 2.

Architecture & Implementation Details

  1. Multi-Tenant Routing Engine (lib/leads.ts):

    • consultationLeadSchema updated with optional client_id (defaults to "default").
    • resolveClientWebhookUrl(clientId) dynamically checks:
      1. CLIENT_WEBHOOK_MAP (JSON map of client_id -> webhook_url).
      2. CLIENT_WEBHOOK_<CLIENT_ID_UPPERCASE> environment variable.
      3. Fallback to global CRM_WEBHOOK_URL.
    • Dispatches leads with X-Client-ID header and returns client_id in response metadata.
  2. API Rate Limiting (lib/rate-limit.ts):

    • In-memory sliding-window token bucket with IP address resolution (getClientIp).
    • Endpoints /api/leads/submit (10 req/min/IP) and /api/contact (5 req/min/IP) enforce rate limits and respond with HTTP 429 Too Many Requests and X-RateLimit-* headers.
  3. Graceful Webhook Error Handling:

    • Upstream network errors or DNS failures are caught and surfaced as LeadWebhookError (502 Bad Gateway) rather than crashing the process.

Consequences

  • Positive: JSGlobtech can onboard multiple white-label agency clients dynamically.
  • Positive: Automated spam and API abuse are throttled via IP rate limiting.
  • Negative: For production setups with high concurrency across many instances, an external Redis rate limiter can replace the in-memory store if needed.