Exchange API URL encoding affects signatures because authentication uses a venue-defined representation, not just a request's apparent meaning. Keep raw parameter values, the signing input and the transmitted query distinct. Apply the endpoint's encoding rules in the specified order, then prevent later serialization from changing the authenticated representation. A literal plus sign, a space and a percent sign require different treatment.
A request can carry the intended values and still fail authentication because one component signs %20 while another sends +. It can also authenticate a request whose values were already corrupted upstream. These are different failures: compare both the intended values and the precise representation.
Find the signing boundary first
Write down the venue, product, protocol and endpoint before choosing an encoder. A REST recipe is not automatically a WebSocket recipe, even at the same exchange.
For Binance Spot REST, the documented signature payload combines the query string and HTTP body without an added separator. Its examples require non-ASCII characters to be percent-encoded before signing. They also show parameters joined by &, with = between names and values; these structural delimiters remain delimiters. See the Binance Spot REST signing procedure.
KuCoin documents a different boundary: the URL used in its signing input must contain the original, unencoded content. Its example contrasts an encoded transmitted value with the unencoded value in the signing URL. See KuCoin's authentication and URL-encoding instructions.
Therefore, “always sign the final URL bytes” is too broad. Define the exact representation that this endpoint verifies, and derive it from the same raw inputs as the request. Do not decode an arbitrary completed URL and hope that it reconstructs the original fields.
The choice of HMAC, RSA or Ed25519 is a separate compatibility question. The API-key algorithm guide covers that choice; changing the algorithm cannot repair a wrong message representation.
A plus sign is not a space everywhere
Percent-encoding represents a byte using % followed by two hexadecimal digits. A percent sign that is itself data needs its own encoding. RFC 3986's encoding rules explain both that distinction and the danger of encoding or decoding the same representation repeatedly.
Form encoding adds another convention: a space becomes +, and parsing a form-encoded query converts a raw + to a space before percent-decoding. JavaScript's URLSearchParams uses that format. These behaviors are defined in the WHATWG URL Standard.
This table is an original local fixture, not a list of values accepted by any exchange endpoint. The middle column uses an illustrative component encoder that escapes spaces as %20; the last uses URLSearchParams with the raw value supplied as a field.
| Intended field value | Component encoding | Form encoding |
|---|---|---|
A B | A%20B | A+B |
A+B | A%2BB | A%2BB |
A%2BB | A%252BB | A%252BB |
A&B | A%26B | A%26B |
é | %C3%A9 | %C3%A9 |
The third row is easy to misdiagnose. If the intended value literally contains the characters %2B, encoding its percent sign is correct. If the application intended a plus sign but stored an already encoded value as though it were raw, the same result reveals an extra encoding stage. You need the intended value to tell those cases apart.
A 20-byte query becomes an 18-byte query
Consider a hypothetical integration whose contract requires spaces as %20 and authenticates the serialized query. It has two invented parameters: tag with raw value A+B C%, and n with raw value 1. These are deliberately small diagnostic fields, not a valid exchange request.
The signer constructs tag=A%2BB%20C%25&n=1. That ASCII string is 20 bytes. After signing, an HTTP helper rebuilds the parameters using form encoding and produces tag=A%2BB+C%25&n=1, which is 18 bytes.
Both strings recover the same original values under a form parser. They still differ as signing inputs. The first difference is at zero-based byte offset 9: the signer has %, byte 0x25, while the rebuilt query has +, byte 0x2B. The two-byte length reduction comes from replacing the three-character %20 with one character.
| Boundary | Captured value | Decision |
|---|---|---|
| Raw field | A+B C% | Preserve the plus, space and percent as separate characters |
| Required signing query | tag=A%2BB%20C%25&n=1 | Freeze this representation under the fictional contract |
| Helper's outgoing query | tag=A%2BB+C%25&n=1 | Detect a transformation after signing |
| Parsed field comparison | Both recover A+B C% | Semantic equality alone misses the mismatch |
| Byte comparison | Lengths 20 and 18; first difference at 9 | Repair the serialization boundary |
The finding is conditional on this invented signing contract. It does not establish whether a particular exchange accepts both spellings or canonicalizes them. Check that interface's documented behavior instead of generalizing the fixture.
Give each transformation one owner
A practical adapter can keep raw fields immutable, construct the venue's signing representation explicitly, and pass the approved transport representation to the HTTP layer without letting it rebuild the parameters afterward. Where signing uses unencoded content, preserve that separate representation deliberately.
Encode data components before joining them with query delimiters. Encoding the entire assembled tag=value&n=1 string as one component would encode the structural = and & too. Conversely, leaving an ampersand inside a value unescaped can make it look like another parameter.
Assign ordering, repeated fields and empty values to the adapter contract. Sorting after signing can change the representation. A map can discard repeated keys; a cleanup step can remove tag=. Do not introduce duplicates just to test them against a live endpoint. Use local fixtures and the endpoint's allowed schema.
The signature's output encoding is another boundary. Binance Spot's RSA instructions separately address Base64 output and its percent-encoding for transport. Do not let a form parser reinterpret a literal plus inside such an output as a space. Follow the documented output procedure without feeding an already escaped signature through an extra escape pass.
Numerical formatting happens earlier. The decimal precision guide explains preserving a price or quantity's intended value. Here, even a numerically equivalent spelling can change the bytes; finish numerical normalization before constructing the signing input.
Use a secret-free comparison worksheet
Start with a completely synthetic request fixture. Capture the raw field list, selected encoding policy, signing-input byte length, outgoing representation and first differing byte. Assert intended values as well as exact expected strings. Keep timestamps fixed only in local fixtures, where no real request is sent.
Then use a documented authenticated read in an appropriate test environment when remote verification is needed. Production signatures, full signed URLs and authorization headers do not belong in shared diagnostics. Redaction can change bytes, so distinguish a synthetic byte comparison from a redacted incident record.
Freshness remains a separate acceptance gate. The monotonic and wall-clock guide explains duration measurement versus authenticated timestamps. Correct encoding cannot make an expired timestamp current.
Inspect the provider's application result too. The HTTP error-envelope guide explains why transport status alone cannot establish request success. If the error is still ambiguous, use the broader timestamp and signature diagnostic.
Finally, repairing authentication says nothing about an earlier order's outcome. Preserve its identity and use order-timeout reconciliation before resubmission. The useful completion evidence is a repeatable representation check and a correctly interpreted response, with any outstanding order uncertainty resolved separately.
Prepared with AI assistance on 23 October 2026 under the editorial policy. Current primary documentation informed the venue distinctions. The encoding fixtures, byte lengths and first-difference location were checked locally; no live credentials or orders were used.
