Guide

Apply a Saved Transform to a Connection

Apply a saved transform to a connection by testing the script, attaching it to one branch, verifying the destination-ready envelope, and debugging or removing the rule.

Use this workflow when an accepted request should still be delivered but one receiver needs different headers, body, query, path, or method.

FastHook keeps the original source request for audit and replay while the selected branch gets its own output and execution history.

Dashboard Shape

In the dashboard, open Connections, choose a route, and enable Transform inside Connection rules. The current editor selects a saved transformation from the dropdown and stores it as a transform rule.

The Editor button opens the saved transformation editor. The connection rule does not hold the script body directly in the dashboard flow; it points to the saved transformation id.

Enable Transform and open Editor to attach a tested transformation to this destination branch.

Choose The Branch Output

A transformation receives a request envelope with headers, body, query, path, and method. Return the full envelope when you want the outbound delivery to keep existing values plus your changes.

FastHook uses the transformed envelope for that connection branch only. Other connections from the same source can deliver the original shape or use a different transformation.

  • headers: add destination-specific headers such as x-provider or x-tenant.
  • body: rename fields, flatten provider envelopes, or add derived fields.
  • query: replace or normalize outbound query parameters.
  • path: send the destination request to a stable receiver path.
  • method: preserve POST unless a receiver contract requires another allowed method.
The source request remains evidence. The destination branch receives the transformed request envelope.

Write The Script

The dashboard transformation editor uses addHandler("transform", ...). The handler receives the current request envelope and a context object. Environment values are exposed through context.env and process.env.

FastHook parses the JavaScript-like DSL into an AST and executes only documented operations. Dashboard Run and production delivery use the same runtime; eval, Function, modules, network calls, and Node.js or browser globals are not available.

Keep scripts deterministic and focused on request shaping. Use destination-specific constants or secrets from the transformation env object instead of hardcoding them in the script.

Transformation code
addHandler("transform", (request, context) => {
  const body = request.body ?? {};

  return {
    ...request,
    headers: {
      ...request.headers,
      "x-provider": "stripe",
      "x-api-key": context.env.API_KEY
    },
    body: {
      provider: "stripe",
      event_id: body.id,
      event_type: body.type,
      object_id: body.data?.object?.id,
      payload: body.data?.object ?? body
    },
    path: "/webhooks/provider-events"
  };
});

DSL Safety And Limits

FastHook validates the complete AST and statically knowable semantics before execution. Unsupported syntax, unavailable globals, blocked properties, unsupported built-in namespace members, and invalid handler registrations fail with a line and column. Execution runs against a cloned envelope so changes are discarded if any later statement fails.

The DSL supports spread, computed properties, destructuring, optional chaining, local functions, switch, for...in, bounded loops, synchronous try/catch/finally, array callbacks, safe regular expressions for String.replace and String.replaceAll, and allowlisted Object, Array, Math, Number, String, Boolean, JSON, Date, URLSearchParams, and RegExp helpers.

Production uses the request ingestion timestamp for context.timestamp, context.now, Date.now(), and new Date(). Manual runs can provide the same timestamp, so default time remains stable during replay. Static aliases are tracked during validation, and lossy output normalization is reported as structured warnings instead of happening silently.

  • No eval, Function constructor, dynamic import, classes, generators, async/await, promises, timers, or network and storage APIs. Regular expressions are limited to the search argument of String.replace and String.replaceAll; test, exec, lookarounds, named groups, backreferences, and unsafe quantifier combinations are unavailable. Use block-scoped const and let declarations; var is rejected.
  • Prototype escape properties constructor, prototype, and __proto__ are blocked.
  • Limits: 100,000 code characters, 25,000 AST nodes, 100,000 execution steps, 5,000 loop iterations, 50,000 intermediate collection operations, call depth 32, value depth 128, 16 MiB input/output, 100,000 input values, 256 KiB/256 entries of environment JSON, plus 512 pattern characters, 10,000 input characters, and 1,000 matches per regular-expression replacement.
  • Environment secrets, common encodings of them, and sensitive structured console keys are redacted from captured console output and thrown errors.
  • Resource and security failures cannot be suppressed by script try/catch.
  • A DSL error prevents destination delivery; FastHook never sends a partially transformed request.

Validate And Migrate Safely

Use dashboard Validate or PUT /v1/transformations/validate before saving. Invalid programs return a stable diagnostic code plus message, line, column, source snippet, and execution statistics when available. The editor uses that position for inline diagnostics and loads autocomplete from the authoritative GET /v1/transformations/capabilities contract.

New and updated records use runtime fasthook-dsl-v1. Earlier records are explicitly marked javascript-legacy instead of silently changing their runtime contract.

  • GET /v1/transformations/compatibility reports each saved program as current, compatible, or incompatible.
  • POST /v1/transformations/compatibility promotes only compatible legacy records; it does not rewrite code or touch incompatible records.
  • The dashboard Audit legacy action exposes the same report and guarded promotion flow.
  • For an exported JSON array or { models: [...] }, run npm run transformations:audit -- transformations.json before rollout.
Validation and compatibility
curl -X PUT "https://api.fasthook.io/v1/transformations/validate" \
  -H "Authorization: Bearer fh_api_xxx" \
  -H "content-type: application/json" \
  -d '{"code":"addHandler(\"transform\", (request) => ({ ...request }));"}'

curl "https://api.fasthook.io/v1/transformations/compatibility" \
  -H "Authorization: Bearer fh_api_xxx"

Create And Test Before Attaching

Create the saved transformation with POST /v1/transformations or upsert by name with PUT /v1/transformations. Then run the code against a realistic request sample before attaching it to production traffic.

For route-specific testing, call GET /v1/connections/:id/latest-input after sending a sample webhook. Use that envelope as the request body for PUT /v1/transformations/run.

Run a saved transformation against latest-input or a sample request before enabling it on the connection.
Create and run
# Create or update the reusable transformation.
curl -X PUT "https://api.fasthook.io/v1/transformations" \
  -H "Authorization: Bearer fh_api_xxx" \
  -H "x-team-id: tm_xxx" \
  -H "content-type: application/json" \
  -d '{
    "name": "add-custom-header",
    "code": "addHandler(\"transform\", (request) => ({ ...request, headers: { ...request.headers, \"x-provider\": \"stripe\" } }));",
    "env": {}
  }'

# Pull the latest input observed by the connection source.
curl "https://api.fasthook.io/v1/connections/web_orders/latest-input" \
  -H "Authorization: Bearer fh_api_xxx" \
  -H "x-team-id: tm_xxx"

# Test the saved transformation against a sample envelope.
curl -X PUT "https://api.fasthook.io/v1/transformations/run" \
  -H "Authorization: Bearer fh_api_xxx" \
  -H "x-team-id: tm_xxx" \
  -H "content-type: application/json" \
  -d '{
    "transformation_id": "trs_add_custom_header",
    "request": {
      "method": "POST",
      "path": "/orders",
      "headers": { "content-type": "application/json" },
      "query": {},
      "body": { "type": "order.created" }
    }
  }'

Attach To A Connection

Attach the tested transformation to the connection whose destination needs the changed payload. In API payloads, use type transform and transformation_id.

FastHook also accepts the legacy type transformation and normalizes it, but new examples should use transform because that is the stored dashboard shape.

  • A connection may contain more than one transform rule.
  • Rules are normalized in the order deduplicate, transform, filter, delay, retry.
  • If a filter also exists, make sure it matches the envelope after the transform rule has run, or preserve the original fields it needs.
  • When replacing the rules array, keep any existing filter, retry, delay, or deduplication rules that should remain active.
Patch connection
curl -X PATCH "https://api.fasthook.io/v1/connections/web_orders" \
  -H "Authorization: Bearer fh_api_xxx" \
  -H "x-team-id: tm_xxx" \
  -H "content-type: application/json" \
  -d '{
    "rules": [
      {
        "type": "transform",
        "transformation_id": "trs_add_custom_header"
      },
      {
        "type": "retry",
        "strategy": "exponential",
        "interval": 1000,
        "count": 5,
        "response_status_codes": ["429", "500-599"]
      }
    ]
  }'

Use Cases

  • Add an x-provider, x-team, or x-route header expected by the receiver.
  • Normalize Stripe, Shopify, GitHub, or custom provider envelopes into one internal event shape.
  • Flatten nested provider payloads for a simple destination service.
  • Move provider-specific fields into a payload object while keeping event_id and event_type stable.
  • Remove noisy fields that downstream consumers should not depend on.
  • Send one destination branch to /webhooks/provider-events while another branch keeps the original path.

Inspect And Debug

Live transformation execution creates an execution record. It links the event, connection id, transformation id, original event data id, transformed event data id, runtime, structured error code, execution statistics, log level, logs, and timestamps.

If transformation code throws, violates the DSL, exceeds a runtime limit, or the referenced transformation is missing, FastHook fails that event branch before destination delivery. The cloned request is discarded atomically. Check the event failure message and transformation executions before looking at receiver logs.

Transformation failures happen before destination delivery, so debug from the event and execution record outward.
Inspection API
curl "https://api.fasthook.io/v1/events/evt_xxx" \
  -H "Authorization: Bearer fh_api_xxx" \
  -H "x-team-id: tm_xxx"

curl "https://api.fasthook.io/v1/transformations/trs_xxx/executions?limit=20&dir=desc" \
  -H "Authorization: Bearer fh_api_xxx" \
  -H "x-team-id: tm_xxx"

curl "https://api.fasthook.io/v1/transformations/trs_xxx/executions/txe_xxx" \
  -H "Authorization: Bearer fh_api_xxx" \
  -H "x-team-id: tm_xxx"

Safety Checklist

  • Preserve provider event ids and idempotency keys.
  • Preserve event type unless the destination contract explicitly renames it.
  • Handle missing optional fields with null-safe access.
  • Avoid depending on array order unless the provider documents it.
  • Keep secrets in transformation env values, not in code.
  • Use only helpers documented for FastHook Transform DSL; do not assume arbitrary JavaScript or Web APIs exist.
  • Prefer lowercase outbound header names for consistency.
  • Test with production-like payloads before enabling a route.

Next