如何从Coinbase读取账户余额?遭遇401无效签名错误求助
解决Coinbase API获取账户时的401无效签名问题
看起来你已经搞定了价格查询的部分,但调用账户接口时的签名生成出了几个关键问题,导致了invalid signature错误。我帮你梳理一下问题点和修复方案:
核心问题分析
Coinbase API的签名规则要求每个请求的签名必须基于timestamp + HTTP方法 + 请求路径(含查询参数) + 请求体的拼接字符串,你的代码在调用账户接口时违反了这个规则:
- 签名消息缺少时间戳:你生成账户请求的签名时,message只写了
"GET/v2/accounts",但必须把当前请求的Unix时间戳加在最前面,和价格请求的格式保持一致。 - 复用旧的时间戳:两次请求用了同一个
unixTimestamp,Coinbase要求每个请求的时间戳必须是当前的(且与服务器时间差不超过30秒),复用旧时间戳会直接导致签名验证失败。 - 多余的查询参数:
/v2/accounts接口不需要currency查询参数,你额外添加的这个参数会让实际请求路径和签名计算的路径不匹配,进而触发签名错误。 - 重复使用HMAC实例:同一个
HMACSHA256实例在多次调用ComputeHash后可能出现状态异常,建议每次请求都创建新的实例。
修正后的代码
下面是修复后的完整代码,我标注了关键修改点:
using System; using System.Text; using System.Security.Cryptography; using Flurl; using Flurl.Http; using Newtonsoft.Json; using System.Net; namespace demibyte.coinbase { class MainClass { public static void Main (string[] args) { var host = "https://api.coinbase.com/"; var apiKey = "my api key"; var apiSecret = "my api secret"; var currency = "AUD"; var apiVersion = "2016-03-03"; // 确保这个值和Coinbase API设置页面的版本一致 // --- 价格请求部分(保留原逻辑,没问题) --- var priceTimestamp = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; var priceMessage = $"{priceTimestamp}GET/v2/prices/spot?currency={currency}"; byte[] priceSecretKey = Encoding.UTF8.GetBytes(apiSecret); using (HMACSHA256 priceHmac = new HMACSHA256(priceSecretKey)) { byte[] priceBytes = Encoding.UTF8.GetBytes(priceMessage); byte[] priceRawHmac = priceHmac.ComputeHash(priceBytes); var priceSignature = priceRawHmac.ByteArrayToHexString(); var price = host .AppendPathSegment("v2/prices/spot") .SetQueryParam("currency", currency) .WithHeader("CB-ACCESS-SIGN", priceSignature) .WithHeader("CB-ACCESS-TIMESTAMP", priceTimestamp) .WithHeader("CB-ACCESS-KEY", apiKey) .WithHeader("CB-VERSION", apiVersion) .GetJsonAsync<dynamic>() .Result; Console.WriteLine(price.ToString(Formatting.None)); Console.WriteLine(); } // --- 账户请求部分(修复后的逻辑) --- // 1. 生成新的当前时间戳,不能复用之前的 var accountTimestamp = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; // 2. 签名消息必须包含时间戳 + 方法 + 请求路径(这里没有查询参数,所以直接写/v2/accounts) var accountMessage = $"{accountTimestamp}GET/v2/accounts"; byte[] accountSecretKey = Encoding.UTF8.GetBytes(apiSecret); // 3. 每次请求创建新的HMAC实例,避免状态异常 using (HMACSHA256 accountHmac = new HMACSHA256(accountSecretKey)) { byte[] accountBytes = Encoding.UTF8.GetBytes(accountMessage); byte[] accountRawHmac = accountHmac.ComputeHash(accountBytes); var accountSignature = accountRawHmac.ByteArrayToHexString(); var accounts = host .AppendPathSegment("v2/accounts") // 4. 移除多余的currency查询参数,/v2/accounts不需要这个 .WithHeader("CB-ACCESS-SIGN", accountSignature) .WithHeader("CB-ACCESS-TIMESTAMP", accountTimestamp) .WithHeader("CB-ACCESS-KEY", apiKey) .WithHeader("CB-VERSION", apiVersion) .GetJsonAsync<dynamic>() .Result; Console.WriteLine(accounts.ToString(Formatting.None)); Console.ReadLine(); } } } }
额外注意事项
- 确保你的API密钥已经启用了
wallet:accounts:read权限(你已经提到这一点,没问题)。 - 如果还是遇到时间相关的错误,可以先对比本地时间和Coinbase服务器时间,确保时间差在30秒以内。
- 建议使用
async/await替代.Result来避免死锁问题,尤其是在非控制台应用中。
内容的提问来源于stack exchange,提问作者Nick_F
相关产品推荐
相关产品推荐

