# system.net.httpGet URL encoding of parameters

**URL:** <https://forum.inductiveautomation.com/t/system-net-httpget-url-encoding-of-parameters/112548>\
**Category:** Ignition\
**Tags:** ignition81\
**Created:** [December 1, 2025, 11:42pm UTC](https://forum.inductiveautomation.com/t/system-net-httpget-url-encoding-of-parameters/112548 "2025-12-01T23:42:48Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![richardNZ15](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/richardnz15/32/33373_2.png) [@richardNZ15](https://forum.inductiveautomation.com/u/richardNZ15)\
**Post date:** [December 1, 2025, 11:42pm UTC](https://forum.inductiveautomation.com/t/system-net-httpget-url-encoding-of-parameters/112548/1 "2025-12-01T23:42:48Z")

</div>

I have written a script library to hold a bunch of functions that GET data from an inventory management system API.

All has been working for years until a curveball recently threw my way.

There was a URL parameter value that had a space in it.

Seems like such things are not supported and need to be URL encoded.

ie: “My Warehouse” must be encoded to “My+Warehouse” or “My%20Warehouse” to work in the API URL.

I’ve been trying to use the encoder recommended elsewhere in the forums:

```python
from java.net import URLEncoder
from java.nio.charset import StandardCharsets

```

Apparently only the VALUE part of the parameter key/value pair should be encoded.

So something like this:

`params={'pageSize': 200, 'WarehouseCode': 'G1.2 Prod'}`

Is converted to a URL string like this, which will be appended to the end of the BASE URL that is sent to the API call.

`result=pageSize=200&WarehouseCode=G1.2+Prod`

However now I’m getting authorization errors (403) because (I presume) the ‘api-auth-signature’ generated does not match.

I think i’m going in circles a bit trying to figure out what needs encoding and where.

Does anyone have any good examples of:

1. Generating the URL parameter string from a dictionary.

2. Generating the appropriate header signature required

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [December 1, 2025, 11:46pm UTC](https://forum.inductiveautomation.com/t/system-net-httpget-url-encoding-of-parameters/112548/2 "2025-12-01T23:46:12Z")

</div>

If you use `system.net.httpClient` it'll do all this for you.

---

<div class="post-metadata">

**Author:** ![richardNZ15](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/richardnz15/32/33373_2.png) [@richardNZ15](https://forum.inductiveautomation.com/u/richardNZ15)\
**Post date:** [December 2, 2025, 1:17am UTC](https://forum.inductiveautomation.com/t/system-net-httpget-url-encoding-of-parameters/112548/3 "2025-12-02T01:17:38Z")

</div>

Huh looks like I had some old testing functions that were trying to do just that.

Looks like I can get it something to work without putting the parameters into the URL myself (as below), but I believe the URL Headers are still giving me problems, because I need to generate that myself to create the api-auth-signature. And the missmatch between the original WarehouseCode value and the encoded one causes signature missmatch right?

```python
def generateSignature(urlQueryParams):
""" This function returns an encoded signature based on url query parameters
and the unique API signature key

Args: 	
   urlQueryParams (str): string of url parameters to encode into signature

"""	

import hashlib	
import base64
import hmac

scriptName = "shared.api.generateSignature"

message = urlQueryParams.encode('utf-8')

apiScriptLogger.debug("%s - urlQueryParams=%s, utf-8 encoded message=%s" % (scriptName, urlQueryParams, message))

secret = API_KEY.encode('utf-8')
hmacDigest = hmac.new(secret,message, digestmod=hashlib.sha256).digest()

return base64.b64encode(hmacDigest)

#end def

def generateHeaders(urlQueryParams):

return {
	"Accept": "application/json", 
	"api-auth-id": API_ID, 
	"api-auth-signature": shared.api.generateSignature(urlQueryParams), 
	"Content-Type": "application/json",
	"client-type": "Ignition"
}

#end def

```

```python
url = BASE_URL + endpoint # NO url params - will be passed into getAsync and handled for us

# Custom function to take parameters and generate headers with signature
urlHeaders = shared.api.generateHeaders(shared.api.urlParamEncode(paramsDict))

try:
	promise = httpClient.getAsync(url=url, headers=urlHeaders, params=paramsDict, timeout = 30000) # Timeout = read timeout.
	LOGGER.info("%s - waiting for Async promise to complete" % (scriptName))
	response = promise.get()
	
	if response.isGood():
		shared.log.debug("%s - GET request to: '%s' returned StatusCode=%0d" % (scriptName, url, response.getStatusCode()))
	else:
		shared.log.error("%s - GET request to: '%s' returned StatusCode=%0d, response: %s" % (scriptName, url, response.getStatusCode(), response.getJson()))
	# end if

except:
	response = None
finally:
	endTime = shared.util.getGatewayDateTime()
	secondsElapsed = system.date.secondsBetween(startTime, endTime)
	###LOGGER.info("%s - statusCode=%s, duration=%0ds" % (scriptName, response.getStatusCode(), secondsElapsed))
	
	# Record request attempt in SQL for future reference
	recordHttpAttempt('GET', endpoint, paramsDict, url, response, secondsElapsed)
	return response
# end try

```

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [December 2, 2025, 4:19pm UTC](https://forum.inductiveautomation.com/t/system-net-httpget-url-encoding-of-parameters/112548/4 "2025-12-02T16:19:53Z")

</div>

Can you share any documentation for the API you're talking to, and/or what a valid request looks like and its associated signature?

---

<div class="post-metadata">

**Author:** ![richardNZ15](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/richardnz15/32/33373_2.png) [@richardNZ15](https://forum.inductiveautomation.com/u/richardNZ15)\
**Post date:** [December 2, 2025, 8:36pm UTC](https://forum.inductiveautomation.com/t/system-net-httpget-url-encoding-of-parameters/112548/5 "2025-12-02T20:36:53Z")

</div>

Unleashed API:

> **[Authentication · Unleashed Software](https://apidocs.unleashedsoftware.com/AuthenticationHelp)**

> The method signature must be generated by taking the query string, and creating a HMAC-SHA256 signature using your API key as the secret key.
> 
> Only the query parameters portion of the URL is used in calculating the signature, e.g. for the request `/ Customers?customerCode=ACME` use the string `customerCode=ACME` when generating the signature. Do not include the endpoint name in the method signature.

I am generating the signature with just the url query parameters, but perhaps my conversion of the pyDict parameters to the URL param string isn’t quite right when spaces get involved.

I had played around with using the encoder java method as per my first post, but then found I didn’t need to do so when passing parameters into the beefier httpClient method.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [December 3, 2025, 12:57am UTC](https://forum.inductiveautomation.com/t/system-net-httpget-url-encoding-of-parameters/112548/6 "2025-12-03T00:57:30Z")

</div>

A few minutes with Gemini gave me this result, which looks like it should work:

```python
import java.net.URLEncoder
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.util.Base64
import java.lang.String

def generate_unleashed_signature(params, api_key):
    """
    Generates an HMAC-SHA256 signature for the Unleashed Software API.
    
    Args:
        params (dict): A dictionary of unescaped string parameters.
        api_key (str): The user's private API key.
        
    Returns:
        str: The Base64 encoded HMAC-SHA256 signature.
    """
    
    # 1. Construct the Query String
    # The API requires the signature to be based on the query string.
    # We sort keys to ensure a deterministic order (common practice), 
    # though you must ensure the actual URL uses this same order.
    sorted_keys = sorted(params.keys())
    
    encoded_parts = []
    for key in sorted_keys:
        # Convert to string in case of non-string input
        k_str = str(key)
        v_str = str(params[key])
        
        # Use java.net.URLEncoder to encode keys and values
        # Standard UTF-8 encoding
        enc_k = java.net.URLEncoder.encode(k_str, "UTF-8")
        enc_v = java.net.URLEncoder.encode(v_str, "UTF-8")
        
        encoded_parts.append("{}={}".format(enc_k, enc_v))
    
    # Join with '&' to form the query string (e.g., "id=123&value=test")
    # If params is empty, this results in an empty string "", which is handled correctly per docs.
    query_string = "&".join(encoded_parts)
    
    # 2. Generate HMAC-SHA256 Signature
    algorithm = "HmacSHA256"
    
    # Create the SecretKeySpec using the API Key
    # We use java.lang.String.getBytes() to ensure we pass a Java byte[] 
    key_bytes = java.lang.String(api_key).getBytes("UTF-8")
    secret_key_spec = SecretKeySpec(key_bytes, algorithm)
    
    # Initialize the Mac instance
    mac = Mac.getInstance(algorithm)
    mac.init(secret_key_spec)
    
    # Compute the hash of the query string
    data_bytes = java.lang.String(query_string).getBytes("UTF-8")
    signature_bytes = mac.doFinal(data_bytes)
    
    # 3. Base64 Encode
    # Using java.util.Base64 (Available in Java 8+, commonly used with Jython 2.7)
    encoder = java.util.Base64.getEncoder()
    signature_b64 = encoder.encodeToString(signature_bytes)
    
    return signature_b64

```

An interesting callout that I didn't think of - this explicitly sorts the input parameters, which is something that the automatic URL escaping in `system.net.httpClient` does **not** do. You should pass an `OrderedDict` in to `httpClient` to make sure the automatic URL construction uses the same ordering as your key generation routine.
