Problem / use case.
When a tenant initiates an operation, say a action.initiated event, tey often want the resulting webhook delivered to a per-operation callback path chosen at initiation time, e.g. https://app.example.com/actions/<uuid>/callback
From a developer-experience standpoint this is a very natural thing to reach for: "I just want Outpost to handle the retries, queuing and delivery of this one HTTP request, so why can't I just hand it the destination URL on the fly?" It's an intuitive ask, which is part of why it keeps coming up.
Today a webhook destination's url is fully static per destination. The only way to achieve a per-operation path is to create a throwaway destination for each operation and publish with destination_id. That means unbounded destination churn plus lifecycle and cleanup burden, which does not fit a high-volume "one callback per action" pattern.
It also fragments the portal/UI experience: instead of thousands of single-use destinations, each with its own isolated logs, delivery attempts, and metrics, templating keeps everything under a single stable destination. All the usual actions (viewing delivery history, retrying, inspecting attempts, editing config) then operate on one coherent destination rather than being scattered across per-operation throwaways.
Proposal
Allow the path portion of a webhook URL to be templated from event fields, while the scheme + host remain fixed static config. This reuses the Go text/template mechanism already used for signing secrets and signature content in the webhook provider.
Example destination confog:
config.url = "https://app.example.com"
config.url_path_template = "/actions/{{.metadata.callback_id}}/callback"
Publishing an event with metadata.callback_id = "abc-123" delivers to:
https://app.example.com/actions/abc-123/callback
Retries, queuing and signing all continue to work exactly as they do today.
Why templated path, not arbitrary URL
A fully event-controlled URL, while much easier to integrate, is deliberately not proposed, because it introduces three problems:
- SSRF. Event data an attacker can influence could redirect delivery to internal or cloud-metadata endpoints (
169.254.169.254, localhost, private ranges).
- Rate-limiting / concurrency fragmentation. Outpost caches publishers and reasons about destinations by a stable config identity. A URL that changes every event fragments that model.
- Fragmented telemetry. Per-destination metrics and logs assume a stable target host; a moving host makes per-destination dashboards meaningless.
Pinning scheme + host to operator-controlled config while templating only the path neutralizes all three: the host (the SSRF-relevant part) is never event-controlled, the destination identity stays stable, and telemetry stays attributed per destination.
Design choices (proposed, open to input)
- Separate field
url_path_template rather than template markers inside url, so the base URL stays a plain, validatable URL and scheme + host can never be templated.
- Template inputs:
metadata (and possibly data). The event's metadata string map is a small, safe surface; parsed data could also be exposed if there's appetite. One open point: metadata keys are already emitted as X-Outpost-* headers, so a value used to shape the path would also go out as a header; if that's undesirable, a reserved metadata namespace (e.g. keys prefixed _path.) could be usable in the template but filtered out of the headers.
- Guardrails: percent-encode resolved path segments; reject templates/results containing
://, .., @, or a leading //; enforce the rendered result is still same-origin as the configured base URL.
Open questions
- Template-resolution failure at delivery time — hard-fail the attempt (visible in the attempt log) vs. fall back to the base URL? Proposal: hard-fail.
- Where template inputs should come from —
metadata only, also parsed data, or is there a different source altogether that would be a better fit than either metadata or data? Open to suggestions if neither is the right home for a path-shaping value.
Problem / use case.
When a tenant initiates an operation, say a
action.initiatedevent, tey often want the resulting webhook delivered to a per-operation callback path chosen at initiation time, e.g.https://app.example.com/actions/<uuid>/callbackFrom a developer-experience standpoint this is a very natural thing to reach for: "I just want Outpost to handle the retries, queuing and delivery of this one HTTP request, so why can't I just hand it the destination URL on the fly?" It's an intuitive ask, which is part of why it keeps coming up.
Today a webhook destination's
urlis fully static per destination. The only way to achieve a per-operation path is to create a throwaway destination for each operation and publish withdestination_id. That means unbounded destination churn plus lifecycle and cleanup burden, which does not fit a high-volume "one callback per action" pattern.It also fragments the portal/UI experience: instead of thousands of single-use destinations, each with its own isolated logs, delivery attempts, and metrics, templating keeps everything under a single stable destination. All the usual actions (viewing delivery history, retrying, inspecting attempts, editing config) then operate on one coherent destination rather than being scattered across per-operation throwaways.
Proposal
Allow the path portion of a webhook URL to be templated from event fields, while the scheme + host remain fixed static config. This reuses the Go
text/templatemechanism already used for signing secrets and signature content in the webhook provider.Example destination confog:
Publishing an event with
metadata.callback_id = "abc-123"delivers to:Retries, queuing and signing all continue to work exactly as they do today.
Why templated path, not arbitrary URL
A fully event-controlled URL, while much easier to integrate, is deliberately not proposed, because it introduces three problems:
169.254.169.254,localhost, private ranges).Pinning scheme + host to operator-controlled config while templating only the path neutralizes all three: the host (the SSRF-relevant part) is never event-controlled, the destination identity stays stable, and telemetry stays attributed per destination.
Design choices (proposed, open to input)
url_path_templaterather than template markers insideurl, so the base URL stays a plain, validatable URL and scheme + host can never be templated.metadata(and possiblydata). The event'smetadatastring map is a small, safe surface; parseddatacould also be exposed if there's appetite. One open point: metadata keys are already emitted asX-Outpost-*headers, so a value used to shape the path would also go out as a header; if that's undesirable, a reserved metadata namespace (e.g. keys prefixed_path.) could be usable in the template but filtered out of the headers.://,..,@, or a leading//; enforce the rendered result is still same-origin as the configured base URL.Open questions
metadataonly, also parseddata, or is there a different source altogether that would be a better fit than eithermetadataordata? Open to suggestions if neither is the right home for a path-shaping value.