> ## Documentation Index
> Fetch the complete documentation index at: https://repost.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Transform Webhook Requests with Forwarder Functions

> Write TypeScript functions that transform webhook request path, headers, and body before a queued forwarder delivers the event to its destination.

A function is a TypeScript transform attached to one queued forwarder. It receives the original webhook request, lets you change the path, headers, or body, and then hands the modified request back to that forwarder for delivery. Functions run per matching path, so you can apply different logic to different providers without creating separate buckets.

## Availability

Functions run in the asynchronous delivery path and are available only on queued bucket forwarders. If you are working with a proxy bucket, the Functions tab does not appear.

<CardGroup cols={3}>
  <Card title="Queued bucket" icon="clock">
    Supports functions, secrets, retries, replay, and failed-delivery handling.
  </Card>

  <Card title="Proxy bucket" icon="repeat">
    Hides the Functions tab. Traffic is delivered to the single proxy forwarder without any transform step.
  </Card>

  <Card title="Per forwarder" icon="route">
    A function belongs to one forwarder. If two destinations both need the same transform, create the function on each forwarder separately.
  </Card>
</CardGroup>

## Path matching

Each function is tied to a specific path. Repost compares the original incoming webhook path against the function's configured path and runs the function only when there is an exact match.

<AccordionGroup>
  <Accordion title="Exact match required">
    A function configured for `/webhooks/stripe` runs when an event arrives at `/webhooks/stripe`. It does not run for `/webhooks/shopify` or `/webhooks/stripe/events`.
  </Accordion>

  <Accordion title="No match — deliver normally">
    When no enabled, saved function matches the incoming path, the forwarder delivers the original request unchanged.
  </Accordion>

  <Accordion title="Unique paths per forwarder">
    One forwarder cannot have two functions configured with the same path. Each path maps to exactly one function on a given forwarder.
  </Accordion>
</AccordionGroup>

<Note>
  Draft, disabled, and never-saved functions do not transform traffic. Save a valid function and confirm it is enabled before routing provider traffic through it.
</Note>

## Create and publish a function

Creating a function stores the configuration shell. Saving from the editor compiles your code and publishes the version that will process live traffic.

<Steps>
  <Step title="Open the forwarder's Functions tab">
    Navigate to the queued bucket forwarder that should transform matching events and select the **Functions** tab.
  </Step>

  <Step title="Create the function shell">
    Enter a name (up to `100` characters), a path such as `/webhooks/stripe` (up to `200` characters), and an error policy. The default error policy sends transform failures to failed deliveries.
  </Step>

  <Step title="Write your transform in the editor">
    Export a single `transform` function with the signature `export function transform(req: WebhookRequest): TransformResult`. The editor provides type information and `process.env` autocomplete for your forwarder's secrets.
  </Step>

  <Step title="Test with a JSON input">
    Use the default request shape, paste your own JSON, or load a real event from **History** using the **From History** button. Tests run against the forwarder's current secrets. Up to `30` tests per minute are allowed per user.
  </Step>

  <Step title="Save the function">
    Saving formats the source, compiles it, and publishes the new version. If the source has compile errors, those errors appear in the editor and the previously active version continues to handle traffic.
  </Step>
</Steps>

## Function signature and types

Your transform receives a `WebhookRequest` object and must return a `TransformResult`. Both types are provided by the Repost runtime — you do not need to import them.

<AccordionGroup>
  <Accordion title="WebhookRequest fields">
    | Field        | Type                     | Description                                       |
    | ------------ | ------------------------ | ------------------------------------------------- |
    | `method`     | `string`                 | HTTP method of the original request               |
    | `path`       | `string`                 | Path of the original request                      |
    | `query`      | `Record<string, string>` | Parsed query string parameters                    |
    | `headers`    | `Record<string, string>` | Request headers                                   |
    | `body`       | `T` (generic)            | Parsed request body                               |
    | `sourceIp`   | `string`                 | IP address of the incoming request                |
    | `receivedAt` | `string`                 | ISO 8601 timestamp when Repost received the event |
  </Accordion>

  <Accordion title="TransformResult fields">
    | Field     | Type                     | Description                                                        |
    | --------- | ------------------------ | ------------------------------------------------------------------ |
    | `path`    | `string`                 | Path to use for the outbound request                               |
    | `headers` | `Record<string, string>` | Headers to send with the outbound request                          |
    | `body`    | `string \| object`       | Body to send — any JSON-serializable value or a raw string         |
    | `discard` | `true`                   | Return `true` to stop delivery for this forwarder without an error |
  </Accordion>

  <Accordion title="Secrets and helpers">
    Read forwarder secrets with `process.env.KEY`. Missing keys resolve to `undefined`, so always guard required credentials. For signing and verification work, use the built-in `repost.crypto.*` and `repost.jwt.*` helpers. External package imports are not supported.
  </Accordion>
</AccordionGroup>

## Example transform

The following function filters out Stripe events that are missing the `stripe-signature` header, injects an internal auth token, and remaps the delivery path.

```ts theme={"languages":{"custom":["/languages/repost.json"]}}
type StripeEvent = {
  type: string;
  data?: unknown;
};

export function transform(
  req: WebhookRequest<StripeEvent>,
): TransformResult {
  if (!req.headers["stripe-signature"]) {
    return {
      discard: true,
      path: req.path,
      headers: req.headers,
      body: req.body,
    };
  }

  const token = process.env.INTERNAL_WEBHOOK_TOKEN ?? "";

  return {
    path: "/internal/stripe",
    headers: {
      ...req.headers,
      "x-repost-source": "stripe",
      authorization: `Bearer ${token}`,
    },
    body: {
      provider: "stripe",
      receivedAt: req.receivedAt,
      event: req.body,
    },
  };
}
```

<Tip>
  Store the `INTERNAL_WEBHOOK_TOKEN` value as a forwarder secret so it never appears in your function source code. See the [Secrets guide](/docs/forwarders/secrets) for instructions.
</Tip>

## Discarding events

Returning `discard: true` tells Repost to stop delivery for this forwarder without treating the outcome as an error.

<Card title="Discard is a successful function result" icon="circle-off">
  The function's error policy is not applied when you return `discard: true`. Repost marks the forward as discarded and skips delivery for that forwarder. Other forwarders on the same bucket are not affected.
</Card>

## Error policies

The error policy controls what Repost does when your function throws an unhandled error, times out, or otherwise cannot execute. Choose the policy that matches how sensitive your destination is to receiving untransformed events.

<CardGroup cols={3}>
  <Card title="Send to failed deliveries" icon="mail-warning">
    Stops normal delivery and creates a failed-delivery item that you can inspect and replay. This is the default.
  </Card>

  <Card title="Forward as-is" icon="send">
    Skips the transform and delivers the original, unmodified request to the forwarder destination.
  </Card>

  <Card title="Discard" icon="trash">
    Marks the forward discarded and does not deliver it to the destination.
  </Card>
</CardGroup>

## Editor features

<AccordionGroup>
  <Accordion title="From History">
    Load a real recent webhook event as your test input. The dialog lets you filter by `24h`, `7d`, or `30d` windows and supports search.
  </Accordion>

  <Accordion title="Generate Types">
    Generate TypeScript types from the current test input body. If the test input matches the full webhook request shape, the editor uses its `body` field. Generated types are placed between generated-type markers, or prepended as a new block if markers are absent.
  </Accordion>

  <Accordion title="Secrets autocomplete">
    Keys from the forwarder's Secrets page appear as `process.env` suggestions in the editor. Add secrets before writing code that depends on them.
  </Accordion>

  <Accordion title="Format">
    Use the Format button in the editor toolbar to auto-format source before saving. Formatting also removes unused imports.
  </Accordion>

  <Accordion title="Save shortcut">
    Press `Cmd+S` on macOS or `Ctrl+S` on Windows and Linux to save the current source and metadata without leaving the keyboard.
  </Accordion>
</AccordionGroup>

## Function states

<AccordionGroup>
  <Accordion title="Draft">
    The function is enabled but no compiled version has been saved yet. Matching traffic is delivered normally without transformation.
  </Accordion>

  <Accordion title="Active">
    The function is enabled and a saved, compiled version exists. Matching traffic is transformed before delivery.
  </Accordion>

  <Accordion title="Disabled">
    The function remains configured but does not process any traffic. Re-enable it at any time.
  </Accordion>

  <Accordion title="Unsaved or Modified">
    The editor contains source or metadata that has not yet replaced the active version. Test and save before sending live traffic through the function.
  </Accordion>

  <Accordion title="Version numbering">
    A successful source compile increments the function version number. Metadata-only saves — such as changing the name or error policy — do not create a new executable version. Compile errors leave the previous active version in place.
  </Accordion>
</AccordionGroup>
