Articles on: Data Security

LLM Guidance For Configuring The Repliers API Firewall

Repliers API IP Firewall: Forwarding the Real Client IP


What this solves


The Repliers API supports an IP-based firewall. When you proxy requests through a backend server, the Repliers API sees your server's IP — not your end users'. To enforce per-client IP rules, you must extract the real client IP and forward it in the x-repliers-forwarded-for header on every outbound Repliers API request.



Why use the Repliers firewall


  • Meet MLS compliance requirements. MLSs require you to protect listing data. The firewall handles this for you by detecting and blocking threats before they reach listing data.
  • Lower your costs. Blocked requests don't count toward your quota, and neither do requests from popular bots. Without the firewall, all of that traffic counts against you and can lead to overage charges.
  • Gain usage insights. Because every request carries the real client IP, you get visibility into how listings are being accessed.
  • No extra infrastructure. Repliers does the detection and blocking. Your only job is to forward the real client IP on each request.


The firewall only works when every request reaches it with the real client's IP, so follow the implementation and best practices below closely.



How it works


Your backend sits between the browser and Repliers. The browser's IP arrives via the X-Forwarded-For header (populated by your load balancer / CDN / reverse proxy). Your server reads that chain, finds the first public (non-private) IP, and forwards it to Repliers.


Browser → CDN/Load Balancer → Your API Server → Repliers API
(real IP) (appends to XFF) (reads XFF) (enforces firewall)



Implementation (TypeScript / Express)


1. Trust the proxy chain


In your Express app setup, tell Express to trust the upstream proxy so X-Forwarded-For is populated correctly:


// app.ts
app.set("trust proxy", true);


Warning: Only set this if your server is actually behind a trusted proxy (load balancer, CDN, Render, Railway, Fly.io, etc.). Do not set it if the server is directly internet-facing — a client could spoof the header.



2. Create the IP extraction utility


// lib/repliers-headers.ts
import type { Request } from "express";

const IPV4 = /^(\d{1,3}\.){3}\d{1,3}$/;
const IPV6 = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;

function isValidIp(ip: string): boolean {
return IPV4.test(ip) || IPV6.test(ip);
}

function isPrivateIp(ip: string): boolean {
return (
/^10\./.test(ip) ||
/^172\.(1[6-9]|2\d|3[01])\./.test(ip) ||
/^192\.168\./.test(ip) ||
ip === "127.0.0.1" ||
ip === "::1"
);
}

export function getRepliersHeaders(req: Request): Record<string, string> {
// X-Forwarded-For is a comma-separated chain: client, proxy1, proxy2, ..., edge
const forwardedFor = (req.headers["x-forwarded-for"] as string | undefined ?? "")
.split(",")
.map((ip) => ip.trim())
.filter(Boolean);

// Take the first public IP — private IPs are always internal proxy hops.
const realIp =
forwardedFor.find((ip) => isValidIp(ip) && !isPrivateIp(ip)) ??
forwardedFor[0] ??
req.socket.remoteAddress?.replace(/^::ffff:/, "") ??
"";

return { "x-repliers-forwarded-for": realIp };
}


IP selection priority:

  1. First valid, public IP in the X-Forwarded-For chain
  2. First IP in the chain (fallback when all are private — unusual, but safe)
  3. req.socket.remoteAddress (direct connection, no proxy)
  4. Empty string (should never happen in practice)


Private ranges filtered out: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.1, ::1


Warning: In production, fallbacks 2 and 3 can resolve to your load balancer's or proxy's IP rather than the end user's. If that happens, every request will appear to come from one IP and it will be banned quickly (see Never send your backend or proxy IP below). Log the forwarded value in production and confirm it is the real client IP.



3. Spread the headers into every Repliers API call


// routes/listings.ts
import { getRepliersHeaders } from "../lib/repliers-headers";

router.get("/listings", async (req, res) => {
const apiKey = process.env["REPLIERS_API_KEY"];

const response = await fetch("https://api.repliers.io/listings", {
headers: {
"repliers-api-key": apiKey,
"Content-Type": "application/json",
...getRepliersHeaders(req), // ← always spread this
},
});

// ...
});


Apply ...getRepliersHeaders(req) to every route that calls the Repliers API — listings, listing detail, locations, statistics, etc.



Firewall behavior and best practices


Never retry without the header


If a request is blocked or throttled, do not retry it without the x-repliers-forwarded-for header. Dropping the header to get around a block circumvents the firewall, defeats its purpose, and removes the protection it provides. Treat a blocked or throttled response as final for that client and handle it gracefully in your UI.


Don't cache Repliers content


Don't cache responses from the Repliers API. Caching has performance benefits, but a listing request to Repliers takes under 50ms on average, so there's little to gain. More importantly, requests served from a cache never reach the Repliers firewall, which removes its ability to detect and block threats. Every client request should result in a live Repliers API call carrying the x-repliers-forwarded-for header.


Never fall back to cached content when a visitor is blocked. If Repliers blocks or throttles a request, serving that visitor a cached copy delivers content to someone the firewall has determined shouldn't receive it. A blocked response should result in no listing content for that visitor.


Blocked requests don't count toward your quota


Requests blocked by the firewall do not count toward your quota. Without the firewall, this traffic counts against you and you are subject to overage charges.



When the firewall is in place, requests from popular bots don't count toward your quota either. Without the firewall, bot traffic counts against you like any other request and can push you into overage charges. This is another reason to forward the real client IP for every request, bots included.


Your own team's IPs may get blocked


It's common for your own IP, or your team's, to get blocked. You're power users, and the firewall is correctly detecting higher-than-normal activity from you. Ask Repliers support to whitelist your team's IP addresses.


Never send your backend or proxy IP


Make sure the header never contains a backend, proxy, or load balancer IP. Because all requests are routed through that IP, it will be banned quickly, blocking every user at once. The IP in the header should always be the real end user's IP, whether that user is a person or a bot.



Gotchas


CDN/proxy strips or rewrites X-Forwarded-For
Some platforms (Cloudflare, Render) use their own header variants (CF-Connecting-IP, X-Real-IP). If X-Forwarded-For is empty or always shows the edge IP, check your platform's docs and adjust the extraction logic accordingly.


Multiple proxies append IPs left-to-right
The format is client, proxy1, proxy2. The leftmost entry is the original client. The rightmost entries are internal/edge hops. This utility correctly picks the leftmost public IP.


IPv6-mapped IPv4 addresses
Node.js sometimes returns ::ffff:1.2.3.4 from req.socket.remoteAddress. The .replace(/^::ffff:/, "") strip handles this.


trust proxy and spoofing
With trust proxy: true, Express trusts the entire X-Forwarded-For chain. If an attacker sends a crafted X-Forwarded-For header and your CDN/load balancer appends to it (rather than replacing it), the real IP is still at the right end of the chain — but this utility picks the leftmost public IP, which could be spoofed. For high-security environments, use trust proxy: N (a count) to trust only the N rightmost IPs, or validate against your known edge IP ranges.



Testing locally


When running locally without a proxy, X-Forwarded-For is absent and req.socket.remoteAddress is 127.0.0.1 or ::1. The header sent to Repliers will be an empty string or a loopback address — this is fine for local development, as the firewall rules apply in production.


To simulate a real IP in local dev:


curl -H "X-Forwarded-For: 203.0.113.42" http://localhost:3000/api/listings?locationId=...

Updated on: 28/10/2026

Was this article helpful?

Share your feedback

Cancel

Thank you!