Description
Saving a Workflow Webhook defined value through the v19 admin UI stores its WorkflowAttributes mapping URL-encoded. LaunchWorkflow.ashx never decodes it, so every mapping in that webhook silently stops resolving — the launched workflow receives the literal template text instead of the value.
The admin UI continues to display the mapping correctly, because the field type's read path decodes what its write path encoded. Only the webhook handler sees the encoded bytes. There is no error, no exception, and nothing written to ~/App_Data/Logs/LaunchWorkflow.txt.
KeyValueListFieldType has two save paths that write two different on-disk formats.
Rock/Field/Types/KeyValueListFieldType.cs — WebForms path (GetEditValue) passes the value through untouched:
public override string GetEditValue( Control control, Dictionary<string, ConfigurationValue> configurationValues )
{
var picker = control as KeyValueList;
if ( picker != null )
{
return picker.Value;
}
return null;
}
Same file — Obsidian path (GetPrivateEditValue) URL-encodes both halves of every pair:
return values.Select( v => $"{HttpUtility.UrlEncode( v.Key )}^{HttpUtility.UrlEncode( v.Value )}" )
.JoinStrings( "|" );
So Body^{{ RawBody }} is stored verbatim by one editor and as Body^%7b%7b+RawBody+%7d%7d by the other.
The consumer never decodes. RockWeb/Webhooks/LaunchWorkflow.ashx → PopulateWorkflowAttributes:
string workflowAttributes = hook.GetAttributeValue( "WorkflowAttributes" );
string[] attributes = workflowAttributes.Split( '|' );
foreach ( string attribute in attributes )
{
if ( attribute.Contains( '^' ) )
{
string[] settings = attribute.Split( '^' );
workflow.SetAttributeValue( settings[0], settings[1].ResolveMergeFields( mergeFields ) );
}
}
ResolveMergeFields receives %7b%7b+RawBody+%7d%7d, finds no {{, and returns it unchanged.
Actual Behavior
The mapped workflow attribute holds the URL-encoded template text:
That is {{ RawBody }} with { → %7b, } → %7d, and spaces → +, i.e. HttpUtility.UrlEncode.
The workflow launches normally and reports success. Opening the webhook's defined value in the admin UI shows {{ RawBody }}, correctly, giving no indication anything is wrong.
Expected Behavior
The mapped attribute holds the resolved value — the request body, in this example — as it does on v18 and as the admin UI implies.
More generally: PopulateWorkflowAttributes should read the same format the field type writes. Every other consumer of a KeyValueList value UrlDecodes each half; this one does not.
Steps to Reproduce
- Go to
Admin Tools > Settings > General > Defined Types > Workflow Webhook (defined type Guid 7B39BA6A-E7EF-48A6-9EC7-4A0F498D8FDB)
- Add or edit a defined value. Set Process Request to
True and pick any workflow type that has a text attribute keyed Body
- Set Workflow Attributes to
Body^{{ RawBody }} and save
- POST any JSON payload to
~/Webhooks/LaunchWorkflow.ashx
- Open the launched workflow and inspect its
Body attribute — it holds %7b%7b+RawBody+%7d%7d rather than the payload
- Reopen the defined value in the admin UI and note that it still displays
{{ RawBody }}
To see the stored bytes directly:
SELECT dv.[Value] AS Hook, ft.[Class] AS FieldType, av.[Value] AS StoredValue
FROM AttributeValue av
JOIN Attribute a ON a.Id = av.AttributeId
JOIN FieldType ft ON ft.Id = a.FieldTypeId
JOIN DefinedValue dv ON dv.Id = av.EntityId
JOIN DefinedType dt ON dt.Id = dv.DefinedTypeId
WHERE dt.[Guid] = '7B39BA6A-E7EF-48A6-9EC7-4A0F498D8FDB'
AND a.[Key] = 'WorkflowAttributes';
Issue Confirmation
Rock Version
19.3
Client Culture Setting
en-US
Description
Saving a Workflow Webhook defined value through the v19 admin UI stores its
WorkflowAttributesmapping URL-encoded.LaunchWorkflow.ashxnever decodes it, so every mapping in that webhook silently stops resolving — the launched workflow receives the literal template text instead of the value.The admin UI continues to display the mapping correctly, because the field type's read path decodes what its write path encoded. Only the webhook handler sees the encoded bytes. There is no error, no exception, and nothing written to
~/App_Data/Logs/LaunchWorkflow.txt.KeyValueListFieldTypehas two save paths that write two different on-disk formats.Rock/Field/Types/KeyValueListFieldType.cs— WebForms path (GetEditValue) passes the value through untouched:Same file — Obsidian path (
GetPrivateEditValue) URL-encodes both halves of every pair:So
Body^{{ RawBody }}is stored verbatim by one editor and asBody^%7b%7b+RawBody+%7d%7dby the other.The consumer never decodes.
RockWeb/Webhooks/LaunchWorkflow.ashx→PopulateWorkflowAttributes:ResolveMergeFieldsreceives%7b%7b+RawBody+%7d%7d, finds no{{, and returns it unchanged.Actual Behavior
The mapped workflow attribute holds the URL-encoded template text:
That is
{{ RawBody }}with{→%7b,}→%7d, and spaces →+, i.e.HttpUtility.UrlEncode.The workflow launches normally and reports success. Opening the webhook's defined value in the admin UI shows
{{ RawBody }}, correctly, giving no indication anything is wrong.Expected Behavior
The mapped attribute holds the resolved value — the request body, in this example — as it does on v18 and as the admin UI implies.
More generally:
PopulateWorkflowAttributesshould read the same format the field type writes. Every other consumer of aKeyValueListvalueUrlDecodes each half; this one does not.Steps to Reproduce
Admin Tools > Settings > General > Defined Types > Workflow Webhook(defined type Guid7B39BA6A-E7EF-48A6-9EC7-4A0F498D8FDB)Trueand pick any workflow type that has a text attribute keyedBodyBody^{{ RawBody }}and save~/Webhooks/LaunchWorkflow.ashxBodyattribute — it holds%7b%7b+RawBody+%7d%7drather than the payload{{ RawBody }}To see the stored bytes directly:
Issue Confirmation
Rock Version
19.3
Client Culture Setting
en-US