Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,12 @@

# Webhook Destination
# DESTINATIONS_WEBHOOK_HEADER_PREFIX="x-outpost-"
# DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_EVENT_ID_HEADER=false
# DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_SIGNATURE_HEADER=false
# DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TIMESTAMP_HEADER=false
# DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TOPIC_HEADER=false
# Header name configs are three-state: unset uses the default <prefix>+key,
# an explicit value pins that exact name, and an empty string disables the header.
# DESTINATIONS_WEBHOOK_EVENT_ID_HEADER_NAME=""
# DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_NAME=""
# DESTINATIONS_WEBHOOK_TIMESTAMP_HEADER_NAME=""
# DESTINATIONS_WEBHOOK_TOPIC_HEADER_NAME=""
# DESTINATIONS_WEBHOOK_SIGNATURE_CONTENT_TEMPLATE={{.Body}}
# DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_TEMPLATE=v0={{.Signatures | join ","}}
# DESTINATIONS_WEBHOOK_SIGNATURE_ENCODING="hex"
Expand Down
30 changes: 23 additions & 7 deletions docs/apis/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3119,14 +3119,12 @@ components:
type: string
DESTINATIONS_INCLUDE_MILLISECOND_TIMESTAMP:
type: string
DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_EVENT_ID_HEADER:
type: string
DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_SIGNATURE_HEADER:
type: string
DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TIMESTAMP_HEADER:
type: string
DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TOPIC_HEADER:
DESTINATIONS_WEBHOOK_EVENT_ID_HEADER_NAME:
type: string
description: >-
Complete name of the event ID header. Unset uses the default
"<prefix>event-id"; an explicit value pins that exact name; an empty
string disables the header. Only applies to "default" mode.
DESTINATIONS_WEBHOOK_HEADER_PREFIX:
type: string
DESTINATIONS_WEBHOOK_MODE:
Expand All @@ -3139,10 +3137,28 @@ components:
type: string
DESTINATIONS_WEBHOOK_SIGNATURE_ENCODING:
type: string
DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_NAME:
type: string
description: >-
Complete name of the signature header. Unset uses the default
"<prefix>signature"; an explicit value pins that exact name; an
empty string disables the header. Only applies to "default" mode.
DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_TEMPLATE:
type: string
DESTINATIONS_WEBHOOK_SIGNING_SECRET_TEMPLATE:
type: string
DESTINATIONS_WEBHOOK_TIMESTAMP_HEADER_NAME:
type: string
description: >-
Complete name of the timestamp header. Unset uses the default
"<prefix>timestamp"; an explicit value pins that exact name; an
empty string disables the header. Only applies to "default" mode.
DESTINATIONS_WEBHOOK_TOPIC_HEADER_NAME:
type: string
description: >-
Complete name of the topic header. Unset uses the default
"<prefix>topic"; an explicit value pins that exact name; an empty
string disables the header. Only applies to "default" mode.
HTTP_USER_AGENT:
type: string
IDGEN_ATTEMPT_PREFIX:
Expand Down
20 changes: 14 additions & 6 deletions docs/content/destinations/webhook.mdoc
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ The request body contains the event's `data` field as JSON. The `metadata` field

Webhook delivery is **at-least-once** (see [Event delivery & retries](/docs/outpost/features/event-delivery)). Your handler should deduplicate using the **event id** — the same stable `id` you set when [publishing](/docs/outpost/publishing/events) (Outpost may redeliver on retries).

In **default** mode, that id is sent as the system **`event-id`** metadata field. The HTTP header name is **`{header_prefix}{metadata-key}`** with **no extra separator** — the prefix and key are concatenated as-is. So the default prefix `x-outpost-` plus `event-id` yields **`X-Outpost-Event-Id`**; a prefix without a trailing separator (for example `x-acme`) would produce `x-acme` + `event-id` → **`X-Acmeevent-Id`** (Go canonicalizes the wire name). Include a trailing hyphen in the prefix when you want a conventional shape like **`X-Acme-Event-Id`**. Change the prefix with **`DESTINATIONS_WEBHOOK_HEADER_PREFIX`**, or omit this header with **`DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_EVENT_ID_HEADER`**.
In **default** mode, that id is sent as the system **`event-id`** metadata field. The HTTP header name is **`{header_prefix}{metadata-key}`** with **no extra separator** — the prefix and key are concatenated as-is. So the default prefix `x-outpost-` plus `event-id` yields **`X-Outpost-Event-Id`**; a prefix without a trailing separator (for example `x-acme`) would produce `x-acme` + `event-id` → **`X-Acmeevent-Id`** (Go canonicalizes the wire name). Include a trailing hyphen in the prefix when you want a conventional shape like **`X-Acme-Event-Id`**. Change the prefix with **`DESTINATIONS_WEBHOOK_HEADER_PREFIX`**, pin the full header name with **`DESTINATIONS_WEBHOOK_EVENT_ID_HEADER_NAME`**, or omit this header by setting that variable to an empty string.

In **Standard Webhooks** mode, the same value is sent as the **`webhook-id`** header (default prefix `webhook-`, so typically **`Webhook-Id`**) per the [Standard Webhooks](https://www.standardwebhooks.com/) specification.

Expand Down Expand Up @@ -271,18 +271,26 @@ Set `DESTINATIONS_WEBHOOK_PROXY_URL` to your proxy URL (basic auth supported). S

{% tabs tabGroup="deployment" %}
{% tab label="Managed" %}
Configure webhook operator behavior using these keys in the Config API or in [Hookdeck Destinations settings](https://dashboard.hookdeck.com/settings/project/destinations): `DESTINATIONS_WEBHOOK_HEADER_PREFIX`, `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_EVENT_ID_HEADER`, `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TIMESTAMP_HEADER`, `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TOPIC_HEADER`, `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_SIGNATURE_HEADER`, `DESTINATIONS_WEBHOOK_MODE`, `DESTINATIONS_WEBHOOK_SIGNATURE_ALGORITHM`, `DESTINATIONS_WEBHOOK_SIGNATURE_ENCODING`, `DESTINATIONS_WEBHOOK_SIGNATURE_CONTENT_TEMPLATE`, `DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_TEMPLATE`, and `DESTINATIONS_WEBHOOK_MAX_RESPONSE_BODY_BYTES`.
Configure webhook operator behavior using these keys in the Config API or in [Hookdeck Destinations settings](https://dashboard.hookdeck.com/settings/project/destinations): `DESTINATIONS_WEBHOOK_HEADER_PREFIX`, `DESTINATIONS_WEBHOOK_EVENT_ID_HEADER_NAME`, `DESTINATIONS_WEBHOOK_TIMESTAMP_HEADER_NAME`, `DESTINATIONS_WEBHOOK_TOPIC_HEADER_NAME`, `DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_NAME`, `DESTINATIONS_WEBHOOK_MODE`, `DESTINATIONS_WEBHOOK_SIGNATURE_ALGORITHM`, `DESTINATIONS_WEBHOOK_SIGNATURE_ENCODING`, `DESTINATIONS_WEBHOOK_SIGNATURE_CONTENT_TEMPLATE`, `DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_TEMPLATE`, and `DESTINATIONS_WEBHOOK_MAX_RESPONSE_BODY_BYTES`.
{% /tab %}
{% tab label="Self-Hosted" %}
### Header Settings

| Variable | Default | Description |
|----------|---------|-------------|
| `DESTINATIONS_WEBHOOK_HEADER_PREFIX` | `x-outpost-` / `webhook-` | Prefix for system webhook headers (event id, topic, timestamp, signature). Unless overridden, defaults to **`x-outpost-`** when `DESTINATIONS_WEBHOOK_MODE` is `default` and **`webhook-`** when `standard`. |
| `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_EVENT_ID_HEADER` | `false` | Disable the event ID header |
| `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TIMESTAMP_HEADER` | `false` | Disable the timestamp header |
| `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TOPIC_HEADER` | `false` | Disable the topic header |
| `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_SIGNATURE_HEADER` | `false` | Disable the signature header |
| `DESTINATIONS_WEBHOOK_EVENT_ID_HEADER_NAME` | — | Complete name of the event ID header. Unset uses the default `<prefix>event-id`; an explicit value pins that exact name; an empty string disables the header. |
| `DESTINATIONS_WEBHOOK_TIMESTAMP_HEADER_NAME` | — | Complete name of the timestamp header. Unset uses the default `<prefix>timestamp`; an explicit value pins that exact name; an empty string disables the header. |
| `DESTINATIONS_WEBHOOK_TOPIC_HEADER_NAME` | — | Complete name of the topic header. Unset uses the default `<prefix>topic`; an explicit value pins that exact name; an empty string disables the header. |
| `DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_NAME` | — | Complete name of the signature header. Unset uses the default `<prefix>signature`; an explicit value pins that exact name; an empty string disables the header. |

{% callout type="warning" %}
The `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_*_HEADER` flags are deprecated and will be removed in a future version. Disable a header by setting its corresponding `*_HEADER_NAME` variable to an empty string instead.
{% /callout %}

{% callout type="info" %}
Header names are matched case-insensitively. Outpost rejects a configuration where two system headers resolve to the same name (for example pinning both event id and topic to `x-acme`). Also avoid pinning a system header to a name you also set via per-destination `custom_headers` — the last write wins and the result is not guaranteed.
{% /callout %}

### Signature Settings

Expand Down
8 changes: 4 additions & 4 deletions docs/content/guides/migrate-to-outpost.mdoc
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@ For walkthroughs, see the [curl](/docs/outpost/quickstarts/hookdeck-outpost-curl
| YAML | Environment Variable | Default Value |
| ---------------------------------- | ------------------------------------------------------- | -------------------------------- |
| `header_prefix` | `DESTINATIONS_WEBHOOK_HEADER_PREFIX` | `x-outpost` |
| `disable_default_event_id_header` | `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_EVENT_ID_HEADER` | `false` |
| `disable_default_signature_header` | `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_SIGNATURE_HEADER` | `false` |
| `disable_default_timestamp_header` | `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TIMESTAMP_HEADER` | `false` |
| `disable_default_topic_header` | `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TOPIC_HEADER` | `false` |
| `event_id_header_name` | `DESTINATIONS_WEBHOOK_EVENT_ID_HEADER_NAME` | — |
| `signature_header_name` | `DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_NAME` | — |
| `timestamp_header_name` | `DESTINATIONS_WEBHOOK_TIMESTAMP_HEADER_NAME` | — |
| `topic_header_name` | `DESTINATIONS_WEBHOOK_TOPIC_HEADER_NAME` | — |
| `signature_content_template` | `DESTINATIONS_WEBHOOK_SIGNATURE_CONTENT_TEMPLATE` | `{{.Body}}` |
| `signature_header_template` | `DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_TEMPLATE` | `v0={{.Signatures \| join ","}}` |
| `signature_encoding` | `DESTINATIONS_WEBHOOK_SIGNATURE_ENCODING` | `hex` |
Expand Down
12 changes: 8 additions & 4 deletions docs/content/self-hosting/configuration.mdoc
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,18 @@ Choose one for event log persistence:
|----------|---------|-------------|
| `DESTINATIONS_WEBHOOK_MODE` | `default` | Set to `standard` for Standard Webhooks compliance |
| `DESTINATIONS_WEBHOOK_HEADER_PREFIX` | `x-outpost-` / `webhook-` | Prefix for system webhook headers (event id, topic, timestamp, signature). Unless overridden, defaults to **`x-outpost-`** when `DESTINATIONS_WEBHOOK_MODE` is `default` and **`webhook-`** when `standard`. |
| `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_EVENT_ID_HEADER` | `false` | Disable the event ID header |
| `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TIMESTAMP_HEADER` | `false` | Disable the timestamp header |
| `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TOPIC_HEADER` | `false` | Disable the topic header |
| `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_SIGNATURE_HEADER` | `false` | Disable the signature header |
| `DESTINATIONS_WEBHOOK_EVENT_ID_HEADER_NAME` | | Complete name of the event ID header. Unset uses the default `<prefix>event-id`; an explicit value pins that exact name; an empty string disables the header. Only applies to `default` mode. |
| `DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_NAME` | | Complete name of the signature header. Unset uses the default `<prefix>signature`; an explicit value pins that exact name; an empty string disables the header. Only applies to `default` mode. |
| `DESTINATIONS_WEBHOOK_TIMESTAMP_HEADER_NAME` | | Complete name of the timestamp header. Unset uses the default `<prefix>timestamp`; an explicit value pins that exact name; an empty string disables the header. Only applies to `default` mode. |
| `DESTINATIONS_WEBHOOK_TOPIC_HEADER_NAME` | | Complete name of the topic header. Unset uses the default `<prefix>topic`; an explicit value pins that exact name; an empty string disables the header. Only applies to `default` mode. |
| `DESTINATIONS_WEBHOOK_SIGNATURE_ALGORITHM` | `hmac-sha256` | Signature algorithm |
| `DESTINATIONS_WEBHOOK_SIGNATURE_ENCODING` | `hex` | Encoding: `hex` or `base64` |
| `DESTINATIONS_WEBHOOK_MAX_RESPONSE_BODY_BYTES` | `131072` (128 KiB) | Max bytes of a destination response body stored on the delivery attempt. Larger responses are replaced with a placeholder so the attempt log stays under the event queue's per-message size limit. Set to `0` to disable the cap. |

{% callout type="warning" %}
The `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_EVENT_ID_HEADER`, `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_SIGNATURE_HEADER`, `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TIMESTAMP_HEADER`, and `DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TOPIC_HEADER` flags are deprecated and will be removed in a future version. Disable a header by setting its corresponding `*_HEADER_NAME` variable to an empty string instead. A set `*_HEADER_NAME` always takes precedence over the matching deprecated flag.
{% /callout %}

## Observability

| Variable | Description |
Expand Down
4 changes: 4 additions & 0 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ func (a *App) PreRun(ctx context.Context) (err error) {

a.logger.Info("starting outpost", a.config.LogConfigurationSummary()...)

for _, warning := range a.config.DeprecationWarnings() {
a.logger.Warn(warning)
}

if err := a.configureIDGenerators(); err != nil {
return err
}
Expand Down
24 changes: 24 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ func (c *Config) parseEnvVariables(osInterface OSInterface) error {
}

c.captureEmptyAlertEnv(osInterface)
c.captureEmptyWebhookHeaderEnv(osInterface)
return nil
}

Expand All @@ -309,6 +310,23 @@ func (c *Config) captureEmptyAlertEnv(osInterface OSInterface) {
}
}

// captureEmptyWebhookHeaderEnv honors "an empty env var disables this webhook
// header". Same caarlos0/env gap as captureEmptyAlertEnv: a present-but-empty
// env var never reaches the OptionalString unmarshaler, so we detect it via
// LookupEnv and set the empty (disabled) state explicitly.
func (c *Config) captureEmptyWebhookHeaderEnv(osInterface OSInterface) {
for envVar, field := range map[string]*OptionalString{
"DESTINATIONS_WEBHOOK_EVENT_ID_HEADER_NAME": &c.Destinations.Webhook.EventIDHeaderName,
"DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_NAME": &c.Destinations.Webhook.SignatureHeaderName,
"DESTINATIONS_WEBHOOK_TIMESTAMP_HEADER_NAME": &c.Destinations.Webhook.TimestampHeaderName,
"DESTINATIONS_WEBHOOK_TOPIC_HEADER_NAME": &c.Destinations.Webhook.TopicHeaderName,
} {
if v, ok := osInterface.LookupEnv(envVar); ok && v == "" {
*field = NewOptionalString("")
}
}
}

func (c *Config) normalizeTopics() {
if len(c.Topics) == 0 {
return
Expand Down Expand Up @@ -557,6 +575,12 @@ func resolveAlertCount(raw OptionalString, defaultValue, min int) (resolvedAlert
return resolvedAlertCount{enabled: true, value: n}, nil
}

// DeprecationWarnings returns human-readable warnings for deprecated config
// options that are actively in use, so callers can surface them at startup.
func (c *Config) DeprecationWarnings() []string {
return c.Destinations.Webhook.deprecationWarnings()
}

// ConfigFilePath returns the path of the config file that was used
func (c *Config) ConfigFilePath() string {
return c.configPath
Expand Down
Loading
Loading