Programming

Fix Binance API Signature Error (-1022) with Nested Parameters in TypeScript

Learn how to fix Binance API signature error (-1022) when making fiat withdrawals with nested parameters in TypeScript. Proper parameter encoding and signature generation guide.

1 answer 2 views

How to fix Binance API signature error (-1022) when making fiat withdrawals with nested parameters in TypeScript? I’m receiving ‘Signature for this request is not valid’ when trying to withdraw fiat currency using the sapi/v2/fiat/withdraw endpoint. My current implementation stringifies nested objects like accountInfo in the query string, but I’m unsure if this is the correct approach according to Binance’s API documentation. What is the proper way to format POST requests with nested parameters for Binance API signature generation?

The Binance API signature error (-1022) when making fiat withdrawals with nested parameters in TypeScript typically occurs because nested objects like accountInfo must be JSON-encoded strings rather than simply stringified. To fix this issue, you need to properly format the nested parameters as JSON strings and ensure proper percent-encoding before generating the signature using the HMAC-SHA256 algorithm according to Binance’s specific requirements.


Contents


Understanding Binance API Signature Error (-1022) and Fiat Withdrawals

When you encounter the Binance API signature error (-1022) while attempting to withdraw fiat currency, you’re facing one of the most common authentication challenges developers encounter when working with the Binance API. This specific error message - “Signature for this request is not valid” - indicates that the HMAC-SHA256 signature you’ve generated doesn’t match what Binance expects for your request, particularly when dealing with nested parameters in fiat withdrawal transactions.

The root cause of this error often stems from how nested parameters are formatted in POST requests to the sapi/v2/fiat/withdraw endpoint. Unlike simpler endpoints, fiat withdrawals require complex parameter structures that can include nested objects like accountInfo which contain additional sensitive information about the withdrawal destination. According to the official Binance API documentation, these nested parameters must be handled differently than simple key-value pairs.

What makes this particularly challenging for TypeScript developers is that JavaScript’s JSON.stringify() function doesn’t automatically handle the specific encoding requirements that Binance’s API expects. Many developers make the mistake of simply stringifying nested objects and then proceeding with signature generation, but Binance requires a more nuanced approach that includes proper JSON stringification followed by percent-encoding of all parameter values.

Another common misconception is treating the fiat withdrawal endpoint like other Binance API endpoints. The sapi/v2/fiat/withdraw endpoint has specific requirements that differ from standard spot trading endpoints, particularly in how it processes nested parameters and validates signatures. Understanding these distinctions is crucial to resolving the -1022 error and successfully processing fiat withdrawals through your TypeScript application.

The error code -1022 specifically indicates a signature validation failure, which means the server calculated a different signature than what you provided. This can happen for several reasons related to nested parameters:

  • Improper JSON stringification of nested objects
  • Missing or incorrect percent-encoding of parameter values
  • Incorrect ordering of parameters during signature generation
  • Impro handling of special characters in nested parameter values

Proper Handling of Nested Parameters in Binance API Requests

Nested parameters in Binance API requests, particularly for fiat withdrawals, require special treatment that differs significantly from standard API parameters. When working with the sapi/v2/fiat/withdraw endpoint, any parameter containing nested objects like accountInfo must be converted to a JSON string before being included in the signature generation process. This requirement stems from Binance’s security model and the need to ensure consistent parameter formatting across different programming languages and environments.

The correct approach involves using JSON.stringify() to convert your nested JavaScript objects into properly formatted JSON strings. However, this is just the first step - after stringification, you must apply percent-encoding to ensure that special characters in your JSON strings don’t interfere with the signature generation process. According to the Binance API security documentation, all parameter values must be properly encoded using encodeURIComponent() before being included in the signature base string.

Consider a nested object structure like this:

typescript
const accountInfo = {
 type: "SAVING",
 holderName: "John Doe"
};

Instead of simply concatenating this object as-is, you need to:

  1. Convert it to a JSON string: JSON.stringify(accountInfo)
  2. Percent-encode the result: encodeURIComponent(JSON.stringify(accountInfo))

This two-step process ensures that your nested parameters are formatted exactly as Binance’s API servers expect them. The result should be a properly encoded string that represents your nested object structure while maintaining the integrity of all special characters.

One common mistake developers make is assuming that the order of parameters doesn’t matter for signature generation. However, Binance’s API requires parameters to be ordered alphabetically by key name before generating the signature. This means that when you have multiple parameters, including nested ones, you need to:

  1. Collect all parameters (including the stringified nested ones)
  2. Sort them alphabetically by key
  3. Join them with = between key and value, and & between parameters
  4. Generate the signature from this properly formatted query string

For fiat withdrawals specifically, the accountInfo parameter often contains sensitive information that must be protected through proper signature validation. If this parameter isn’t correctly formatted as a JSON string and properly encoded, it will result in a mismatched signature and the -1022 error.

Another important consideration is handling of timestamps and the recvWindow parameter. These must also be properly formatted and included in your parameter list before signature generation. The timestamp represents when the request was created and helps prevent replay attacks, while recvWindow specifies how long the request is valid (in milliseconds).


TypeScript Implementation for Fiat Withdrawals with Nested Objects

Implementing a robust TypeScript solution for Binance API fiat withdrawals with nested parameters requires careful attention to detail and adherence to Binance’s specific formatting requirements. Let’s walk through a complete implementation that addresses the signature error (-1022) by properly handling nested objects like accountInfo.

First, let’s establish the foundational interfaces and types for our withdrawal request:

typescript
interface AccountInfo {
 type: string;
 holderName: string;
 // Add any other required fields based on your specific withdrawal type
}

interface FiatWithdrawRequest {
 coin: string;
 amount: number;
 fiatCurrency: string;
 network?: string;
 accountInfo: AccountInfo;
 // Other required parameters
}

interface BinanceApiCredentials {
 apiKey: string;
 apiSecret: string;
}

The core of our implementation will be a function that properly formats the parameters, generates the signature, and makes the API request. Here’s how we can approach this:

typescript
import * as crypto from 'crypto';

async function createFiatWithdrawal(
 credentials: BinanceApiCredentials,
 request: FiatWithdrawRequest
): Promise<any> {
 // 1. Prepare the base parameters
 const timestamp = Date.now();
 const recvWindow = 5000; // 5 seconds window
 
 // 2. Convert nested parameters to JSON strings and encode them
 const params = {
 coin: request.coin,
 amount: request.amount.toString(), // Convert to string
 fiatCurrency: request.fiatCurrency,
 accountInfo: encodeURIComponent(JSON.stringify(request.accountInfo)),
 timestamp: timestamp.toString(),
 recvWindow: recvWindow.toString()
 };
 
 // Add optional parameters if they exist
 if (request.network) {
 params.network = request.network;
 }
 
 // 3. Sort parameters alphabetically by key
 const sortedParams = Object.keys(params)
 .sort()
 .map(key => `${key}=${params[key]}`)
 .join('&');
 
 // 4. Generate the signature
 const signature = crypto
 .createHmac('sha256', credentials.apiSecret)
 .update(sortedParams)
 .digest('hex');
 
 // 5. Add signature to parameters
 const finalParams = `${sortedParams}&signature=${signature}`;
 
 // 6. Make the API request
 const response = await fetch('https://api.binance.com/sapi/v2/fiat/withdraw', {
 method: 'POST',
 headers: {
 'X-MBX-APIKEY': credentials.apiKey,
 'Content-Type': 'application/x-www-form-urlencoded'
 },
 body: finalParams
 });
 
 if (!response.ok) {
 const errorData = await response.json();
 throw new Error(`Binance API error ${errorData.code}: ${errorData.msg}`);
 }
 
 return response.json();
}

This implementation addresses several critical aspects of Binance’s API requirements:

  1. Nested Parameter Handling: The accountInfo parameter is converted to a JSON string using JSON.stringify() and then percent-encoded with encodeURIComponent(). This is the correct approach for handling nested objects in Binance API requests.

  2. Parameter Formatting: All numeric values are converted to strings before being included in the parameter list, which is a requirement for proper signature generation.

  3. Parameter Ordering: Parameters are sorted alphabetically by key before generating the signature, which is crucial for matching Binance’s expected signature format.

  4. Signature Generation: The HMAC-SHA256 signature is generated using the properly formatted query string, which includes all parameters (including the nested ones) in the correct order.

  5. Error Handling: The function includes proper error handling that catches and formats Binance API errors, including the specific -1022 signature error.

For more complex nested structures, you might need to create a helper function to recursively process nested objects:

typescript
function processNestedParams(obj: any): string {
 return encodeURIComponent(JSON.stringify(obj));
}

// Usage in the main function
const accountInfo = {
 type: "SAVING",
 holderName: "John Doe",
 additionalInfo: {
 bankCode: "BINANCE",
 country: "US"
 }
};

params.accountInfo = processNestedParams(accountInfo);

When testing this implementation, you should start with minimal nested parameters and gradually increase complexity to isolate any issues. The Stack Overflow discussion on this specific error provides valuable insights from other developers who’ve encountered similar challenges.

Remember that Binance’s API requirements may vary slightly between different endpoints and regions. Always consult the official documentation for the most current requirements specific to your use case and geographic location.


Step-by-Step Signature Generation Process

Generating a valid signature for Binance API requests, especially those with nested parameters like fiat withdrawals, requires a precise sequence of steps. Let’s break down the complete process in detail, ensuring you can implement it correctly in your TypeScript application.

Step 1: Prepare All Required Parameters

Begin by collecting all parameters required for the sapi/v2/fiat/withdraw endpoint. For fiat withdrawals, these typically include:

typescript
const baseParams = {
 coin: 'USD', // Fiat currency code
 amount: '1000', // Withdrawal amount as string
 fiatCurrency: 'USD', // Fiat currency code
 accountInfo: { // Nested object requiring special handling
 type: 'SAVING',
 holderName: 'John Doe',
 bankCode: 'BINANCE'
 },
 timestamp: Date.now().toString(), // Current timestamp as string
 recvWindow: '5000' // Request validity window in milliseconds as string
};

Important: Convert all numeric values to strings at this stage. Binance’s API requires string representations of numbers for proper signature generation.

Step 2: Process Nested Parameters

This is the most critical step for resolving the -1022 error. Nested objects like accountInfo must be converted to JSON strings and properly encoded:

typescript
function encodeNestedParams(params: any): any {
 const result: any = {};
 
 for (const [key, value] of Object.entries(params)) {
 if (typeof value === 'object' && value !== null) {
 // Handle nested objects by JSON stringifying and encoding
 result[key] = encodeURIComponent(JSON.stringify(value));
 } else {
 // Handle primitive values
 result[key] = encodeURIComponent(value.toString());
 }
 }
 
 return result;
}

const encodedParams = encodeNestedParams(baseParams);

Step 3: Sort Parameters Alphabetically

Binance’s API requires parameters to be sorted alphabetically by key name before signature generation:

typescript
const sortedParams = Object.keys(encodedParams)
 .sort()
 .map(key => `${key}=${encodedParams[key]}`)
 .join('&');

Step 4: Generate the HMAC-SHA256 Signature

Use Node.js’s built-in crypto module to generate the signature:

typescript
import * as crypto from 'crypto';

const apiSecret = 'your_api_secret_here'; // Store securely, don't hardcode
const signature = crypto
 .createHmac('sha256', apiSecret)
 .update(sortedParams)
 .digest('hex');

Step 5: Add Signature to Parameters

Append the generated signature to your parameter list:

typescript
const finalParams = `${sortedParams}&signature=${signature}`;

Step 6: Make the API Request

Construct and send the POST request with properly formatted headers and body:

typescript
async function makeWithdrawalRequest(finalParams: string) {
 const response = await fetch('https://api.binance.com/sapi/v2/fiat/withdraw', {
 method: 'POST',
 headers: {
 'X-MBX-APIKEY': 'your_api_key_here', // Store securely
 'Content-Type': 'application/x-www-form-urlencoded'
 },
 body: finalParams
 });
 
 const data = await response.json();
 
 if (!response.ok) {
 throw new Error(`Binance API error ${data.code}: ${data.msg}`);
 }
 
 return data;
}

Step 7: Handle the Response

Process the response from Binance’s API:

typescript
try {
 const result = await makeWithdrawalRequest(finalParams);
 console.log('Withdrawal successful:', result);
} catch (error) {
 console.error('Withdrawal failed:', error.message);
 
 // Handle specific error cases
 if (error.message.includes('Signature for this request is not valid')) {
 console.error('This indicates a signature generation issue. Check parameter encoding.');
 }
}

Complete Implementation Example

Here’s a complete TypeScript function that incorporates all these steps:

typescript
import * as crypto from 'crypto';

interface FiatWithdrawalParams {
 coin: string;
 amount: number;
 fiatCurrency: string;
 accountInfo: Record<string, any>;
 network?: string;
 recvWindow?: number;
}

export async function createFiatWithdrawal(
 apiKey: string,
 apiSecret: string,
 params: FiatWithdrawalParams
): Promise<any> {
 // Step 1: Prepare base parameters with defaults
 const timestamp = Date.now();
 const recvWindow = params.recvWindow || 5000;
 
 const baseParams = {
 coin: params.coin,
 amount: params.amount.toString(),
 fiatCurrency: params.fiatCurrency,
 accountInfo: params.accountInfo,
 timestamp: timestamp.toString(),
 recvWindow: recvWindow.toString()
 };
 
 // Add optional network parameter if provided
 if (params.network) {
 baseParams.network = params.network;
 }
 
 // Step 2: Encode nested parameters
 const encodedParams: Record<string, string> = {};
 for (const [key, value] of Object.entries(baseParams)) {
 if (typeof value === 'object' && value !== null) {
 // Nested object requires JSON stringification and encoding
 encodedParams[key] = encodeURIComponent(JSON.stringify(value));
 } else {
 // Regular parameter requires encoding
 encodedParams[key] = encodeURIComponent(value.toString());
 }
 }
 
 // Step 3: Sort parameters alphabetically
 const sortedParams = Object.keys(encodedParams)
 .sort()
 .map(key => `${key}=${encodedParams[key]}`)
 .join('&');
 
 // Step 4: Generate signature
 const signature = crypto
 .createHmac('sha256', apiSecret)
 .update(sortedParams)
 .digest('hex');
 
 // Step 5: Add signature to parameters
 const finalParams = `${sortedParams}&signature=${signature}`;
 
 // Step 6: Make the API request
 const response = await fetch('https://api.binance.com/sapi/v2/fiat/withdraw', {
 method: 'POST',
 headers: {
 'X-MBX-APIKEY': apiKey,
 'Content-Type': 'application/x-www-form-urlencoded'
 },
 body: finalParams
 });
 
 const data = await response.json();
 
 // Step 7: Handle response
 if (!response.ok) {
 throw new Error(`Binance API error ${data.code}: ${data.msg}`);
 }
 
 return data;
}

Verification and Testing

To verify your implementation is working correctly:

  1. Test with minimal parameters: Start with a simple withdrawal request containing only essential parameters to ensure your basic signature generation works.

  2. Gradually add complexity: Once basic requests work, gradually add nested parameters to isolate any issues.

  3. Compare with official examples: Consult the Binance signature examples repository to compare your approach with implementations in other programming languages.

  4. Log intermediate values: During development, log the encoded parameters and final query string to compare against Binance’s expectations.

  5. Check timestamp accuracy: Ensure your system clock is synchronized, as timestamp mismatches are a common cause of signature failures.

By following this step-by-step process, you should be able to resolve the -1022 signature error and successfully process fiat withdrawals with nested parameters in your TypeScript application.


Common Pitfalls and Troubleshooting Tips

Even when following Binance’s API documentation, developers frequently encounter signature errors (-1022) when working with nested parameters in fiat withdrawal requests. Let’s explore the most common pitfalls and provide specific troubleshooting strategies to help you resolve these issues efficiently.

1. Incorrect JSON Stringification of Nested Objects

Problem: Many developers incorrectly assume that nested parameters can be handled with simple string concatenation or improper JSON formatting.

Symptoms: Consistent -1022 signature errors specifically when nested parameters like accountInfo are included in the request.

Solution: Ensure nested objects are properly JSON stringified and percent-encoded:

typescript
// Incorrect approach
const accountInfo = JSON.stringify(accountInfo); // Missing percent encoding

// Correct approach
const accountInfo = encodeURIComponent(JSON.stringify(accountInfo));

Verification: Log the encoded parameter to verify it matches Binance’s expected format. For example:

typescript
console.log('Encoded accountInfo:', accountInfo);
// Should look like: %7B%22type%22%3A%22SAVING%22%2C%22holderName%22%3A%22John%20Doe%22%7D

2. Parameter Ordering Issues

Problem: Binance’s API requires parameters to be sorted alphabetically by key name before signature generation. Failing to sort parameters correctly will result in signature mismatches.

Symptoms: Inconsistent -1022 errors that appear and disappear as parameters are added or removed from the request.

Solution: Implement a robust parameter sorting mechanism:

typescript
function generateQueryString(params: Record<string, string>): string {
 return Object.keys(params)
 .sort() // Critical: sort alphabetically
 .map(key => `${key}=${params[key]}`)
 .join('&');
}

Debugging Tip: Temporarily log the sorted parameter string to verify it matches Binance’s expected format:

typescript
const sortedParams = generateQueryString(encodedParams);
console.log('Sorted parameters for signature:', sortedParams);

3. Missing or Incorrect Timestamp

Problem: Using an incorrect or stale timestamp is a common source of signature failures, especially in development environments where requests might be delayed.

Symptoms: Intermittent -1022 errors that occur primarily during development or testing.

Solution: Generate the timestamp immediately before creating the signature and ensure it’s included in the parameter list:

typescript
// Generate timestamp close to signature creation
const timestamp = Date.now().toString();

// Include in parameters
params.timestamp = timestamp;

Best Practice: Implement timestamp validation to ensure it’s recent (within a few seconds of generation):

typescript
function isValidTimestamp(timestamp: string, maxAgeMs: number = 10000): boolean {
 const requestTime = parseInt(timestamp);
 const currentTime = Date.now();
 return Math.abs(currentTime - requestTime) <= maxAgeMs;
}

4. Improper Handling of Special Characters

Problem: Nested parameters containing special characters (like spaces, quotes, or Unicode characters) can break signature generation if not properly encoded.

Symptoms: -1022 errors that occur only with specific parameter values containing special characters.

Solution: Ensure all parameter values are properly percent-encoded, not just the nested objects:

typescript
function encodeAllParams(params: Record<string, any>): Record<string, string> {
 const result: Record<string, string> = {};
 
 for (const [key, value] of Object.entries(params)) {
 if (typeof value === 'object' && value !== null) {
 result[key] = encodeURIComponent(JSON.stringify(value));
 } else {
 result[key] = encodeURIComponent(value.toString());
 }
 }
 
 return result;
}

Testing Strategy: Create test cases with various special characters to verify encoding works correctly:

typescript
const testAccountInfo = {
 type: 'SAVING',
 holderName: 'John O\'Malley', // Contains apostrophe
 address: '123 Main St. #101', // Contains period and hash
 notes: 'Special characters: © ® ™'
};

5. Incorrect HMAC-SHA256 Implementation

Problem: Using incorrect libraries or implementations for HMAC-SHA256 signature generation can result in signatures that don’t match Binance’s expectations.

Symptoms: Persistent -1022 errors across all parameter combinations, suggesting the core signature generation is flawed.

Solution: Use a well-tested crypto library and verify your implementation against known test vectors:

typescript
import * as crypto from 'crypto';

function generateSignature(queryString: string, apiSecret: string): string {
 return crypto
 .createHmac('sha256', apiSecret)
 .update(queryString)
 .digest('hex');
}

Verification: Test with known inputs and compare outputs:

typescript
const testSecret = 'test_secret';
const testQuery = 'param1=value1&param2=value2';
const testSignature = generateSignature(testQuery, testSecret);

console.log('Generated signature:', testSignature);
// Compare against expected result for your specific input

6. API Endpoint or Region Mismatches

Problem: Using the wrong API endpoint or region for your specific fiat withdrawal requirements can lead to authentication failures.

Symptoms: -1022 errors combined with other API-specific error messages or unexpected responses.

Solution: Verify you’re using the correct endpoint for your needs:

typescript
// Testnet vs Mainnet
const isTestnet = false;
const baseUrl = isTestnet 
 ? 'https://testnet.binance.vision' 
 : 'https://api.binance.com';

const endpoint = `${baseUrl}/sapi/v2/fiat/withdraw`;

Regional Considerations: Different regions may have different endpoints or authentication requirements:

typescript
// Specify region if needed
const region = 'international'; // or 'europe', etc.
const regionEndpoint = `https://api.binance.${region}.com/sapi/v2/fiat/withdraw`;

7. Missing or Incorrect API Headers

Problem: Forgetting to include required headers or using incorrect header values can result in authentication failures.

Symptoms: 401 Unauthorized errors mixed with -1022 signature errors.

Solution: Ensure all required headers are properly included:

typescript
const headers = {
 'X-MBX-APIKEY': yourApiKey,
 'Content-Type': 'application/x-www-form-urlencoded',
 // Add other required headers based on your specific use case
};

8. Network and Firewall Issues

Problem: Network connectivity problems or firewall restrictions can interfere with signature validation at the server level.

Symptoms: Intermittent -1022 errors that don’t correlate with code changes.

Solution: Implement network diagnostic checks:

typescript
async function checkApiConnectivity() {
 try {
 const response = await fetch('https://api.binance.com/api/v3/ping');
 return response.ok;
 } catch (error) {
 console.error('API connectivity check failed:', error);
 return false;
 }
}

Systematic Troubleshooting Approach

When encountering persistent -1022 errors, follow this systematic approach:

  1. Isolate the issue: Test with minimal parameters first, then gradually add complexity.

  2. Verify each component: Test timestamp generation, parameter encoding, and signature creation independently.

  3. Compare with working examples: Reference the official Binance signature examples to verify your approach.

  4. Implement comprehensive logging: Log intermediate values during signature generation to identify where the process diverges from expectations.

  5. Test with the official Binance API console: Use Binance’s API testing tools to verify your parameters match expected formats.

  6. Check for library updates: Ensure you’re using the latest versions of any libraries or SDKs you’re using to interact with Binance’s API.

By addressing these common pitfalls and following the troubleshooting strategies outlined above, you should be able to resolve the -1022 signature error and successfully process fiat withdrawals with nested parameters in your TypeScript application.


Sources

  1. Binance API Security Documentation — Official guide on request security, parameter encoding, and signature generation: https://developers.binance.com/docs/binance-spot-api-docs/rest-api/request-security

  2. Binance Fiat Withdrawal Endpoint Documentation — Specification for the sapi/v2/fiat/withdraw endpoint with parameter requirements: https://developers.binance.com/docs/wallet/capital/withdraw

  3. Stack Overflow Discussion on Error -1022 — Community insights and solutions for signature validation issues with nested parameters: https://stackoverflow.com/questions/79852901/binance-api-error-1022-signature-for-this-request-is-not-valid-on-fiat-withd

  4. Binance Signature Examples Repository — GitHub examples showing proper signature generation in various programming languages: https://github.com/binance-exchange/binance-signature-examples

  5. Node.js Crypto Module Documentation — Official documentation for HMAC-SHA256 implementation in Node.js: https://nodejs.org/api/crypto.html#crypto_hmac


Conclusion

Successfully resolving the Binance API signature error (-1022) when making fiat withdrawals with nested parameters in TypeScript requires a precise approach that follows Binance’s specific documentation requirements. The key to fixing this issue lies in properly formatting nested parameters like accountInfo as JSON strings and applying percent-encoding before generating the HMAC-SHA256 signature.

By implementing the step-by-step process outlined in this guide—properly encoding nested objects, sorting parameters alphabetically, generating accurate timestamps, and following Binance’s exact signature generation protocol—you can eliminate the -1022 error and reliably process fiat withdrawals through your TypeScript application. Remember that Binance’s API has specific requirements for nested parameter handling that differ from standard API parameters, and failing to follow these exactly will result in signature validation failures.

The most critical takeaway is that nested parameters must be processed with both JSON.stringify() and encodeURIComponent() before being included in the signature generation process. This two-step encoding ensures that your nested objects maintain their structure while being properly formatted for Binance’s API servers. By addressing the common pitfalls outlined in the troubleshooting section and following the implementation examples provided, you can create a robust solution that handles all aspects of Binance’s fiat withdrawal API authentication requirements.

Authors
Verified by moderation
Moderation
Fix Binance API Signature Error (-1022) with Nested Parameters in TypeScript