Skip to main content
When an HTTP button is pressed, every {{name}} placeholder in the URL and the request body is replaced with the corresponding argument value. The encoding applied to that value depends on where in the request the placeholder appears. Only the URL (SubstituteURL) and body (SubstituteBody) are templated. Header values are not — see Header values below. This page documents the encoding rules and shows what each one prevents.

Encoding rules by location

The URL is split into a locked scheme + host prefix and a substitutable path/query/fragment suffix. The split happens on the first ://, then the first ? and #. The Content-Type lookup for the body is case-insensitive and ignores any ; charset=… suffix.

Scheme and host are locked

You cannot put a {{arg}} in the scheme or host of an HTTP button’s URL. The button service derives an allowed_host from the literal scheme + authority when the button is created, and rejects a URL whose scheme or host contains a placeholder. Placeholders are only ever expanded in the path, query, and fragment. This is what stops a {{arg}} value sourced from an attacker (for example a value forwarded from a webhook body) from redirecting the request to a different host. If a hand-edited button.json somehow smuggles a placeholder into the scheme/host, the substitution step leaves it untouched and dispatch fails with a VALIDATION_ERROR rather than making the request. See HTTP API buttons for the host-locking model in full.

Attack scenario 1: URL query injection

Template:
Attack value: real&admin=true Without encoding:
The injected &admin=true becomes a second query parameter that the server sees. With QueryEscape:
The & and = are percent-encoded. The server receives q = real&admin=true as a single value.

Attack scenario 2: URL path traversal

Template:
Attack value: ../../etc/passwd Without encoding:
The path traversal may resolve to a different resource depending on the server. With PathEscape:
Slashes are encoded. The server treats the entire string as a single path segment.

Attack scenario 3: JSON field injection

Template:
Attack value: ","role":"admin Without encoding:
The injected value breaks out of the string and introduces a duplicate key. Depending on the server’s JSON parser, it may use the injected role value. With JSON string escaping:
The quotes and commas are escaped. The server receives the attack string as a literal username value. The body is escaped with json.Marshal and the outer quotes stripped, so the template’s own quotes stay in place.

Header values

Header values are not templated. {{arg}} placeholders inside a header value are not expanded — the header is sent exactly as stored in the button. A credential such as a token or API key is therefore set as a literal, static header value when the button is created, which is correct for values that encoding would break (e.g. a base64 credential containing + and =). Because header values are static and never carry an argument, there is no per-press injection vector through them — but you are responsible for the value you store. Treat anything you put in a header as a committed part of the button spec.

Raw body types

For bodies with a Content-Type other than application/json / text/json (or no Content-Type at all, which is treated as JSON) and application/x-www-form-urlencoded, substitution is raw — no encoding is applied. Use this only when you fully control the body format.