Skip to main content
Supertab Connect integrates with AWS CloudFront for two purposes: serving your RSL license at /license.xml via your domain, and enforcing the Crawler Authentication Protocol (CAP) using Lambda@Edge and CloudFront Functions.

RSL License Deployment

Your RSL license needs to be accessible at https://yourdomain.com/license.xml. CloudFront proxies this path to the Supertab Connect origin using a CloudFront Function for URI rewriting, a new origin, and a dedicated cache behavior.

CloudFront Function

Create a function with runtime cloudfront-js-2.0. This runs on viewer request and rewrites the URI before CloudFront selects the origin.
function handler(event) {
  var request = event.request;
  var merchantURN = "YOUR_WEBSITE_URN";
  request.uri = "/merchants/systems/" + merchantURN + request.uri;
  return request;
}
Publish the function after saving.

Origin

Add an origin to your distribution:
Origin domain:  api-connect.supertab.co
Protocol:       HTTPS only
Name:           supertab-connect-origin

Cache Behavior

Create a cache behavior for /license.xml:
SettingValue
Path pattern/license.xml
Originsupertab-connect-origin
Viewer protocol policyRedirect HTTP to HTTPS
Cache policyCachingOptimized
Origin request policyAllViewerExceptHostHeader
Viewer request functionYour published CloudFront Function
Use AllViewerExceptHostHeader, not AllViewer. The SDK needs viewer headers like User-Agent for bot detection, but forwarding the Host header causes origin routing failures.
The /license.xml behavior must sit above the default * behavior in the behaviors list. Deployment takes 10–15 minutes after saving.
If you manage your distribution with Terraform, use this configuration instead of the manual steps above.
resource "aws_cloudfront_function" "supertab_rewrite_license_path" {
  name    = "supertab-rewrite-license-path"
  runtime = "cloudfront-js-2.0"
  publish = true
  comment = "Rewrites /license.xml to the Supertab Connect URN path"

  code = <<-EOT
function handler(event) {
  var request = event.request;
  var merchantURN = "YOUR_WEBSITE_URN";
  request.uri = "/merchants/systems/" + merchantURN + request.uri;
  return request;
}
EOT
}

resource "aws_cloudfront_distribution" "your_distribution" {
  # ... your existing config ...

  origin {
    domain_name = "api-connect.supertab.co"
    origin_id   = "supertab-connect-origin"

    custom_origin_config {
      http_port              = 80
      https_port             = 443
      origin_protocol_policy = "https-only"
      origin_ssl_protocols   = ["TLSv1.2"]
    }
  }

  ordered_cache_behavior {
    path_pattern     = "/license.xml"
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "supertab-connect-origin"

    forwarded_values {
      query_string = false
      headers      = ["Origin"]
      cookies { forward = "none" }
    }

    function_association {
      event_type   = "viewer-request"
      function_arn = aws_cloudfront_function.supertab_rewrite_license_path.arn
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 86400
    max_ttl                = 31536000
    compress               = true
  }

  depends_on = [aws_cloudfront_function.supertab_rewrite_license_path]
}

CAP Enforcement

CloudFront CAP deployment combines two edge features:
  • CloudFront Function (viewer request): Identifies Authorization: License headers on every request, including cache hits, without adding latency for regular traffic.
  • Lambda@Edge (origin request): Runs the Supertab Connect SDK to verify the token and record usage. Fires only on cache misses.
CloudFront Functions fire on every request including cache hits. Lambda@Edge origin-request fires only on cache misses. This distinction matters for billing and completeness of usage recording.
Lambda@Edge functions must be deployed in us-east-1.

Prerequisites

You need Node.js 20+, npm, and the AWS CLI configured (aws configure or aws login). If you have PowerUserAccess, that covers all IAM requirements. Otherwise, the deploying user needs a scoped policy.
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "IAMRole",
      "Effect": "Allow",
      "Action": ["iam:CreateRole", "iam:GetRole", "iam:AttachRolePolicy", "iam:PassRole"],
      "Resource": "arn:aws:iam::*:role/supertab-edge-verify*"
    },
    {
      "Sid": "AllowServiceLinkedRoleForLambdaEdge",
      "Effect": "Allow",
      "Action": "iam:CreateServiceLinkedRole",
      "Resource": "arn:aws:iam::*:role/aws-service-role/replicator.lambda.amazonaws.com/*",
      "Condition": {
        "StringEquals": {
          "iam:AWSServiceName": "replicator.lambda.amazonaws.com"
        }
      }
    },
    {
      "Sid": "LambdaEdge",
      "Effect": "Allow",
      "Action": [
        "lambda:CreateFunction", "lambda:UpdateFunctionCode", "lambda:GetFunction",
        "lambda:GetFunctionConfiguration", "lambda:PublishVersion",
        "lambda:AddPermission", "lambda:EnableReplication*"
      ],
      "Resource": "arn:aws:lambda:us-east-1:*:function:supertab-verify*"
    },
    {
      "Sid": "CloudFrontFunctionCreate",
      "Effect": "Allow",
      "Action": ["cloudfront:CreateFunction", "cloudfront:ListFunctions"],
      "Resource": "*"
    },
    {
      "Sid": "CloudFrontFunctionManage",
      "Effect": "Allow",
      "Action": [
        "cloudfront:GetFunction", "cloudfront:DescribeFunction",
        "cloudfront:UpdateFunction", "cloudfront:PublishFunction"
      ],
      "Resource": "arn:aws:cloudfront::*:function/supertab-*"
    },
    {
      "Sid": "CloudFrontCachePolicyManage",
      "Effect": "Allow",
      "Action": ["cloudfront:ListCachePolicies", "cloudfront:CreateCachePolicy"],
      "Resource": "*"
    },
    {
      "Sid": "CloudFrontDistributionManage",
      "Effect": "Allow",
      "Action": [
        "cloudfront:GetDistribution", "cloudfront:GetDistributionConfig",
        "cloudfront:UpdateDistribution"
      ],
      "Resource": "arn:aws:cloudfront::*:distribution/YOUR_DISTRIBUTION_ID"
    }
  ]
}

Step 1: Build the Lambda Package

mkdir supertab-verify && cd supertab-verify
npm init -y
npm install @getsupertab/supertab-connect-sdk
npm install -D esbuild typescript @types/aws-lambda
Create index.ts:
import { SupertabConnect } from "@getsupertab/supertab-connect-sdk";
import type { CloudFrontRequestEvent, CloudFrontRequestResult } from "aws-lambda";

export async function handler(
  event: CloudFrontRequestEvent
): Promise<CloudFrontRequestResult> {
  return SupertabConnect.cloudfrontHandleRequests(event, {
    apiKey: "YOUR_MERCHANT_API_KEY", // from your Supertab Connect dashboard
  });
}
Add build scripts to package.json:
{
  "scripts": {
    "build": "esbuild index.ts --bundle --platform=node --target=node20 --outfile=dist/index.js --format=cjs",
    "package": "cd dist && zip -r function.zip index.js",
    "bundle": "npm run build && npm run package"
  }
}
Build:
npm run bundle
This produces dist/function.zip.

Step 2: Deploy to AWS

IAM execution role — Create a role that both Lambda and CloudFront’s edge service can assume. Attach AWSLambdaBasicExecutionRole for CloudWatch logging. Trust policy:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": ["lambda.amazonaws.com", "edgelambda.amazonaws.com"]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
Lambda function — Create the function in us-east-1 (required for Lambda@Edge — CloudFront replicates it globally from there). Upload dist/function.zip with nodejs20.x runtime and the execution role from above. Publish a version — Lambda@Edge requires a published, numbered version ARN. Do not attach $LATEST. After publishing, grant CloudFront permission to invoke the function at edge locations (lambda:GetFunction and lambda:InvokeFunction for the edgelambda.amazonaws.com principal). Save the published version ARN — you need it in Step 4.
If you update your handler code later, upload the new zip, publish a new version, and update the behavior to point to the new version ARN.

Step 3: Create the Filtering Function

Create a CloudFront Function (cloudfront-js-2.0 runtime) that runs on viewer request. It checks whether the Authorization header contains a license token. If it does, it adds x-license-auth to the request headers — this header is used in the cache key to separate licensed and unlicensed cache entries, and its presence triggers the Lambda@Edge function on cache misses.
function isLicenseRequest(request) {
  var authHeader = (request.headers || {}).authorization;
  var authValue = authHeader && authHeader.value ? authHeader.value : "";
  return authValue.length > 7 && authValue.slice(0, 8).toLowerCase() === "license ";
}

function handler(event) {
  var request = event.request;
  var headers = request.headers || {};

  if (isLicenseRequest(request)) {
    request.headers["x-license-auth"] = { value: event.context.requestId };
    request.headers["x-original-request-url"] = {
      value: (headers.host && headers.host.value ? headers.host.value : "") + request.uri,
    };
  }

  return request;
}
Publish the function after saving.

Step 4: Configure the Cache Behavior

Edit the behavior for the path you want to protect (the default * behavior for all requests, or a specific path like /articles/*). Cache policy — Create a custom cache policy with x-license-auth included in the cache key headers. This ensures that requests with and without a license token produce separate cache entries — without it, a cached response from a licensed request could be served to unlicensed requests. Keep defaults for TTL, query strings, cookies, and compression. Origin request policy — Set to AllViewerExceptHostHeader. This forwards viewer headers (including User-Agent for bot detection) to the origin while letting CloudFront set the correct origin hostname.
Do not use AllViewer — it forwards the original Host header, which causes routing failures on S3 and API Gateway origins.
Function associations:
EventTypeValue
Viewer requestCloudFront FunctionsYour published filtering function from Step 3
Origin requestLambda@EdgePublished version ARN from Step 2
Save and wait for the distribution to deploy (10–15 minutes).

Deploy in Your CDN

CDN-agnostic guide covering RSL serving, CAP enforcement, and robots.txt.

Other CDNs

Generic CDN patterns for platforms not listed above.