How to setup APIGEE Instrumentation

Last updated: May 28, 2026

Introduction

This guide walks you through adding a JavaScript policy to your Apigee proxy to capture API request and response data and forward it to the Astra Traffic Collector for security monitoring.

Illustration: High-level integration flow between APIGEE and Astra Traffic Collector


Prerequisites

Before you begin, please ensure you have the following ready:

  • Apigee Access: You must have access to the Apigee UI within Google Cloud.

  • Active Proxy: An existing Apigee proxy where you will attach the security policy.

  • Astra Traffic Collector: Your collector must be installed and reachable from your Apigee environment.

  • Sensor ID: You have created an APIGEE integration in your Astra Dashboard and copied the Sensor ID.

Instructions

Step 1: Set Up OTLP/HTTP Receiver in Astra Traffic Collector

You can configure Astra Traffic Collector to receive OTLP data over HTTP or HTTPS.

📄 How To Set Up OTLP/HTTP Receiver in Astra Traffic Collector

Step 2: Create a JavaScript Policy in APIGEE

  1. Go to your Apigee API Proxy.

  2. Select the Develop tab.

  3. Click + Add Step in the ProxyEndpoint PostFlow section at Response time.

  4. Choose to create a new Policy with type as JavaScript under the Extension Policies.

  5. Provide a name and then upload the javascript policy available here.

Before attaching the policy, modify the configuration variables at the top of the JavaScript file

Variable

Description

Example Value

samplingRatio

Controls sampling frequency (0–1). 1 means 100% sampling.

1

domainName

Your proxy domain name.

"mydomain.nip.io"

sensorId

Sensor ID from Astra dashboard.

"12345678-1234-4abc-9def-987654321000"

astraTrafficCollectorEndpoint

OTLP/HTTP endpoint of the Astra Traffic Collector.

"https://collector.example.com:4318/v1/traces"

var samplingRatio = 1;                   // exampleValue: 0.5
var domainName = "your-domain.com";      // exampleValue: "mydomain.nip.io"
var sensorId = "your-sensor-id";         // exampleValue: "12345678-1234-4abc-9def-987654321000"
var astraTrafficCollectorEndpoint = "";  // exampleValue: "https?://collector-ip-or-domain-endpoint:4318/v1/traces"





//----------------------DO NOT CHANGE THIS. Below is javascript policy. DO NOT CHANGE THIS ----------------------

// Generate a random number between 0 and 1
var randomValue = Math.random();

// Execute only if the random value is = samplingRatio
if (samplingRatio == 1 || randomValue = samplingRatio) {
    // Capture request headers
    var allReqHeaders = {};
    for (var header in request.headers) {
        allReqHeaders[header] = request.headers[header];
    }
    allReqHeaders["host"] = domainName;
    context.setVariable('allReqHeaders', JSON.stringify(allReqHeaders));

    // Capture response headers
    var allRespHeaders = {};
    for (var header in response.headers) {
        allRespHeaders[header] = response.headers[header];
    }
    context.setVariable('allRespHeaders', JSON.stringify(allRespHeaders));

    // Extract path from proxy URL
    var proxyUrl = context.getVariable('proxy.url');
    
    function getPathFromUrl(proxyUrl) {
        var firstDoubleSlash = proxyUrl.indexOf("//");
        if (firstDoubleSlash !== -1) {
            var startIndex = proxyUrl.indexOf("/", firstDoubleSlash + 2);
            return startIndex !== -1 ? proxyUrl.substring(startIndex) : "/";
        }
        return "/";
    }

    var targetPath = getPathFromUrl(proxyUrl);

    // Construct OpenTelemetry payload
    var payload = {
        "resourceSpans": [
            {
                "resource": {
                    "attributes": [
                        { "key": "service.name", "value": { "stringValue": "astra-apigee-js-instrumentation" } },
                        { "key": "sensor.id", "value": { "stringValue": sensorId } },
                        { "key": "sensor.version", "value": { "stringValue": "1.0.0" } },
                    ]
                },
                "scopeSpans": [
                    {
                        "scope": { "name": "instrumentaton" },
                        "spans": [
                            {
                                "traceId": "1234567890abcdef1234567890abcdef",
                                "spanId": "abcdef1234567890",
                                "name": "proxy-endpoint-span",
                                "kind": 1,
                                "startTimeUnixNano": Date.now() * 1e6,
                                "endTimeUnixNano": Date.now() * 1e6,
                                "attributes": [
                                    { "key": "http.method", "value": { "stringValue": context.getVariable('request.verb') } },
                                    { "key": "http.status_code", "value": { "intValue": parseInt(context.getVariable('response.status.code'), 10) } },
                                    { "key": "http.target", "value": { "stringValue": targetPath } },
                                    { "key": "http.host", "value": { "stringValue": domainName } },
                                    { "key": "http.flavor", "value": { "stringValue": context.getVariable('request.version') } },
                                    { "key": "http.scheme", "value": { "stringValue": context.getVariable('client.scheme') } },
                                    { "key": "net.sock.peer.addr", "value": { "stringValue": context.getVariable('client.ip') } },
                                    { "key": "http.request.headers", "value": { "stringValue": context.getVariable('allReqHeaders') } },
                                    { "key": "http.response.headers", "value": { "stringValue": context.getVariable('allRespHeaders') } },
                                    { "key": "http.request.body", "value": { "stringValue": context.getVariable('request.content') } },
                                    { "key": "http.response.body", "value": { "stringValue": context.getVariable('response.content') } }
                                ]
                            }
                        ]
                    }
                ]
            }
        ]
    };

    // Store payload as an Apigee variable
    context.setVariable('OTELPayload', JSON.stringify(payload));

    // Function to handle HTTP response
    function onComplete(response, error) {
        if (response) {
            if (response.status >= 200 && response.status  300) {
                print('Payload sent successfully. Response Status: ' + response.status);
            } else {
                print('Failed to send payload. Status: ' + response.status);
                print('Response Content: ' + response.content);
            }
        } else {
            print('HTTP Request Error: ' + error);
            context.setVariable('jscallout-http-error', 'Error: ' + error);
        }
    }

    // Send payload to OpenTelemetry collector
    var method = 'POST';
    var headers = { 'content-type': 'application/json' };
    var body = context.getVariable('OTELPayload');
    var req = new Request(astraTrafficCollectorEndpoint, method, headers, body);
    httpClient.send(req, onComplete);
}
  1. Click on the policy that just got created and you will be able to see a similar XML that apigee created to attach the JS File to the policy

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Javascript continueOnError="false" enabled="true" timeLimit="500" name="logReqRespPolicy">
  <DisplayName>logReqRespPolicy</DisplayName>
  <Properties/>
  <ResourceURL>jsc://instrument.js</ResourceURL>
</Javascript>

By default, the timeLimit attribute in policy's xml file is set to 200ms. If your Astra Traffic Collector used https secure communication, increase the timeLimit (500ms) to avoid failure of response from your backend service.

Step 3: How the Policy Works

  • Request & Response Headers — Captures all request and response headers.

  • Request & Response Body — Captures the full payload content.

  • Trace Attributes — Generates an OpenTelemetry-compatible payload.

  • Conditional Sampling — Sends only a subset of requests if samplingRatio is less than 1.

  • Outbound Transmission — Sends the payload to Astra Traffic Collector over HTTP(S).


Expected Outcome

Once the policy is attached and saved, Apigee will begin capturing headers and body content for both requests and responses. This data is then formatted as an OpenTelemetry-compatible payload and sent to your Astra Traffic Collector. You can verify this by checking your Astra Dashboard's API Inventory or inspecting the logs of your traffic collector

Troubleshooting

  • Traces not appearing: Double-check that the sensorId and astraTrafficCollectorEndpoint are correctly updated in the JavaScript file.

  • Syntax Errors: Ensure the JavaScript file is correctly referenced in the policy and that no syntax errors were introduced during variable updates.

  • Variable Debugging: Use the context.setVariable() function within Apigee to debug and verify variable values during runtime.

  • Use context.setVariable() to debug variable values.

    For further assistance, refer to the Apigee Documentation.

Further Assistance: If you encounter any issues, please raise a support ticket through your Astra dashboard or contact us at help@getastra.com.

Related Articles

📄 How to setup Astra Traffic Collector in Linux

📄 How to Create Sensor Integration for API Observability