AWS Lambda Proxy
The AWS Lambda Proxy is an open source implementation of the MonetizationOS proxy that runs as a Node.js server on AWS Lambda Web Functions, behind an Amazon CloudFront distribution. To deploy it, follow the AWS Lambda Proxy guide.
Environment variables
The proxy reads its variables from the Web Function revision's environment.
infra/template.yaml sets them from stack parameters, with the defaults below.
| Variable | Required | Description |
|---|---|---|
MONETIZATION_OS_SECRET_KEY | Yes | Your MonetizationOS secret key. Used to authenticate Surface Decision API requests and to derive the environment prefix for custom endpoint routing. |
ORIGIN_URL | Yes | The base URL of your origin server. All non-endpoint requests are proxied here. |
SURFACE_SLUG | No | The MonetizationOS surface to evaluate for every HTML request. The template defaults to web. |
AUTHENTICATED_USER_JWT_COOKIE_NAME | No | Cookie name containing the authenticated user's JWT. The template defaults to __session. |
ANONYMOUS_SESSION_COOKIE_NAME | No | Cookie name for anonymous session identifiers. The template defaults to anon-session-id. |
MONETIZATION_OS_HOST | No | Override the MonetizationOS API host. Defaults to https://api.monetizationos.com. |
MONETIZATION_OS_ENDPOINTS_PREFIX | No | URL prefix for custom MOS endpoint routing. Defaults to /mos-endpoints/. |
INJECT_SCRIPT_URL | No | URL of a script appended to <head> after component transforms. Unset by default. Set it to https://assets.monetizationos.com/web-components-latest.js to use MonetizationOS web components. |
ORIGIN_REQUEST_HEADERS | No | JSON object of headers added to, or overriding, every request to the origin. The template defaults to {}. |
SURFACE_DECISIONS_IGNORE_PATHS | No | Comma-separated regex patterns for paths that should skip surface decisions. No stack parameter. |
SURFACE_DECISIONS_COOKIES | No | Comma-separated regex patterns for cookie names sent to the Surface Decision API as http.cookies. No stack parameter. |
ORIGIN_VERIFY_SECRET | No | Shared secret that CloudFront sends as x-mos-origin-verify. The template sets it from the OriginVerifySecret parameter, which infra/deploy.sh generates on the first deploy. When unset, as in local development, the proxy skips the check. |
CloudFront front door
The template puts a CloudFront distribution in front of the Web Function endpoint.
Its default behavior uses the managed CachingDisabled cache policy and the AllViewerExceptHostHeader origin request policy, so requests reach the proxy with the viewer's headers, cookies, and query string.
Static paths
The viewer-request function sends paths ending in a StaticExtensions extension straight to ORIGIN_URL, without the proxy, because the proxy passes non-HTML responses through unchanged.
It keeps the scheme, port, and path prefix of ORIGIN_URL, and turns off the Origin Access Control signature and the shared secret for these requests.
Paths under MONETIZATION_OS_ENDPOINTS_PREFIX always go to the proxy, and setting ORIGIN_REQUEST_HEADERS turns static paths off.
Proxy headers
| Header | Set by | Purpose |
|---|---|---|
x-mos-viewer-host | Viewer-request function | The viewer's Host. Lambda only accepts its own domain as Host, so the proxy rebuilds the public URL from this. |
x-mos-accept-encoding | Viewer-request function | The viewer's Accept-Encoding, or identity when absent. Used to pick the response compression. |
x-mos-origin-verify | CloudFront origin custom header | The shared secret. Requests without a matching value get a 403. |
The viewer-request function overwrites any viewer-sent value of the first two headers, and rejects requests without a Host with a 400.
The proxy removes all three headers, the CloudFront Origin Access Control signature, and Lambda's own request headers before fetching from the origin.
With OriginAuth set to IamAuth, CloudFront also signs each request with Origin Access Control, and Lambda rejects unsigned callers before they reach the proxy.
Custom endpoint routing
Requests whose path begins with the MONETIZATION_OS_ENDPOINTS_PREFIX (default /mos-endpoints/) are forwarded directly to the MonetizationOS API as Endpoint Workflow requests. The remaining pipeline stages are skipped.
The environment prefix is extracted from MONETIZATION_OS_SECRET_KEY to target the correct environment:
/mos-endpoints/my-route → https://api.monetizationos.com/api/v1/envs/{env}/endpoints/my-routeCloudFront request context
The proxy calls the MonetizationOS API from an AWS Region, so it reads the viewer's location and network from CloudFront request headers instead.
It sends them in an edge object in the Surface Decision API request body:
edge field | CloudFront header |
|---|---|
provider | Always cloudfront |
requestId | X-Amz-Cf-Id |
geo.countryCode | CloudFront-Viewer-Country |
geo.regionCode | CloudFront-Viewer-Country-Region, an ISO 3166-2 subdivision |
geo.region | CloudFront-Viewer-Country-Region-Name |
geo.city | CloudFront-Viewer-City |
geo.postalCode | CloudFront-Viewer-Postal-Code |
geo.metroCode | CloudFront-Viewer-Metro-Code, US only |
geo.latitude, geo.longitude | CloudFront-Viewer-Latitude, CloudFront-Viewer-Longitude |
geo.timezone | CloudFront-Viewer-Time-Zone, an IANA time zone |
network.asn | CloudFront-Viewer-ASN |
tls.version | CloudFront-Viewer-TLS, the protocol part only |
tls.httpProtocol | CloudFront-Viewer-Http-Version, in the form HTTP/2 |
Fields whose header is missing are left out.
The edge object is sent whenever the request carries X-Amz-Cf-Id or CloudFront-Viewer-Country, even if CloudFront can't locate the viewer.
JA3 and JA4 fingerprints aren't sent, because the AllViewerExceptHostHeader origin request policy can't forward them.
The viewer's IP address, from CloudFront-Viewer-Address, is sent as http.clientIP.
HTML transformation with lol-html
Component behaviors use lol-html, the streaming HTML rewriter behind Cloudflare's HTMLRewriter, built to WASM for Node.js.
The proxy rewrites the body as it streams from the origin, and supports range replacement.
CSS selector limitations
- Selectors using
:last-childaren't supported and are silently skipped. - All other CSS selectors that lol-html supports work as expected. A component with a selector lol-html rejects is skipped.
Script injection
If INJECT_SCRIPT_URL is set and at least one component transform runs, the proxy appends the following to <head>:
<script src="{INJECT_SCRIPT_URL}" async defer></script>Compression
CloudFront doesn't compress responses under CachingDisabled, so the proxy does.
Static paths keep your origin's own compression.
Text, JSON, JavaScript, XML, SVG, and WASM responses from the proxy are compressed with brotli, or gzip if the viewer doesn't accept brotli, and flushed per chunk so streamed HTML keeps streaming.
Responses that are already encoded, partial, or marked Cache-Control: no-transform are passed through uncompressed.
Caching behavior
The proxy sets Cache-Control: no-store on all HTML responses during the link-rewriting stage. This prevents browser caching of personalized, per-user responses. Non-HTML responses passed through unmodified retain whatever cache headers the origin returned.
The template's CloudFront behavior caches nothing, and neither Lambda nor Node.js fetch caches the proxy's requests to your origin.
Put your origin behind its own CDN with cacheable responses, so the proxy's fetches, and the static paths CloudFront sends there, are served from that CDN rather than your servers.
Scaling and timeouts
The template sets the revision's TimeoutSeconds to 30 and MaxConcurrencyPerEnvironment to 32.
Each execution environment runs one Node.js event loop, so throughput is bound by CPU per page. Tune the concurrency from your own load tests.
The endpoint has no MaxEnvironments cap by default, so Lambda adds environments as traffic grows.
Lambda answers with 429 instead of scaling when the endpoint reaches MaxEnvironments, if you set one, or one of your account's Web Functions quotas in the Region.
Each environment uses 2 vCPUs of the vCPU quota.
Check your account's limits with aws lambda-web get-web-account-settings before a high-traffic launch, and see scaling behavior and Web Functions quotas for the current values.
The template's endpoint is HomeRegion, so the proxy runs in the stack's Region only. Deploy it in the Region nearest your origin.
Error handling
After the origin fetch, the shared pipeline falls back to the last safe response if a stage throws, so your site stays available if the MonetizationOS API is unreachable or a workflow errors. The Node.js server adds these responses of its own:
| Status | When |
|---|---|
400 | The viewer host or path can't form a valid URL |
403 | ORIGIN_VERIFY_SECRET is set and x-mos-origin-verify is missing or doesn't match |
502 | Any other error, for example the origin fetch failing |
Each request is logged as one JSON line with its Lambda request ID, method, path without the query string, status, and duration.