RequestRocketRequestRocketDocs
Guides

Request and Response Transforms

Change outbound requests and inbound responses with sandboxed TypeScript or JavaScript

Request and Response Transforms

Transforms let you run small TypeScript or JavaScript functions inside a RequestRocket proxy. Use them when a target API needs a different request shape, or when callers need a normalized response shape.

RequestRocket supports two transform types:

  • Request transforms run after proxy overrides and before RequestRocket sends the request to the target API.
  • Response transforms run after payload filters and before the response is returned or recorded.

Transform code runs in a restricted QuickJS sandbox. It cannot use network access, filesystem access, npm modules, environment variables, process, fetch, require, or imports.

How Transforms Work

Transforms are attached to a proxy. Each transform can either run for every request, or only when its predicates match the current request or response.

Only one request transform and one response transform are selected for each request. When multiple active transforms match, the highest priority value wins.

When To Use Transforms

Use request transforms to:

  • Rename request fields before forwarding to the target.
  • Add target-specific headers, query parameters, or body fields.
  • Convert a caller-friendly payload into a legacy target format.

Use response transforms to:

  • Normalize different upstream response formats into one consistent schema.
  • Add response metadata for callers.
  • Rename or reshape fields after filters have removed data.

For simple fixed additions to every request, use proxy overrides first. Use transforms when the change needs code, branching, or payload restructuring.

Minimum Transform Code

A transform must export a default function or async function. The function can return either an object or a JSON string.

Request no-op

export default async function transform(proxyContext, proxyRequest) {
  return proxyRequest;
}

The returned object can modify headers, params, data, url (as a relative path), and method. All other fields are ignored; they are controlled by the platform and cannot be changed from a transform. Returning the provided proxyRequest unchanged is the safest no-op request transform.

Response no-op

export default async function transform(proxyContext, proxyRequest, targetResponse) {
  return targetResponse;
}

The returned response object must include a numeric status. Returning the provided targetResponse unchanged is the safest no-op response transform.

Runtime Inputs

RequestRocket injects these values into your transform function.

ArgumentAvailable InDescription
proxyContextRequest and response transformsProxy metadata such as proxyId, proxyName, proxyRegion, clientId, method, and path
proxyRequestRequest and response transformsThe Axios-style request configuration being sent to the target
targetResponseResponse transformsThe Axios-style target response after filters have been applied

proxyRequest contains:

{
  method: string;      // editable — GET, POST, PUT, PATCH, DELETE, HEAD, or OPTIONS
  url: string;         // the relative path only (e.g. "/items/123") — you may rewrite this; must stay a relative path
  headers: Record<string, string>;   // editable — deny-listed and auth headers are enforced by the platform
  params?: Record<string, string>;   // editable query parameters
  data?: unknown;                    // editable request body (applied when the final method is POST / PUT / PATCH)
}

Fields not listed above (baseURL, timeout, validateStatus, etc.) are platform-controlled and are ignored even if your transform returns them. The base URL of the target is intentionally hidden from transform code.

targetResponse contains:

{
  status: number;
  statusText: string;
  headers: Record<string, string>;
  data: unknown;
}

Platform Helpers (rr)

RequestRocket injects a small library of platform-provided helper functions into every transform as the variable rr. These are further isolated (not inside the sandbox) — so they can access built-ins safely. No imports or extra setup are needed; rr is always available.

rr.createHmac(algorithm, key)

Computes an HMAC signature. Returns a chainable object with .update(data) and .digest(encoding).

ArgumentTypeDescription
algorithmstringOne of "sha256", "sha384", "sha512"
keystringUTF-8 signing key (max 8 KB)

Returns — .update(data).digest(encoding)

ArgumentTypeDescription
datastringUTF-8 string to sign (max 1 MB per call)
encodingstringOne of "hex", "base64", "base64url"

Multiple .update() calls can be chained before calling .digest(). .digest() may only be called once per chain.

Example — Unleashed-style HMAC-SHA256 query-string signing

Some APIs (for example, Unleashed) require request headers or query strings to be signed with an HMAC-SHA256 signature. Use rr.createHmac to compute that signature inside a request transform:

export default async function transform(proxyContext, proxyRequest) {
  const apiId = "YOUR_API_ID";       // retrieve from proxyContext.proxyId or a fixed value
  const apiKey = "YOUR_API_KEY";     // never hardcode real secrets; configure via target credentials

  // Build the query string to sign (everything after the "?")
  const queryString = proxyRequest.params
    ? new URLSearchParams(proxyRequest.params).toString()
    : "";

  // Compute HMAC-SHA256 over the query string, keyed with the API key
  const signature = rr.createHmac("sha256", apiKey)
    .update(queryString)
    .digest("base64");

  return {
    ...proxyRequest,
    headers: {
      ...proxyRequest.headers,
      "api-auth-id": apiId,
      "api-auth-signature": signature,
    },
  };
}

The rr variable is always available in transform code — you do not need to import it or declare it. Only the string result of .digest() crosses the sandbox boundary; the underlying HMAC object stays on the host.

Transform code is stored and visible to client users.

Aborting Early

Both request and response transforms can short-circuit the proxy pipeline by returning an abort property. When abort is present, RequestRocket returns the abort response directly to the caller without executing any remaining pipeline stages.

Abort response shape

The abort value must be a plain object with at least a numeric status. All other fields are optional:

{
  abort: {
    status: number;                        // required — HTTP status code to return
    statusText?: string;                   // optional
    headers?: Record<string, string>;      // optional — merged with platform headers
    data?: any;                            // optional — response body
  }
}

Request transform abort

When a request transform aborts, the outbound HTTP call to the target is never made. The abort response is returned directly to the caller:

export default async function transform(proxyContext, proxyRequest) {
  // Reject requests for a specific path segment
  if (proxyRequest.url.includes("/admin")) {
    return {
      abort: {
        status: 403,
        data: { error: "Access denied by policy" },
      },
    };
  }
  return proxyRequest;
}

Response transform abort

When a response transform aborts, the target has already been called. The abort response replaces the filtered target response returned to the caller:

export default async function transform(proxyContext, proxyRequest, targetResponse) {
  // Substitute a cached fallback when the target is unavailable
  if (targetResponse.status === 503) {
    return {
      abort: {
        status: 200,
        data: { cached: true, items: [] },
      },
    };
  }
  return targetResponse;
}

Pipeline behaviour on abort

Abort typeHTTP callFiltersResponse transformMetersAlertsLogged
Request transform abortSkippedSkippedSkippedSkippedSkippedYes
Response transform abortAlready doneAlready doneN/ASkippedSkippedYes

Headers added on abort

The platform adds two headers to the abort response:

HeaderValue
requestrocket-proxy-codetransform-abort
requestrocket-proxy-errorrequest-transform-aborted or response-transform-aborted

Any headers you include in the abort object are merged with the platform headers above.

If abort is present but malformed — not a plain object, or missing a numeric status — the transform fails with a transform-invalid-output error instead of aborting.

Creating A Request Transform

Open The Proxy

Go to Proxies, select the proxy, then open Request Transforms.

Create A Transform

Choose whether the transform is active, whether it should apply to all requests or conditional matches, and set a priority.

Write The Code

For example, add a target-specific header and reshape the body:

export default async function transform(proxyContext, proxyRequest) {
  return {
    ...proxyRequest,
    headers: {
      ...proxyRequest.headers,
      "x-source-system": "requestrocket"
    },
    data: {
      customer_id: proxyRequest.data?.customerId,
      plan_code: proxyRequest.data?.plan
    }
  };
}

Save

RequestRocket validates the code before saving it. If validation passes, the transform is stored and becomes available immediately.

Creating A Response Transform

Response transforms receive the final filtered target response. For example, normalize a target response into a caller-facing shape:

export default async function transform(proxyContext, proxyRequest, targetResponse) {
  return {
    ...targetResponse,
    data: {
      id: targetResponse.data?.customer_id,
      name: targetResponse.data?.display_name,
      status: targetResponse.data?.account_status
    }
  };
}

Conditional Transforms

Set transformMode to conditional when a transform should only apply to some traffic. Conditional transforms can match request data such as method, path, query parameters, headers, body fields, and JWT claims.

Response transforms can also match response data such as response headers, response body fields, and response status.

Use effect to decide how matches are interpreted:

  • include applies the transform when the predicates match.
  • exclude applies the transform when the predicates do not match.

Static Validation

The Core API and dashboard reject transform code before it is saved if it fails the minimum static checks.

Validation requires:

  • Non-empty TypeScript or JavaScript under 512 KB.
  • A callable export default.
  • No import, re-export, dynamic import, or require.
  • No references to unsupported sandbox globals such as process, global, globalThis, fetch, XMLHttpRequest, WebSocket, Deno, Bun, eval, or Function.

Static validation catches unsupported code shape early. Runtime validation still checks the returned object after the transform executes.

Security Contract

RequestRocket enforces strict boundaries on what a transform can and cannot change. These protections exist to prevent credential leakage, request routing attacks, and misconfiguration that could break the proxy pipeline.

What request transforms CAN modify

FieldRules
headersYou can add or change custom headers. However, headers on the platform deny-list and authentication headers are always stripped before the request is sent (see below).
paramsYou can add, change, or remove query parameters freely.
dataYou can reshape the request body. Only applied when the final method is POST, PUT, or PATCH.
urlYou can rewrite the relative path (e.g. /v1/items to /v2/items). Must remain a relative path — absolute URLs are rejected.
methodYou can change the outbound HTTP method sent to the target. Allowed values (case-insensitive): GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. Invalid values are rejected with transform-invalid-output. Body and Content-Type follow the final method: a GET rewritten to POST can send data; a POST rewritten to GET drops the body and strips Content-Type. proxyContext.method remains the caller's original method.

What request transforms CANNOT modify

These fields are controlled by the platform and are ignored if your transform returns them:

FieldWhy
baseURLThe target's base URL is locked to the configured target and is never exposed to transform code.
timeoutControlled by the platform to prevent gateway timeouts.
validateStatusRequestRocket never throws on HTTP status codes — the caller handles status codes.
responseTypeLocked to arraybuffer for binary data integrity.
maxRedirectsLocked to 0 — the proxy never auto-follows redirects; the caller receives 3xx responses directly.
paramsSerializerAutomatically selected based on whether final query parameters contain OData $ prefixes.
Any other Axios config fieldFields such as proxy, httpAgent, httpsAgent, adapter, socketPath, auth, or beforeRedirect are always ignored.

Returning an absolute URL (e.g. https://example.com/path or //example.com/path) in the url field will cause the request to be hard-rejected with a transform-invalid-output error. This prevents credential exfiltration to arbitrary hosts.

Header governance

RequestRocket enforces a platform header deny-list. These headers are always removed from the final request, regardless of whether they come from the caller, a proxy override, or your transform:

  • Authentication: Authorization, x-api-key
  • Routing / method override: Host, x-http-method-override, x-method-override, x-original-url, x-rewrite-url, x-forwarded-host, forwarded, x-forwarded-server, x-host
  • Hop-by-hop: Connection, Keep-Alive, Transfer-Encoding, Upgrade, Via, Proxy-Authenticate, Proxy-Authorization, TE, Trailer
  • Forwarding / infrastructure: x-forwarded-for, x-forwarded-proto, x-forwarded-port, Content-Length
  • AWS / CloudFront: x-amzn-trace-id, x-amz-cf-pop, x-amz-cf-id, cloudfront-* headers

Authentication headers are injected exclusively by the configured target credential after your transform runs. A transform cannot inject its own authentication — the credential always wins.

Header names and values must be plain strings. Headers containing control characters (CR/LF) are rejected with a transform-invalid-output error to prevent header injection attacks.

What response transforms CAN see

Response transforms receive:

  • The proxyRequest object with headers and params sanitised (authentication material is never visible).
  • The targetResponse after payload filters have been applied.

Response transforms CAN modify status, headers, and data on the response.

What response transforms CANNOT see

  • The target credential or any injected authentication headers/params.
  • The target base URL.
  • Internal Axios configuration fields.

Sandbox Limits

Each transform runs with:

  • 5 second execution timeout.
  • 32 MB memory limit.
  • No network access.
  • No filesystem access.
  • No npm module access.
  • No access to AWS SDK, environment variables, or the Node.js process.

RequestRocket always removes authentication and infrastructure headers from the request object before passing it to the sandbox.

Runtime Errors

When a transform fails at runtime, RequestRocket returns a structured error response with transform-specific headers.

HeaderValue
requestrocket-proxy-codetransform-error
requestrocket-proxy-errorrequest-transform-failed or response-transform-failed
requestrocket-proxy-messageSanitized failure message

Most transform failures return 422. Memory limit failures return 500.

Response body:

{
  "error": "request-transform-failed",
  "message": "Transform output does not conform to the expected request structure",
  "code": "transform-invalid-output"
}

Possible error codes are:

  • transform-error
  • transform-timeout
  • transform-memory-exceeded
  • transform-invalid-output

Sync And Async APIs

Transforms run for both Proxy API requests and Async API requests. The same proxy transform configuration is used by both paths, so test transform changes against whichever API mode your integration uses.

Best Practices

  • Keep transforms small and deterministic.
  • Return a complete request or response object, not just the fields you changed.
  • Avoid storing secrets in transform code — use the credential configuration for authentication.
  • Use payload filters for redaction and transforms for shape changes.
  • Use conditional predicates instead of branching on every request inside the code when possible.
  • Express query parameters via params rather than appending them to the url path.
  • Never attempt to set authentication headers in your transform — they will be stripped. Configure credentials through the target credential settings instead.
  • If you need to rewrite the path, keep it relative (e.g. /api/v2/resource). Do not include the scheme or host.

Next Steps

On this page