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
- Multi-Tenancy: Enable routing leads to client-specific Google Sheets or CRM webhooks based on incoming
client_idparameter orX-Client-ID/X-API-Keyheaders without duplicating code. - Security & Anti-Spam: Protect public API routes with sliding-window rate limiting to prevent DDOS, automated spam, and upstream webhook quota exhaustion.
- 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_idresolution with environment mapping (Chosen). Accepted: Resolves webhooks dynamically viaCLIENT_WEBHOOK_MAPJSON orCLIENT_WEBHOOK_<CLIENT_ID>env vars.
Decision Outcome
Chosen Option: Option 2.
Architecture & Implementation Details
Multi-Tenant Routing Engine (
lib/leads.ts):consultationLeadSchemaupdated with optionalclient_id(defaults to"default").resolveClientWebhookUrl(clientId)dynamically checks:CLIENT_WEBHOOK_MAP(JSON map ofclient_id -> webhook_url).CLIENT_WEBHOOK_<CLIENT_ID_UPPERCASE>environment variable.- Fallback to global
CRM_WEBHOOK_URL.
- Dispatches leads with
X-Client-IDheader and returnsclient_idin response metadata.
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 andX-RateLimit-*headers.
- In-memory sliding-window token bucket with IP address resolution (
Graceful Webhook Error Handling:
- Upstream network errors or DNS failures are caught and surfaced as
LeadWebhookError(502 Bad Gateway) rather than crashing the process.
- Upstream network errors or DNS failures are caught and surfaced as
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.