First-Party Proxy
Learn how Trusted Server proxies third-party assets through first-party domains to improve privacy, security, and ad performance.
Overview
The First-Party Proxy system rewrites third-party URLs in ad creatives to route through your domain, providing:
- Privacy Protection - No direct third-party cookies or tracking
- EC ID Forwarding - Controlled identity propagation
- Creative Rewrites - Automatic HTML/CSS URL transformation
- Click Tracking - First-party click redirects
- Content Security - Validated, signed URLs prevent tampering
How It Works
Core Endpoints
/first-party/proxy - Asset Proxy
Proxies third-party assets with automatic HTML/CSS rewriting.
Request:
GET /first-party/proxy?tsurl=https://example.com/ad.html&tstoken=signatureQuery Parameters:
| Parameter | Required | Description |
|---|---|---|
tsurl | Yes | Base URL of the resource (without query params) |
tstoken | Yes | HMAC-SHA256 signature of the full URL |
tsexp | No | Unix timestamp expiration (30s default from signing) |
| (others) | No | Original query parameters from target URL |
Behavior:
- Validates the
tstokensignature against reconstructed URL - Appends
ts-ecquery parameter (if available) - Proxies request to target URL with forwarded headers:
User-AgentAcceptAccept-LanguageAccept-EncodingRefererX-Forwarded-For
- Processes response based on content type:
- HTML (
text/html) - Rewrites all URLs, returnstext/html - CSS (
text/css) - Rewritesurl()values, returnstext/css - Images - Detects pixels, sets
image/*if missing - Other - Passthrough without modification
- HTML (
Example:
Original URL:
https://tracker.com/pixel.gif?campaign=123&uid=abcSigned proxy URL:
/first-party/proxy?
tsurl=https://tracker.com/pixel.gif&
campaign=123&
uid=abc&
tstoken=HmacSha256SignatureFinal proxied request:
https://tracker.com/pixel.gif?campaign=123&uid=abc&ts-ec=xyz/first-party/click - Click Redirects
Handles click tracking with first-party redirects.
Request:
GET /first-party/click?tsurl=https://advertiser.com/landing&tstoken=signatureQuery Parameters: Same as /first-party/proxy
Behavior:
- Validates the
tstokensignature - Appends
ts-ecparameter to target URL - Issues 302 redirect to target (browser navigates directly)
- Logs click metadata:
- Target URL base (
tsurl) - Whether parameters were present
- Full reconstructed URL
- Referer, User-Agent
- EC ID (if available)
- Target URL base (
Example:
Click URL in creative:
<a
href="/first-party/click?
tsurl=https://advertiser.com/buy&
product=widget&
tstoken=signature"
>
Buy Now
</a>User clicks → Server responds:
HTTP/1.1 302 Found
Location: https://advertiser.com/buy?product=widget&ts-ec=xyzClick vs Proxy
Use /first-party/click for navigational links (anchors) since it avoids downloading content. Use /first-party/proxy for embedded resources (images, scripts, iframes) that need to be rendered inline.
/first-party/sign - URL Signing
Generates signed proxy URLs for dynamic use cases.
Request (GET):
GET /first-party/sign?url=https://example.com/resource.jpgRequest (POST):
POST /first-party/sign
{
"url": "https://example.com/resource.jpg?param=value"
}Response:
{
"href": "/first-party/proxy?tsurl=https://example.com/resource.jpg¶m=value&tstoken=signature&tsexp=1234567890",
"base": "https://example.com/resource.jpg"
}Response Fields:
| Field | Description |
|---|---|
href | Complete signed proxy URL ready to use |
base | Original base URL (without query parameters) |
Expiration:
- Default: 30 seconds from signing
tsexpparameter included in signed URL- Validation fails after expiration
Example Usage:
// Client-side JavaScript dynamically signing URLs
fetch('/first-party/sign?url=' + encodeURIComponent(imageUrl))
.then((r) => r.json())
.then((data) => {
img.src = data.href // Use signed URL
})/first-party/proxy-rebuild - URL Modification
Modifies existing signed URLs by adding or removing parameters.
Request:
POST /first-party/proxy-rebuild?tsclick=encoded_click_url&add=key:value&del=keyQuery Parameters:
| Parameter | Required | Description |
|---|---|---|
tsclick | Yes | Base64-encoded original click/proxy URL |
add | No | Parameter to add (format: key:value) |
del | No | Parameter key to remove |
Behavior:
- Decodes the
tsclickbase64 URL - Parses existing parameters
- Adds specified parameters (
add) - Removes specified parameters (
del) - Re-signs the modified URL
- Returns new signed URL
Example:
Original click URL:
/first-party/click?tsurl=https://example.com&product=A&tstoken=sig1Modify URL (add variant=red, remove product):
POST /first-party/proxy-rebuild?
tsclick=L2ZpcnN0LXBhcnR5L2NsaWNrP3RzdXJsPWh0dHBzOi8vZXhhbXBsZS5jb20mcHJvZHVjdD1BJnRzdG9rZW49c2lnMQ==&
add=variant:red&
del=productResponse:
{
"href": "/first-party/click?tsurl=https://example.com&variant=red&tstoken=sig2"
}Use Cases
This endpoint is designed for advanced scenarios like A/B testing where you need to modify URLs without re-signing from scratch. Most implementations won't need this.
URL Signing & Validation
Trusted Server signs proxy and click URLs using the publisher proxy_secret. Signed URLs include a tstoken and may include tsexp for expiration.
For the detailed signing algorithm, validation steps, and security notes, see Proxy Signing.
Content Type Handling
HTML Rewriting
Triggers: Response Content-Type: text/html
Process:
- Parse HTML with streaming processor
- Rewrite absolute URLs to
/first-party/proxyor/first-party/click - Preserve relative URLs unchanged
- Sign all rewritten URLs with
tstoken - Return as
text/html; charset=utf-8
Rewritten Elements: See Creative Processing for full list.
CSS Rewriting
Triggers: Response Content-Type: text/css
Process:
- Parse CSS for
url(...)values - Rewrite absolute URLs to
/first-party/proxy - Sign URLs with
tstoken - Return as
text/css; charset=utf-8
Example:
/* Original */
.banner {
background: url(https://cdn.com/bg.jpg);
}
/* Rewritten */
.banner {
background: url(/first-party/proxy?tsurl=https://cdn.com/bg.jpg&tstoken=sig);
}Image Handling
Triggers:
- Response
Content-Type: image/*, OR - Request
Acceptheader containsimage/
Process:
- Set
Content-Type: image/*if missing - Detect likely pixels with heuristics:
Content-Length≤ 256 bytes- URL contains
/pixel,/p.gif,/1x1,/track
- Log pixel detection (no response alteration)
- Passthrough image data
Logging:
proxy: likely pixel detected size=43 url=https://tracker.com/p.gifPassthrough (Other Types)
Triggers: Any other Content-Type
Process:
- Forward response without modification
- Preserve original
Content-Type - No HTML/CSS/URL rewriting
- Useful for: JSON, JavaScript, binary files, etc.
Redirect Handling
The proxy automatically follows HTTP redirects:
Supported Status Codes:
301- Moved Permanently302- Found303- See Other (switches to GET)307- Temporary Redirect308- Permanent Redirect
Behavior:
- Follow up to 4 redirect hops
- Re-apply
ts-econ each hop - Switch to
GETafter303response - Log when redirect limit reached
- Preserve request headers across hops
Example Flow:
Request: /first-party/proxy?tsurl=https://short.link&tstoken=sig
→ 302 to https://cdn.com/ad.html
→ 200 with HTML content
→ Rewrite HTML and returnEC ID Propagation
Automatic Forwarding
When proxying, Trusted Server automatically appends the ts-ec parameter:
Source Priority:
x-ts-ecrequest headerts-eccookie- Generate new ID if missing
Example:
Original request to proxy:
/first-party/proxy?tsurl=https://tracker.com/pixel.gif&tstoken=sig
Cookie: ts-ec=user123
Proxied backend request:
https://tracker.com/pixel.gif?ts-ec=user123Redirect Propagation
EC IDs are re-applied on every redirect hop:
/first-party/proxy?tsurl=https://redirect1.com&tstoken=sig
→ https://redirect1.com?ts-ec=user123
→ 302 to https://redirect2.com
→ https://redirect2.com?ts-ec=user123
→ 302 to https://final.com
→ https://final.com?ts-ec=user123
→ 200 responseThis ensures downstream trackers receive consistent IDs even through redirect chains.
Click ID Forwarding
Click redirects also forward EC IDs:
<a href="/first-party/click?tsurl=https://advertiser.com&tstoken=sig"></a>User clicks → redirect includes ID:
302 Found
Location: https://advertiser.com?ts-ec=user123Privacy Control
EC IDs are only forwarded when:
- User has given GDPR consent (if required)
- ID exists in request (header/cookie)
- Integration hasn't disabled forwarding (
forward_ec_id: false)
Configuration
Publisher Settings
Configure proxy behavior in trusted-server.toml:
[publisher]
domain = "publisher.com"
origin_url = "https://origin.publisher.com"
proxy_secret = "your-secure-random-secret"
cookie_domain = ".publisher.com" # For ts-ec cookiesProxy Allowlist
Restrict which domains the proxy may redirect to via the [proxy] section:
[proxy]
allowed_domains = [
"tracker.com", # Exact match
"*.adserver.com", # Wildcard: adserver.com and all subdomains
"*.trusted-cdn.net",
]Semantics: When a proxied request receives an HTTP redirect (301/302/303/307/308), the redirect target host is checked against allowed_domains. If the host does not match any pattern the redirect is blocked and a 403 error is returned.
Wildcard matching:
| Pattern | Matches | Does not match |
|---|---|---|
tracker.com | tracker.com | sub.tracker.com |
*.tracker.com | tracker.com, sub.tracker.com, a.b.tracker.com | evil-tracker.com |
- The
*prefix matches the base domain and any subdomain at any depth. - Matching is case-insensitive; entries are normalized to lowercase on startup.
- The wildcard requires a dot boundary —
*.example.comwill not matchevil-example.com. - A bare
"*"entry is not valid and will be removed at startup with a warning. Use an empty list for open mode.
:::note Unicode / Internationalized Domain Names Matching uses ASCII case-folding (to_ascii_lowercase). Internationalized domain names (IDNs) in Punycode form (e.g., xn--nxasmq6b.com) are matched literally — the Unicode label and its Punycode equivalent are treated as different strings. If your ad network uses IDN domains, add the Punycode form to allowed_domains. :::
Default behavior: When allowed_domains is omitted (or set to an empty list) every redirect destination is permitted. This default exists solely for development convenience and must be overridden in production.
Production Recommendation
Always set allowed_domains explicitly in production deployments. Without an allowlist, a signed proxy URL that follows redirects could be used to reach internal or unintended hosts (SSRF).
[proxy]
allowed_domains = [
"*.your-ad-network.com",
"tracker.your-partner.com",
]URL Rewrite Exclusions
Exclude specific domains from rewriting:
[rewrite]
exclude_domains = [
"*.cdn.trusted.com", # Wildcard pattern
"first-party.example.com" # Exact match
]URLs matching these patterns will NOT be rewritten to /first-party/proxy.
Streaming vs Buffered
Control whether responses are streamed or buffered:
// Integration code example
ProxyRequestConfig::new(url)
.with_streaming() // Enable streaming (no HTML/CSS rewrites)Streaming (buffered rewrites disabled):
- Preserves origin compression (gzip/brotli)
- Lower memory usage
- No HTML/CSS URL rewriting
- Best for: large files, images, videos
Buffered (default):
- Enables HTML/CSS rewriting
- Decompresses response
- Higher memory usage
- Best for: ad creatives, landing pages
Performance Optimization
Compression
Buffered Mode:
- Decompresses origin response
- Processes content
- Returns uncompressed (Fastly can re-compress)
Streaming Mode:
- Preserves origin
Content-Encoding - No decompression/recompression overhead
- Passes through gzip/brotli/deflate
Caching
Proxy responses respect origin cache headers:
Cache-ControlExpiresETagLast-Modified
Best Practices:
Cache-Control: public, max-age=3600
Vary: Accept-EncodingHeader Forwarding
Only essential headers are forwarded to reduce overhead:
Forwarded Headers:
User-Agent- Client identificationAccept- Content negotiationAccept-Language- Language preferencesAccept-Encoding- Compression supportReferer- Page contextX-Forwarded-For- Client IP chain
Not Forwarded:
- Authentication headers (unless explicitly added)
- Cookies (except EC ID appended as query param)
- Custom headers (unless added via
ProxyRequestConfig)
Error Handling
Common Errors
Invalid Signature:
HTTP 502 Bad Gateway
tstoken validation failed: signature mismatchSolutions:
- Verify
proxy_secretmatches signing configuration - Check URL reconstruction includes all parameters in correct order
- Ensure no URL encoding issues
Expired URL:
HTTP 502 Bad Gateway
tstoken expiredSolutions:
- URLs signed with
/first-party/signexpire in 30s - Re-sign URL if needed
- Check client/server clock sync
Missing Parameters:
HTTP 400 Bad Request
Missing required parameter: tsurlSolutions:
- Ensure
tsurlparameter is present - Include
tstokenin request - Verify URL encoding is correct
Redirect Limit
When redirect limit (4 hops) is reached:
Log: proxy: redirect limit reached for url=https://...Response: Returns last redirect response (302/307/308) without following.
Solutions:
- Contact origin to reduce redirect chain
- Increase limit in code (modify
MAX_REDIRECTSconstant)
Security Considerations
Token Security
Do: ✅ Use cryptographically strong proxy_secret (32+ bytes random)
✅ Rotate secrets periodically
✅ Validate expiration on all requests
✅ Use constant-time comparison for signatures
Don't: ❌ Expose proxy_secret in client-side code
❌ Reuse secrets across environments
❌ Accept unsigned URLs
❌ Skip validation for "trusted" domains
URL Injection Prevention
Signed URLs prevent injection attacks:
Attacker tries:
/first-party/proxy?tsurl=https://evil.com&tstoken=forged
Trusted Server:
1. Computes expected token for https://evil.com
2. Compares with provided token
3. Rejects if mismatch (502 Bad Gateway)Content Security
Automatic Protection:
- HTML/CSS rewriting removes malicious URLs
- Data URIs are skipped (
data:,javascript:,blob:) - Protocol validation (only
http://andhttps://)
Considerations:
- Origin content is still served (validate trusted sources)
- Streaming mode bypasses HTML inspection
- Enable CSP headers for additional protection
Monitoring & Debugging
Logging
Proxy requests emit detailed logs:
proxy: origin response status=200 ct=text/html cl=1234 accept=text/html url=https://...
proxy: likely pixel detected size=43 url=https://tracker.com/p.gif
click: tsurl=https://advertiser.com had_params=true target=... referer=... ua=... tsid=...Diagnostic Headers
Add custom headers for debugging:
[response_headers]
X-Proxy-Mode = "rewrite"
X-TS-Version = "1.0"Metrics to Track
Key Metrics:
- Proxy request count (total)
- Signature validation failures (rate)
- Redirect hops (average/max)
- Response time (p50/p95/p99)
- Content type distribution
- Pixel detection rate
Next Steps
- Learn about Creative Processing for HTML rewriting details
- Review Edge Cookies for identity management
- Set up Configuration for your deployment
- Explore Integration Guide for custom integrations