Handling forwarded requests
In this guide, we will look at how to handle forwarded requests coming from your users on their custom domains.
Headers on forwarded requests
Every request we forward to your upstream carries these headers. We set them ourselves and overwrite any value the visitor sent.
| Header | Value |
|---|---|
X-Served-For | The custom domain the visitor requested, e.g. app.mydomain.com |
X-SaaS-Domains-IP | The visitor's IP address |
X-SaaS-Domains-Auth-Token | Your upstream's auth token (see below) |
Host | Your upstream's host, e.g. app.example.com, not the custom domain |
X-Forwarded-Host | Your upstream's host, not the custom domain |
X-Forwarded-Proto | https |
X-Forwarded-For | The visitor's IP address |
If geocoding_enabled is on for the upstream, we also add the geocoding headers. Because Host is the same for every custom domain, use X-Served-For to tell which domain a request was for.
X-Served-For and X-SaaS-Domains-IP headers
When a request is forwarded from a custom domain to your upstream, the X-Served-For and X-SaaS-Domains-IP headers will be added to the request.
X-Served-For contains information about the custom domain for which the request was forwarded.
X-SaaS-Domains-IP contains information about the IP address of the client sending the request.
X-SaaS-Domains-Auth-Token header
Every forwarded request also carries the X-SaaS-Domains-Auth-Token header, set to your upstream's auth token. Use it to check that a request really came through SaaS Custom Domains.
Your app decides which customer's data to show based on X-Served-For. Anyone who can reach your upstream directly could send that header themselves and see another customer's pages. Reject any request that has X-Served-For but not the right token. Requests to your own domain, without X-Served-For, are unaffected.
New upstreams get a random 32-character token. Upstreams created before October 2026 keep their original 16-character token; it isn't changed automatically, because that would break apps already checking it. You can set your own token (1 to 64 letters, digits, dashes or underscores) from the dashboard or with the auth_token field in the API. If you change it, update your app at the same time, or requests will be rejected in between.
Keep the token in an environment variable, check it before you look up the customer, and compare it in constant time. Also reject the request when the expected token is empty: if the variable is set to an empty string in some environment, an empty token compares equal to a missing header, and every request would pass.
Verify the auth token
class ApplicationController < ActionController::Base
# Runs before any customer lookup
prepend_before_action :verify_proxy_token
private
def verify_proxy_token
return unless request.headers["X-Served-For"]
token = request.headers["X-SaaS-Domains-Auth-Token"].to_s
expected = ENV["PROXY_AUTH_TOKEN"].to_s
valid = expected.present? &&
ActiveSupport::SecurityUtils.secure_compare(token, expected)
head :forbidden unless valid
end
end
Requests come from changing IP addresses
Our proxies run on AWS and don't use a fixed list of outgoing IP addresses, so don't allowlist IPs to protect your upstream. Check the X-SaaS-Domains-Auth-Token header instead.
If a WAF or Cloudflare sits in front of your upstream, add a rule that lets requests with the correct X-SaaS-Domains-Auth-Token through. Many custom domains share the same proxy IP addresses, so their combined traffic can look like bot traffic and get blocked or challenged.
CDNs and caches in front of your upstream
If a CDN or cache sits in front of your upstream, include X-Served-For in its cache key. Every forwarded request has the same Host, so without it one customer's cached page, or a cached redirect, can be served on another customer's domain.
Example
You have an upstream that is listening on https://app.example.com.
You have a custom domain https://app.mydomain.com that is forwarding requests to https://app.example.com.
A client with IP 37.228.203.125 makes a request to https://app.mydomain.com.
The X-Served-For header will be added to the forwarded request with the value app.mydomain.com.
The X-SaaS-Domains-IP header will be added to the forwarded request with the value 37.228.203.125.
The X-SaaS-Domains-Auth-Token header will be set to your upstream's auth token, e.g. abcdef123456.
The Host header will be set to app.example.com.