ERB in webhook headers not processed

Problem:
I’m trying to set up a shellhook, so I’m using the blank webhook template and sending arguments in headers as per the documentation. The event I’m using is Build Exited, and the headers I’m trying to send are:

{
"X-Shellhook-Arg-1": "<%= @payload[:id] %>",
"X-Shellhook-Arg-2": "<%= @payload[:hostname] %>"
}

with HTTP Content Type set to application/json.
However, I’m getting the literals <%= @payload[:id] %> and <%= @payload[:hostname] %> passed to the shellhook script when I use the Test webhook function from the web UI.
shellhook script:

#!/usr/bin/sh
echo "Built host ID is ${1}, hostname is '${2}'." >&2

proxy.log output:

2025-04-01T15:51:57 f38e3cb8 [W] [28263] Built host ID is <%= @payload[:id] %>, hostname is '<%= @payload[:hostname] %>'.

Expected outcome:
I expected the output to be

2025-04-01T15:51:57 f38e3cb8 [W] [28263] Built host ID is 23, hostname is 'hostname.example.com'.

assuming the ID was 23 and its hostname was hostname.example.com.

Foreman and Proxy versions:
3.13
Foreman and Proxy plugin versions:
katello 4.15
webhooks 4.0.0
there are others but I assume these are the only relevant.
Distribution and version:
Oracle Linux 9.5 x86_64

I just read through the code and I’m reasonably sure this is just an issue with the test function, as it appears the the headers aren’t rendered in test but are rendered in deliver.

Will report back with results after running a fresh build.

Hi, @dschlenk,

You’re right, it’s because test fire of a webhook uses payload as is, thus without any processing. The real webhook fire will substitute values in ERB statement. The implementation works like that because we don’t have any object at the test time, so we don’t have any actual data to process.

If we finish Fixes #33691 - Make webhook templates preview work by ofedoren · Pull Request #79 · theforeman/foreman_webhooks · GitHub it will give us an idea of mock objects, which we could re-use for test fire also.

1 Like

So the webhook fired, but the args were empty. The docs use symbols for @payload hash keys, but I’ve seen other places where ERB keys defined in the web UI seems to get converted from symbols to strings. Maybe I need to define the headers with hammer?

Hammer shows them intact as symbols. Will add variants with string keys I guess.

Nothing seems to be getting to the shellhook. I have it echoing $@ to the log and there’s nothing there.

It would appear that if you try to include the raw @payload hash as an argument, it breaks trying to escape the hash string:

2025-04-03T12:23:37 [I|app|08d70c5b] Enqueued ForemanWebhooks::DeliverWebhookJob (Job ID: 859bc087-d101-412d-bfe8-5febbf897bca) to Dynflow(default) with arguments: {:event_name=>"build_exited.event.foreman", :payload=>"", :headers=>"{\n\"X-Shellhook-Arg-1\": \"32\",\n\"X-Shellhook-Arg-2\": \"host.example.com\",\n\"X-Shellhook-Arg-4\": \"32\",\n\"X-Shellhook-Arg-5\": \"host.example.com\",\n\"X-Shellhook-Arg-6\": \"{\"context\"=>{\"remote_ip\"=>\"10.0.0.1\", \"request\"=>\"08d70c5b-f0b6-4cec-b25c-c04149bfa11a\", \"session\"=>\"08d70c5b-f0b6-4cec-b25c-c04149bfa11a\", \"user_login\"=>\"foreman_api_admin\", \"user_admin\"=>true, \"org_id\"=>3, \"org_name\"=>\"Example\", \"org_label\"=>\"Example\", \"loc_id\"=>6, \"loc_name\"=>\"Example Staging\"}, \"id\"=>32, \"hostname\"=>\"host.example.com\"}\"\n}", :url=>"https://hq-proxy.example.com:9090/shellhook/built", :webhook_id=>1}
2025-04-03T12:23:37 [I|app|08d70c5b] Completed 201 Created in 1422ms (ActiveRecord: 64.8ms | Allocations: 164107)
2025-04-03T12:23:37 [I|app|08d70c5b] Performing ForemanWebhooks::DeliverWebhookJob (Job ID: 859bc087-d101-412d-bfe8-5febbf897bca) from Dynflow(default) enqueued at 2025-04-03T16:23:37Z with arguments: {:event_name=>"build_exited.event.foreman", :payload=>"", :headers=>"{\n\"X-Shellhook-Arg-1\": \"32\",\n\"X-Shellhook-Arg-2\": \"host.example.com\",\n\"X-Shellhook-Arg-4\": \"32\",\n\"X-Shellhook-Arg-5\": \"host.example.com\",\n\"X-Shellhook-Arg-6\": \"{\"context\"=>{\"remote_ip\"=>\"10.0.0.1\", \"request\"=>\"08d70c5b-f0b6-4cec-b25c-c04149bfa11a\", \"session\"=>\"08d70c5b-f0b6-4cec-b25c-c04149bfa11a\", \"user_login\"=>\"foreman_api_admin\", \"user_admin\"=>true, \"org_id\"=>3, \"org_name\"=>\"Example\", \"org_label\"=>\"Example\", \"loc_id\"=>6, \"loc_name\"=>\"Example Staging\"}, \"id\"=>32, \"hostname\"=>\"host.example.com\"}\"\n}", :url=>"https://hq-proxy.example.com:9090/shellhook/built", :webhook_id=>1}
2025-04-03T12:23:37 [I|app|08d70c5b] Performing 'host build exited' webhook request for event 'build_exited.event.foreman'
2025-04-03T12:23:37 [W|app|08d70c5b] Could not parse HTTP headers JSON, ignoring: 809: unexpected token at '{
08d70c5b | "X-Shellhook-Arg-1": "32",
08d70c5b | "X-Shellhook-Arg-2": "host.example.com",
08d70c5b | "X-Shellhook-Arg-4": "32",
08d70c5b | "X-Shellhook-Arg-5": "host.example.com",
08d70c5b | "X-Shellhook-Arg-6": "{"context"=>{"remote_ip"=>"10.0.0.1", "request"=>"08d70c5b-f0b6-4cec-b25c-c04149bfa11a", "session"=>"08d70c5b-f0b6-4cec-b25c-c
04149bfa11a", "user_login"=>"foreman_api_admin", "user_admin"=>true, "org_id"=>3, "org_name"=>"Example", "org_label"=>"Example", "loc_id"=>6, "loc_name"=>"Example St
aging"}, "id"=>32, "hostname"=>"host.example.com"}"

header config:

{
"X-Shellhook-Arg-1": "<%= @payload[:id] %>",
"X-Shellhook-Arg-2": "<%= @payload[:hostname] %>",
"X-Shellhook-Arg-4": "<%= @payload['id'] %>",
"X-Shellhook-Arg-5": "<%= @payload['hostname'] %>",
"X-Shellhook-Arg-6": "<%= @payload %>"
}

TBH I don’t need the last 3 args so I’m just removing them, but in case someone else tries this: it doesn’t work!