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.
| Argument | Available In | Description |
|---|---|---|
proxyContext | Request and response transforms | Proxy metadata such as proxyId, proxyName, proxyRegion, clientId, method, and path |
proxyRequest | Request and response transforms | The Axios-style request configuration being sent to the target |
targetResponse | Response transforms | The 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).
| Argument | Type | Description |
|---|---|---|
algorithm | string | One of "sha256", "sha384", "sha512" |
key | string | UTF-8 signing key (max 8 KB) |
Returns — .update(data).digest(encoding)
| Argument | Type | Description |
|---|---|---|
data | string | UTF-8 string to sign (max 1 MB per call) |
encoding | string | One 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 type | HTTP call | Filters | Response transform | Meters | Alerts | Logged |
|---|---|---|---|---|---|---|
| Request transform abort | Skipped | Skipped | Skipped | Skipped | Skipped | Yes |
| Response transform abort | Already done | Already done | N/A | Skipped | Skipped | Yes |
Headers added on abort
The platform adds two headers to the abort response:
| Header | Value |
|---|---|
requestrocket-proxy-code | transform-abort |
requestrocket-proxy-error | request-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:
includeapplies the transform when the predicates match.excludeapplies 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, orrequire. - No references to unsupported sandbox globals such as
process,global,globalThis,fetch,XMLHttpRequest,WebSocket,Deno,Bun,eval, orFunction.
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
| Field | Rules |
|---|---|
headers | You 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). |
params | You can add, change, or remove query parameters freely. |
data | You can reshape the request body. Only applied when the final method is POST, PUT, or PATCH. |
url | You can rewrite the relative path (e.g. /v1/items to /v2/items). Must remain a relative path — absolute URLs are rejected. |
method | You 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:
| Field | Why |
|---|---|
baseURL | The target's base URL is locked to the configured target and is never exposed to transform code. |
timeout | Controlled by the platform to prevent gateway timeouts. |
validateStatus | RequestRocket never throws on HTTP status codes — the caller handles status codes. |
responseType | Locked to arraybuffer for binary data integrity. |
maxRedirects | Locked to 0 — the proxy never auto-follows redirects; the caller receives 3xx responses directly. |
paramsSerializer | Automatically selected based on whether final query parameters contain OData $ prefixes. |
| Any other Axios config field | Fields 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
proxyRequestobject with headers and params sanitised (authentication material is never visible). - The
targetResponseafter 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.
| Header | Value |
|---|---|
requestrocket-proxy-code | transform-error |
requestrocket-proxy-error | request-transform-failed or response-transform-failed |
requestrocket-proxy-message | Sanitized 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-errortransform-timeouttransform-memory-exceededtransform-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
paramsrather than appending them to theurlpath. - 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.