Aforo integrates with AWS API Gateway (REST v1) via two complementary paths: a Lambda authorizer for real-time entitlement enforcement, and a CloudWatch Logs subscription filter for asynchronous usage metering. Both are deployed through standard AWS tooling — no agent, no sidecar, no infrastructure change.
aws-api-gateway-metering-architecture.flowLIVE
CLIENT
API Request
HTTPS to API GW
Client calls API Gateway endpoint with X-Tenant-Id and Authorization headers.
0ms
AUTHORIZER
Entitlement
Lambda authorizer
Aforo Lambda checks Redis edge cache for quota + margin. Returns IAM policy.
<5ms
INTEGRATION
Backend
Lambda / HTTP / VPC
API Gateway proxies request to your integration target.
passthrough
CLOUDWATCH
Log Capture
execution logs
API Gateway writes execution log entry to CloudWatch Logs group.
0ms added
FILTER
Subscription
log filter → Lambda
CloudWatch subscription filter triggers the metering Lambda per request log.
The Aforo metering Lambda receives CloudWatch log events from API Gateway, extracts request metadata, and forwards enriched usage events to the Aforo ingest endpoint. Deploy it from the provided source:
terminal
# Clone the AWS metering Lambda
git clone https://github.com/aforoai/aws-lambda-aforo-metering.git
cd aws-lambda-aforo-metering
# Install dependencies
npm install
# Package for deployment
zip -r aforo-metering.zip index.js node_modules/ package.json
Lambda Handler (index.js)
The core handler decodes CloudWatch log events and forwards them to Aforo. The full source is in the repository; here is the key extraction logic:
The handler caches the API key in the Lambda execution context. Secrets Manager is called only on cold start — warm invocations read from memory. This keeps the hot path under 1ms for secret resolution.
API Gateway writes one log entry per request to a CloudWatch Logs group when execution logging is enabled. A subscription filter watches this group and triggers the Aforo Lambda for every new log batch. This is the zero-touch path — no application changes required.
Enable Execution Logging on Your Stage
terminal
# Enable execution logging on the stage (required if not already set)
aws apigateway update-stage \
--rest-api-id YOUR_API_ID \
--stage-name prod \
--patch-operations \
op=replace,path=/accessLogSettings/destinationArn,value=arn:aws:logs:REGION:ACCOUNT:log-group:API-GW-Execution-Logs_YOUR_API_ID/prod \
op=replace,path=/*/*/logging/loglevel,value=INFO \
op=replace,path=/*/*/logging/dataTrace,value=true
# Note the log group name — it follows this pattern:
echo "Log group: API-Gateway-Execution-Logs_YOUR_API_ID/prod"
Create the CloudWatch Subscription Filter
terminal
# Get the Lambda ARN from the previous deploy step
LAMBDA_ARN=$(aws lambda get-function --function-name aforo-metering-aws --query 'Configuration.FunctionArn' --output text)
# Add permission for CloudWatch to invoke the Lambda
aws lambda add-permission \
--function-name aforo-metering-aws \
--statement-id cloudwatch-subscription \
--action lambda:InvokeFunction \
--principal logs.amazonaws.com \
--source-arn arn:aws:logs:REGION:ACCOUNT:log-group:API-Gateway-Execution-Logs_YOUR_API_ID/prod:*
# Create the subscription filter
aws logs put-subscription-filter \
--log-group-name "API-Gateway-Execution-Logs_YOUR_API_ID/prod" \
--filter-name "aforo-metering" \
--filter-pattern "[method, resource, stage, status, latency, integrationStatus, ...]" \
--destination-arn "$LAMBDA_ARN"
INFO
The subscription filter pattern [method, resource, ...] matches structured API Gateway execution logs. CloudWatch batches log events and delivers them to the Lambda within 2-5 seconds of the original request — usage appears in Aforo with a short lag, not in real time. Billing is unaffected: events are timestamped to the original request time.
The Aforo Lambda function requires a minimal IAM execution role. Apply the principle of least privilege — the role needs only CloudWatch Logs write access and Secrets Manager read access for the Aforo API key.
# Create the trust policy for Lambda
cat > trust-policy.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
EOF
# Create the role
aws iam create-role \
--role-name aforo-metering-lambda-role \
--assume-role-policy-document file://trust-policy.json
# Attach the custom policy
aws iam put-role-policy \
--role-name aforo-metering-lambda-role \
--policy-name aforo-metering-permissions \
--policy-document file://aforo-metering-lambda-policy.json
# Store the Aforo API key in Secrets Manager
aws secretsmanager create-secret \
--name "aforo/api-key" \
--secret-string '{"apiKey":"sk_live_your_key_here"}'
WARNING
Never hardcode the Aforo API key in the Lambda source or environment variables. Secrets Manager with the IAM policy above is the correct approach — the key is encrypted at rest, audited on every access, and rotatable without a Lambda redeploy.
Deploy the Lambda function and run a verification request through API Gateway:
Deploy the Lambda
terminal
# Get the role ARN
ROLE_ARN=$(aws iam get-role --role-name aforo-metering-lambda-role --query 'Role.Arn' --output text)
# Create the Lambda function
aws lambda create-function \
--function-name aforo-metering-aws \
--runtime nodejs20.x \
--handler index.handler \
--role "$ROLE_ARN" \
--zip-file fileb://aforo-metering.zip \
--timeout 30 \
--memory-size 256 \
--environment Variables="{AWS_REGION=$(aws configure get region)}" \
--tracing-config Mode=Active
# Verify function is active
aws lambda get-function-configuration \
--function-name aforo-metering-aws \
--query '{State: State, Runtime: Runtime, MemorySize: MemorySize}'
Send a Test Request
terminal
# Make a request through your API Gateway stage
curl -v "https://YOUR_API_ID.execute-api.REGION.amazonaws.com/prod/your-endpoint" \
-H "X-Tenant-Id: test_tenant_123" \
-H "Authorization: Bearer your_customer_api_key"
# Check Lambda logs for successful event forwarding
aws logs tail /aws/lambda/aforo-metering-aws --follow --format short
# Expected log output:
# ingested 1 events
# POST https://ingest.aforo.ai/v1/ingest/batch → 200 OK
# Verify the event arrived in Aforo
curl -s "https://api.aforo.ai/v1/events?tenant_id=test_tenant_123&limit=1" \
-H "Authorization: Bearer sk_live_your_admin_key" | jq .
TROUBLESHOOTING
▸
No Lambda invocations: Verify the CloudWatch subscription filter is attached to the correct log group. Check with: aws logs describe-subscription-filters --log-group-name "..."
▸
Lambda invoked but no Aforo events: Check Lambda logs for parse failures. API Gateway log format varies by logging level — ensure level=INFO and data trace=true.
▸
SecretsManager access denied: Verify the IAM role ARN in the Lambda config matches the role that has the SecretsManager policy attached.
▸
Missing X-Tenant-Id: API Gateway must pass the header through to execution logs. Enable full request logging under Stage → Logs/Tracing → Data Trace.
PRO TIP
CloudWatch subscription filters have a 2-5 second delivery lag. This is expected — usage events appear in Aforo a few seconds after the API request completes. Real-time dashboards in Aforo account for this lag automatically. The event timestamp is set to the original request time, not the delivery time, so billing calculations are always accurate.