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.
When your Next.js server calls any internal API endpoint (e.g., /auth/upsert-user, /user/inboxes, etc.), you must supply the following headers:
The shared secret connecting the frontend to the backend.
Value: process.env.INTERNAL_API_KEY
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.
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).
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.
A unique, single-use token to prevent replay attacks.
Value: crypto.randomUUID()
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.
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();
}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.
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:
- 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.
- Body:
{ "userId": "...", "priceId": "pri_xyz123" } - Description: Executes a one-off automated transaction for API credits.
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,retryingstatus, exact HTTPresponseCode, andtimestamp).
GET /api/statistics/platform-stats: Fast Redis-driven endpoint that outputs:total_api_callstotal_emails_receivedactive_api_users``