# Webhook Forward Proxy

Outpost can route outgoing webhook traffic through an HTTP forward proxy. Common reasons:

* Static-IP egress — destinations that allowlist a specific source IP
* Network isolation — keep delivery workers off the public internet
* Centralized egress policy — single chokepoint for outbound traffic

The proxy applies to every webhook destination served by the deployment.

## Configuration

| Env var | Description |
| --- | --- |
| `DESTINATIONS_WEBHOOK_PROXY_URL` | Proxy URL, e.g. `http://user:pass@proxy.example.com:8080`. Supports basic auth. |

When set, Outpost installs an HTTP proxy on the webhook publisher's transport. HTTPS destinations use the standard `CONNECT` tunneling flow; HTTP destinations are forwarded request-by-request.

## Failure attribution

Outpost separates failures the proxy is responsible for from failures the destination is responsible for:

* Proxy-side failures (proxy unreachable, proxy auth rejected) are not charged to the destination. The message is redelivered by the queue, so a short proxy outage is transparent to the destination.
* Destination-side failures reported via the proxy (DNS failure, connection refused, upstream timeout) are recorded as a normal delivery attempt with the matching network error code, the same as if no proxy were in path.

If you expect proxy outages longer than your queue's redelivery window, raise `MinRetryBackoff`, `MaxRetryBackoff`, and `RetryLimit` in your queue configuration so the redelivery window covers the worst case. See [Configuration](/docs/outpost/self-hosting/configuration).

## Using Envoy

When the proxy is [Envoy](https://www.envoyproxy.io/), Outpost automatically reads `x-envoy-response-flags` from proxy responses and maps them to the same network error codes used elsewhere (e.g. `UT` → `timeout`, `DF` → `dns_error`). No Outpost-side configuration is required, but Envoy must be configured to emit the header.

Add the response-flag header on the route configuration (not the HTTP connection manager — Envoy rejects it there):

```yaml
route_config:
  response_headers_to_add:
    - header:
        key: "x-envoy-response-flags"
        value: "%RESPONSE_FLAGS%"
      append_action: OVERWRITE_IF_EXISTS_OR_ADD
    - header:
        key: "x-envoy-response-code-details"
        value: "%RESPONSE_CODE_DETAILS%"
      append_action: OVERWRITE_IF_EXISTS_OR_ADD
  virtual_hosts:
    # ...

```

`OVERWRITE_IF_EXISTS_OR_ADD` prevents a destination from spoofing the header. The `response-code-details` header is optional but gives operators a precise stage/reason string in logs.

### Minimal reference Envoy

A minimal forward-proxy Envoy listener with response-flag reporting enabled:

```yaml
admin:
  address:
    socket_address: { address: 127.0.0.1, port_value: 9901 }

static_resources:
  listeners:
    - name: forward_proxy
      address:
        socket_address: { address: 0.0.0.0, port_value: 10000 }
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                stat_prefix: forward_proxy
                codec_type: AUTO
                http_filters:
                  - name: envoy.filters.http.dynamic_forward_proxy
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.FilterConfig
                      dns_cache_config:
                        name: dfp_cache
                        dns_lookup_family: V4_ONLY
                  - name: envoy.filters.http.router
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                upgrade_configs:
                  - upgrade_type: CONNECT
                route_config:
                  response_headers_to_add:
                    - header:
                        key: "x-envoy-response-flags"
                        value: "%RESPONSE_FLAGS%"
                      append_action: OVERWRITE_IF_EXISTS_OR_ADD
                    - header:
                        key: "x-envoy-response-code-details"
                        value: "%RESPONSE_CODE_DETAILS%"
                      append_action: OVERWRITE_IF_EXISTS_OR_ADD
                  virtual_hosts:
                    - name: proxy
                      domains: ["*"]
                      routes:
                        - match: { connect_matcher: {} }
                          route:
                            cluster: dfp_cluster
                            upgrade_configs:
                              - upgrade_type: CONNECT
                                connect_config: {}
                        - match: { prefix: "/" }
                          route: { cluster: dfp_cluster }

  clusters:
    - name: dfp_cluster
      lb_policy: CLUSTER_PROVIDED
      cluster_type:
        name: envoy.clusters.dynamic_forward_proxy
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig
          dns_cache_config:
            name: dfp_cache
            dns_lookup_family: V4_ONLY

```

Whether you need proxy authentication and TLS depends on your network topology: if Outpost and the proxy share a private network, neither is strictly required; if the proxy is reachable over the public internet, both are strongly recommended to prevent the proxy being used as an open relay.