Author picture Phil Leggetter

Transformation & Filter Ordering: A New Way to Control Your Event Flow

Published

Article image

One of the most common points of friction we've heard from developers is about the fixed order of filter and transformation rules in Hookdeck connections.

Previously, transformations always ran before filters. This worked for many cases, but not all. For example, if you wanted to filter based on something inside the payload (like the event type from Stripe), your transformation had to handle every incoming event, even if it was going to be discarded.

We also heard from teams who include extra fields in the body or add custom headers purely to help with routing or filtering decisions. These fields aren't meant to be delivered to downstream systems. Sometimes because they contain internal metadata, other times because they're just not useful to the target service. But since filtering happened after the transformation, it wasn't possible to use that data for filtering and then remove it before sending the event to its destination.

That didn't feel right. So, we've changed it.

Why this change matters

This update gives you more control over your event flow by letting you decide whether to transform before or after filtering. You can now avoid unnecessary processing, simplify transformation logic, and use filter-only metadata (like internal headers) without accidentally leaking it downstream.

What are Filters and Transformations?

For those new to the Hookdeck Event Gateway, here's a quick refresher:

A Filters allows you to conditionally route events based on their content. For example, only allowing events with a specific type to pass through.

A Transformations lets you modify the event payload before delivery. For example, restructuring the payload, or adding a new field to the header.

Reordering transformations and filters

You can now control whether a transformation runs before or after a filter in your connection rule set.

The order is respected in both the dashboard UI and the API. In the UI, you'll see drag handles next to each rule. Just move the transformation above or below the filter to set the execution order.

Drag and drop transformations and filters in the Hookdeck dashboard

Change to API and Terraform behavior

Starting with version 2025-07-01 of the Hookdeck API, the order of Filter and Transformation rules is determined by their position in the rules[] array. Previously, filters always ran before transformations. Existing connections are unaffected. Hookdeck preserves the rule order and explicitly represents it in the connection object.

To support this change within the Hookdeck Terraform Provider the rules property in the hookdeck_connection resource is an ordered list from the v2.0.0 release, allowing you to specify the order of execution. Please ensure that transformation rules are placed before filter rules in the rules array to maintain existing behavior. See the v1.x to v2.0 migration guide for more details.

Using transformations after filters

Supporting transformations running after filters makes it easier to write transformations that are clean, focused, and only run when needed.

Let's say you're consuming events from Stripe and only want to process invoice.updated events. Before this change, you had two choices:

  1. Let your transformation run on every event and check inside the code whether to do anything
  2. Split event types into separate connections

Now you can define a body filter like this:

{
  "type": "invoice.updated"
}

This expression matches events whose body has a top-level type field with the value "invoice.updated".

Then, add a transformation that assumes the filter has already run:

addHandler("transform", (request, context) => {
  request.body.transformed = true;
  // Perform logic on the `invoice.updated` payload
  return request;
});

This keeps your transformation logic simple and avoids unnecessary execution for irrelevant events.

It also gives you control when filtering depends on data you don't want to deliver. For example, you might filter using a temporary header or an internal field added upstream, but want to make sure it doesn't get passed on to your destination. With the transformation placed after the filter, that cleanup step becomes straightforward.

addHandler("transform", (request, context) => {
  // Remove internal header
  delete request.headers["x-internal-data"];
  return request;
});

You could apply the same logic to remove any unnecessary fields from verbose payloads before the event is sent downstream.

Previously, solving this required chaining multiple connections or duplicating logic across routes.

When to transform before filtering

While this update gives you the option to run transformations after filters, there are still valid reasons to keep transformations before filters in many setups.

Normalising payloads for consistent filtering

If your event payloads come from multiple sources with slightly different structures, you might want to standardise them early:

addHandler("transform", (request, context) => {
  if (request.body.event_type) {
    request.body.type = request.body.event_type;
  }
  return request;
});

Then your filter can match on type, regardless of the original source format.

Computing derived values for simpler filters

You may want to perform more complex logic in a transformation and add a new field that simplifies filtering:

addHandler("transform", (request, context) => {
  const status = request.body.status;
  request.body.should_alert = status === "failed" || status === "timeout";
  return request;
});

Then define a body filter like:

{
  "should_alert": true
}

This keeps filters declarative and avoids duplicating logic in the UI or config.

Transforming before filtering is useful when:

  • You're standardising or normalising payloads
  • You want to reduce filter complexity by precomputing values
  • You need to clean up or extract nested data before matching

The key difference now is that you can decide whether filtering should happen before or after that logic, depending on your use case.

How to use it

You don't need to change anything if your current setup works as is. But if you've been running into the limitations described above, here's how to take advantage of the update:

  1. Open a connection in the Hookdeck dashboard
  2. Drag the rules to reorder them as needed
  3. Or update the Connection rules[] array via the API with the desired sequence

Hookdeck will execute the rules in the order you define.

🎥 Here's a short walkthrough of the new feature in action:

What's next

We're continuing to look at ways to make rule logic and transformations more expressive, composable, and debuggable. If you've got feedback or edge cases this still doesn't cover, we'd like to hear them.

Let us know how this fits into your workflow. If you're using this in a creative way, we'd love to hear about it.