Skip to content

Latest commit

 

History

History
118 lines (92 loc) · 5.29 KB

File metadata and controls

118 lines (92 loc) · 5.29 KB

Internal API Integration Guide (Next.js Frontend)

This document outlines the requirements for interacting with the protected Internal APIs from the Next.js frontend. In order to robustly prevent abuse across the platform, we have transitioned from a single static key to a contextual identity-aware approach.

Required Headers

When your Next.js server calls any internal API endpoint (e.g., /auth/upsert-user, /user/inboxes, etc.), you must supply the following headers:

1. x-internal-api-key (Required)

The shared secret connecting the frontend to the backend. Value: process.env.INTERNAL_API_KEY

2. x-fp (Required for Abuse Protection)

A unique fingerprint generated on the client or Next.js edge, which groups requests logically by device/network. Value: A SHA-256 hash of IP + User-Agent + Timezone + Accept-Language. If you omit this, the backend will generate one based on the proxy IP, which may inadvertently block legitimate users if you are behind a shared NAT or CDN.

3. x-cookie-id (Required for Abuse Protection)

A unique persistent identifier for the user's browser, bypassing IP limitations. Value: cookie.fp_id || uuidv4() (Generated by Next.js and stored in an HTTP-only cookie).

4. x-user-id (Recommended)

The ID of the currently logged-in user. Value: The user's wyiUserId (if known). This links the fingerprint to a specific account, preventing mass-creation bots from cycling through accounts.

5. x-nonce (Required)

A unique, single-use token to prevent replay attacks. Value: crypto.randomUUID()

Environmental Security

Internal APIs must be authenticated via HMAC signature + nonce. The IP address is an optional signal, but not a replacement for cryptographic trust. Ensure your Next.js server signs requests appropriately.

Example Implementation (Next.js Route Handler / Server Action)

import crypto from 'crypto';

function generateFingerprint(req: Request, cookieId: string) {
  const ip = req.headers.get('x-forwarded-for') || 'unknown-ip';
  const ua = req.headers.get('user-agent') || 'unknown-ua';
  const tz = req.headers.get('x-timezone') || 'unknown-tz';
  const lang = req.headers.get('accept-language') || 'unknown-lang';
  
  // Hash includes cookieId for stable cross-session tracking
  return crypto.createHash('sha256').update(`${cookieId}|${ip}|${ua}|${tz}|${lang}`).digest('hex');
}

export async function callInternalAPI(endpoint: string, data: any, req: Request, user: any) {
  // Extract or generate a persistent cookie ID
  let cookieId = getCookie('fp_id', req);
  if (!cookieId) {
    cookieId = crypto.randomUUID();
    setCookie('fp_id', cookieId, req);
  }

  const fp = generateFingerprint(req, cookieId);
  const nonce = crypto.randomUUID();
  const timestamp = Date.now().toString();
  const body = JSON.stringify(data);
  const method = 'POST';
  
  // Sign the full request context
  const signature = crypto.createHmac('sha256', process.env.INTERNAL_API_SECRET!)
    .update(`${timestamp}.${method}.${endpoint}.${body}`)
    .digest('hex');

  const response = await fetch(`${process.env.INTERNAL_API_URL}${endpoint}`, {
    method,
    headers: {
      'Content-Type': 'application/json',
      'x-internal-api-key': process.env.INTERNAL_API_KEY!,
      'x-fp': fp,
      'x-cookie-id': cookieId,
      'x-user-id': user?.id || '',
      'x-nonce': nonce,
      'x-timestamp': timestamp,
      'x-signature': signature,
    },
    body
  });

  if (response.status === 429) {
    // Handle Global Rate Limit reached
    throw new Error('Too many requests from this device.');
  }

  return response.json();
}

Progressive Friction Notice

If a Free user makes a high volume of requests, they will automatically be subjected to Progressive Friction. The API will artificially inject a 200ms or 500ms delay into the response. Do not treat this latency as an error, but rather as the system protecting itself. Ensure your Next.js frontend has appropriate loading states and doesn't timeout requests under 1.5 seconds.

Internal Billing Endpoints (One-Click Automations)

If a user tries to upgrade their API plan, and they have an existing paddleCustomerId saved from upgrading their app plan, you can trigger these to bypass standard checkout models:

POST /api/billing/auto-charge-plan

  • Body: { "userId": "...", "targetPlan": "growth", "interval": "yearly" }
  • Description: Uses collection_mode: 'automatic' to charge the user immediately. If Paddle fails (no saved card or declined), returns { error: 'paddle_error', message: '...' } so your frontend can fallback to standard checkout.

POST /api/billing/auto-buy-credits

  • Body: { "userId": "...", "priceId": "pri_xyz123" }
  • Description: Executes a one-off automated transaction for API credits.

Webhooks & Management (Growth+)

Webhooks now enforce strict logging and plan validations:

  • GET /api/user/webhooks/:wyiUserId : Returns active webhooks.
  • GET /api/user/webhooks/logs/:wyiUserId : Returns the last 100 webhook delivery logs (success, failed, retrying status, exact HTTP responseCode, and timestamp).

Global Platform Stats

  • GET /api/statistics/platform-stats : Fast Redis-driven endpoint that outputs:
    • total_api_calls
    • total_emails_received
    • active_api_users ``