Resources Pricing Docs FAQ Start Free
Resources / Integration
Integration

Client-Side CORS vs. Backend API: Integrating GetVisitor.info

Client-side fetch or a backend proxy: the real latency, quota, and security tradeoffs for integrating GetVisitor.info.

You can call GetVisitor.info directly from the browser or route it through your own backend. Both work. The right choice depends on your stack and how often you're calling it per session.

Client-Side (CORS)

Drop a fetch() call into your frontend and you're done, no backend route needed. This fits static sites, Jamstack setups, and SPAs hosted on static storage.

The tradeoff is a cold start on the first call. A browser hitting a new cross-origin domain has to do DNS resolution, a TCP handshake, and a TLS handshake before it can send the actual request:

Total Latency ≈ DNS Lookup + (3 × RTT)
Browser (Client)                                   GVI Edge Server
   |                                                     |
   |--- 1. DNS Resolution Request ----------------------->| (0.5 - 1 RTT)
   |                                                     |
   |--- 2. TCP SYN ------------------------------------->| 
   |<-- TCP SYN-ACK -------------------------------------| (1st RTT: TCP Handshake)
   |                                                     |
   |--- 3. TLS Client Hello ---------------------------->| 
   |<-- TLS Server Hello & Cert -------------------------| (2nd RTT: TLS 1.3 Handshake)
   |                                                     |
   |--- 4. HTTP GET / ---------------------------------->| 
   |<-- 200 OK (JSON Payload) ---------------------------| (3rd RTT: Data Transfer)
   v                                                     v

On a typical connection with an 80ms RTT, that's 250 to 400ms before your script has the data. If your page layout depends on that response for pricing or banners, expect visible layout shift.

Backend API / Proxy Route

Route the request through your own server or edge function instead:

Browser                Your Backend Server                GVI Edge Server
   |                            |                                |
   |--- Standard HTTP Request ->|                                |
   |                            |=== Persistent Warm Conn ======>| (1 RTT)
   |                            |<-- Instant GVI Payload --------|
   |<-- Rendered HTML / Data ---|                                |

Your backend keeps a warm, pooled connection to GetVisitor.info, so the DNS lookup and both handshakes are already done. The browser's connection to your own origin is also already warm from page load. Net result: roughly 2 RTTs total instead of the 3+ a cold cross-origin call needs.

This only holds for the first call in a session. If your frontend calls GVI multiple times in one page load, the browser socket from call #1 stays open and subsequent CORS calls are actually the fastest option. Since most integrations fetch location and tax data once per session, the backend route wins in practice almost every time.

Backend routing also gets you a few things CORS can't:

The tradeoff is you need a route handler or middleware, and you need to send the visitor's real IP and user agent in the request body. Behind a proxy or CDN, read the IP from X-Forwarded-For on your side first.

Comparison

Client-Side (CORS)Backend / Proxy
SetupRegister a domain, add a fetch() callAdd a server route
First-request latency~3 RTT + DNS~2 RTT, warm connections
Repeat-request latencyLowest, socket already openSlightly higher
Monthly quotaSeparate, smaller CORS quotaLarger API quota
CredentialsNone in the browser; the registered domain is checkedAPI key, kept on your server
Best forStatic sites, SPAs, prototypesHigh-traffic SaaS, SSR, checkout flows

Code

Client-side:

const ACCOUNT_ID = "YOUR_ACCOUNT_ID";

// No key, no headers, no body. Your domain must be registered first.
async function fetchVisitorInfo() {
  const response = await fetch(`https://${ACCOUNT_ID}.web.getvisitor.info/`);
  return response.json();
}

Backend proxy (Express):

const ACCOUNT_ID = process.env.GVI_ACCOUNT_ID;
const API_KEY = process.env.GVI_API_KEY;

// fetch() reuses the connection to the same origin across calls.
app.get("/api/visitor-info", async (req, res) => {
  const ip = req.headers["x-forwarded-for"]?.split(",")[0].trim() || req.socket.remoteAddress;
  const gviResponse = await fetch(`https://${ACCOUNT_ID}.api.getvisitor.info/`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ ip, ua: req.headers["user-agent"] }),
  });
  res.json(await gviResponse.json());
});

Full request, response, limits and errors: CORS docs and API docs.

Use client-side if you have no backend or you're prototyping. Use a backend proxy for payments, SSR pricing pages, or anything where first-load speed and quota matter.

Either way, cache the response for the session instead of calling on every page load. Location, currency, and tax rarely change mid-session, so one call plus a stored result beats both approaches run repeatedly.

Ready to try it in your own stack?
Start Free