I am trying to send a simple post request to the bybit api and I keep getting the 10004 sign error.
Here is the response:
{"ret_code":10004,"ret_msg":"error sign! origin_string[api_key=(my api key)\u0026symbol=BTCUSDT\u0026timestamp=1635967650768]","ext_code":"","ext_info":"","result":null,"time_now":"1635967651.397800"}
This is the code I am using to send the request.
public async static Task<string> cancelAllOrders()
{
string ts = await GenerateTimeStamp();
string paramString = "api_key=" + apiKey + "&symbol=BTCUSDT" + "timestamp=" + ts;
string sign = CreateSignature(secretKey, paramString);
CancelOrderContent co = new CancelOrderContent(apiKey, "BTCUSDT", ts, sign);
var client = new RestClient(ApiUrl + "/v2/private/order/cancelAll");
var request = new RestRequest();
request.AddJsonBody(co);
var response = client.Post(request);
Trace.WriteLine(response.StatusCode.ToString() + " " + response);
return "";
}
Here is the class I am Serializing to JSON for the body of the request.
public class CancelOrderContent
{
public string api_key;
public string sign;
public string symbol;
public string timestamp;
public CancelOrderContent(string api_key, string symbol, string timestamp,string sign)
{
this.api_key = api_key;
this.symbol = symbol;
this.timestamp = timestamp;
this.sign = sign;
}
}
Here is the code I am using to create signatures:
public static string CreateSignature(string secret, string message)
{
var signatureBytes = Hmacsha256(Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes(message));
return ByteArrayToString(signatureBytes);
}
private static byte[] Hmacsha256(byte[] keyByte, byte[] messageBytes)
{
using (var hash = new HMACSHA256(keyByte))
{
return hash.ComputeHash(messageBytes);
}
}
I've tried al kinds of methods to fix this but I can't seem to get rid of it.I've tried mutliple endpoints and I still get the same error.
2022/01/17 this still works.
Hey #Vexatious I ran into a similar issue with the bybit-api trying to submit orders and I kept receiving a Key denied, insufficient permissions error even though I knew I was setting up my keys properly.
Maybe ByBit changed something? Lib outdated? Who Knows.
One major thing that I noticed is that they require you to Order the params alphabetically before appending the signature to the body of the request.
Edit: Updated because I recognized GET Requests can be equally as confusing. Scroll down to view the example POST Request.
GET and POST requests are handled differently
axios(https://api-testnet.bybit.com/GET?aParam=foo&bParam=bar&sign=sign)
axios(https://api-testnet.bybit.com/POST, {data: queryString})
SIGNATURE
For Both: You must alphabetically arrange the params before generating the signature.
get your query param's on a single object [Including 'api_key', Excluding the API Secret].
Sort the query param's alphabetically.
Iterate over the objects sorted keys, building a QueryString like in the ex below
use hmac sha256 with a hex digest to create the sign (look at ./sign.ts at the bottom)
ex: "aParam=foo&bParam=bar",
That's your sign parameter dealt with.
GET REQUESTS: Append the sign parameter to the end of the QueryString
and send'er, might need to use a header
// Might still need {'Content-Type': 'application/x-www-form-urlencoded'}
// header depending on what request lib you're using.
const url = "https://api-testnet.bybit.com/GET?aParam=foo&bParam=bar&sign=" + sign
POST REQUESTS: it is required that the object is sent as request data (still, in the form of a QueryString like above) and not a fully built out Http string similar to the GET Request. Add the sign parameter to the end of the QueryString you generated the signature with, assign that to your request handlers data parameter and fire away!
I did come up with a minimal working version that successfully posted a Spot Order on their testnet, here is the jest test.
./bybit.test.ts
test("WORKING BYBIT TRADE.", async () => {
const serverTime:number = (await axios.get(`https://api-testnet.bybit.com/spot/v1/time`)).data
// These need to be within 1000ms of eachother (I'm pree sure, their formula is kinda confusing)
console.log(`Their Timestamp`, serverTime)
console.log(`Our Timestamp`, Date.now())
const queryParams = {
// Alphabetically ordered
// (Sign generation function should deal with unordered params using .sort())
'api_key': bybitApiKey,
qty:10,
recvWindow: 10000,
side:"BUY",
symbol:"ETHUSDT",
timestamp: Date.now(),
type:"MARKET",
}
const queryString = querystring.stringify(queryParams)
const sign = "&sign=" + getSignature(queryParams, bybitSecret)
const fullQuery = queryString + sign
console.log(`FullQuery example`, fullQuery)
// api_key=...&qty=10&recvWindow=10000&side=BUY&symbol=ETHUSDT×tamp=1638371037593&type=MARKET&sign=...
let result = await axios(`https://api-testnet.bybit.com/spot/v1/order`, {
withCredentials: true,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
method: "POST",
data: fullQuery,
})
console.log(`Post Status`, result.status)
console.log(`Post Body`, result.data)
})
/**
Post Status 200
Post Body {
ret_code: 0,
ret_msg: '',
ext_code: null,
ext_info: null,
result: {
accountId: '...',
symbol: 'ETHUSDT',
symbolName: 'ETHUSDT',
orderLinkId: '...',
orderId: '...',
transactTime: '...',
price: '0',
origQty: '10',
executedQty: '0',
status: 'FILLED',
timeInForce: 'GTC',
type: 'MARKET',
side: 'BUY'
}
*/
}
./sign.ts
import crypto from 'crypto'
export function getSignature(parameters: any, secret: string) {
var orderedParams = "";
Object.keys(parameters).sort().forEach(function(key) {
orderedParams += key + "=" + parameters[key] + "&";
});
orderedParams = orderedParams.substring(0, orderedParams.length - 1);
return crypto.createHmac('sha256', secret).update(orderedParams).digest('hex');
}
Hopefully this helps!
I have just figured this out for the ByBit api, but using Javascript instead of C#. However i do know C#, so hopefully this will work for you.
The ByBit API endpoints which use POST require the same HMAC encryption of the data as in GET request. Instead of signing the parameters in the query string (and appending &sign=xxxx to it), you:
sign the serialized to JSON object,
then add the [object].sign = "xxxx" to the object before the POST.
This is where it gets tricky for your C# class. You have CancelOrderContent with a sign property. This will serialize the key 'sign' with the blank value. However, the ByBit API won't accept that because the data signed will be different.
Either you must
serialize an object without the 'sign' key (CancelOrderContentNosignkey),
copy the properties from CancelOrderContentNosignkey to a new CancelOrderContent
add the signature hash string to the CancelOrderContent object.sign prop
then post the serialized object with the sign key/value pair,
or...
serialize the object as you are now,
but then munge the serialized string to remove the ,"sign":'' part.
Then sign that string,
then add the sign value to the object,
serialise it to JSON again, and
POST that as the data.
I believe this will work, since i had to to that to get it working in JS. However, adding the sign key/value is easier since there's no classes.
A variation of this would be to make CancelOrderContent a dynamic type, where you don't add the 'sign' key/value until after serializing/signing it.
Note, when you manually serialize the object to JSON (do not use paramString), in theory the serializer should be the same one used or configured in RestRequest.AddJsonBody()
Sorry i don't have C# code, but this should work.
Thank you #Kwuasimoto for the detailed answer, it steered me in the right direction, however, it didn't quite work for me as is. When passing the fullQuery string as the axios data I was getting the error "missing required parameter 'sign'", but when I replaced the string with URLSearchParams like this
const postData = new URLSearchParams(queryParams);
postData.append("sign", signature);
it worked. Here's the full code I ended up using. It's mostly #Kwuasimoto 's answer with a few of my tweaks.
import crypto from 'crypto'
import axios from 'axios';
const getSignature(parameters: any, secret: string) => {
let orderedParams = "";
Object.keys(parameters).sort().forEach(function(key) {
orderedParams += key + "=" + parameters[key] + "&";
});
orderedParams = orderedParams.substring(0, orderedParams.length - 1);
return crypto.createHmac('sha256', secret).update(orderedParams).digest('hex');
}
const postSpotOrder = async () => {
const queryParams = {
api_key: bybitApiKey,
qty: 10,
recvWindow: 10000,
side: "BUY",
symbol: "ETHUSDT",
timestamp: Date.now(),
type: "MARKET",
};
const signature = getSignature(queryParams, bybitSecret);
const postData = new URLSearchParams(queryParams);
postData.append("sign", signature);
let result = await axios(`https://api-testnet.bybit.com/spot/v1/order`, {
withCredentials: true,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
method: "POST",
data: postData,
});
console.log(`Post Status`, result.status);
console.log(`Post Body`, result.data);
};
This is my API method and it takes 3 parameters from body
public async Task<IEnumerable<EnCurso>> GetIdCondByDTRuta(EnCurso encurso)
{
var db = dbConnection();
return await db.QueryAsync<EnCurso>("select * from public.tb_encurso where to_timestamp('" + encurso.inicio+ "','DD/MM/YYYY HH24:MI:SS') between inicio AND fin and idruta = " + encurso.idruta+ " and idbus = "+ encurso.idbus + " and estado=true;", new { encurso.inicio, encurso.idruta, encurso.idbus });
}
[HttpGet("GetIdCondByDTRuta")]
public async Task<IActionResult> GetIdCondByDTRuta([FromBody] EnCurso encurso)
{
return Ok(await _encursoRepository.GetIdCondByDTRuta(encurso));
}
When testing on Postman from body works fine.
But then I don't know how to send content from the app consuming the API.
I tried adding the parameters in the URI, like this
var _URI = "http://XXX.XXX.0.XX:4XXX8/api/encurso/GetIdCondByDTRuta.json?inicio:" + encurso.inicio + "&idruta:" + encurso.idruta + "&idbus:" + encurso.idbus;
HttpResponseMessage result = await client.GetAsync(_URI);
Doesn't work. I tried search for a httpclient method that takes the Uri and content, as the post do, but GET methods don't have the option
HTTP Get methods should not contain a body as referenced in this thread: HTTP GET with request body.
When creating Get API methods it is better to use [FromQuery]-FromQueryAttribute Class this will bind the data to primitive types. So you will need 3 separate parameters. If you want to bind to an object you will need to create a custom ModelBinder.
This doc from Microsoft goes through your options when trying to bind data in an ASP.Net core/5 API Binding source parameter inference
I am trying to set up a small ASP.NET Web API projects so I can post data to the database from a small React.JS project. I tried alot of sollutions but the results made no sense and I have no idea how to fix it anymore.
I have this very simple model:
public class Hour
{
public int WeekID { get; set; }
}
And this is my controller
[HttpPost]
public IHttpActionResult AddHour(Hour hour)
{
return Ok();
}
This is the method that I use to POST my data
export const SaveWeek = weekData=> {
const headers = new Headers();
headers.append("Content-Type", "application/json");
const Week= {
method: "POST",
headers,
mode: "cors",
body: weekData
};
console.log("Hours:");
// Returns {"WeekID": 1}
console.log(Hours.body);
return axios.post("http://localhost:52350/api/REST/AddHour", {
Week
});
};
The way I call this SaveWeek method in React is:
// The JSON parameter is for testing hard coded to: {"WeekID": 1}
handleSave = async json => {
const data = await SaveWeek(json);
console.log(data);
this.closeModal();
};
I know that the axios POST request works, the way I tested that is by changing the method to not use any parameters and looking at the result that where received:
[HttpPost]
public IHttpActionResult AddHour(Hour hour)
{
// This returns a string in which the data that I sent
// can be found.
string body = Request.Content.ReadAsStringAsync().Result;
return Ok();
}
The weird thing is that the body will be filled with data when the method does not contain any parameters, but when I provide the method with the Hour object parameter the body will be an empty string (""). And also the Hour object parameter wont be filled with the values that I provide it.
What am I doing wrong here?
According to https://github.com/axios/axios#axiosposturl-data-config axios.post has following signature
axios.post(url[, data[, config]])
So you just need to change your request to
export const SaveWeek = weekData => {
//headers should be simple object, not Headers
const headers = {
"Content-Type": "application/json"
};
//removed body, because we pass data as second parameter
//removed method, because 'axios.post' implies using "post" method
const Config = {
headers,
mode: "cors"
};
const url = "http://localhost:52350/api/REST/AddHour";
return axios.post(url, weekData, Config);
}
An incoming request to the ASP.Net Web API pipeline is read as a forward-only stream for super speed. Once it has been read it cannot be read again.
[HttpPost]
public IHttpActionResult AddHour(Hour hour)
{
// With model binding
// use hour.WeekID
}
In this first example model binding is already done and once it has been read it cannot be read again. Hence, Request.Content will be empty after that.
[HttpPost]
public IHttpActionResult AddHour()
{
// Without model binding
// use Request.Content
}
In second example it does not use model binding therefore still has the Request.Content property populated.
Use one or the other, not both, do not mix with MVC model binding which works differently.
A better explanation is available in this blog post
http://www.hackered.co.uk/articles/asp-net-web-api-why-is-the-request-Content-empty-when-the-model-is-populated
I try to update the identity of a worker on my project, I use HttpClient with a put, working in Angular 6 project and web API 2 on .NET Core. You can see here the request on the front-end side:
updateWorkerIdentity(worker: WorkerRead) : Observable<WorkerRead> {
const url = 'workerinfo/activeContractId=' + worker.activeContract.id;
return this.httpClient.put<WorkerRead>(url , JSON.stringify(worker) );
}
And at the API side:
[HttpPut("{activeContractId}")]
public async Task<IActionResult> Put([FromRoute] string activeContractId, [FromBody] WorkerRead worker)
{
var companyId = GetCompanyId();
var period = GetPeriod();
var language = GetLanguage();
var workerInfo = await _workerInfoService.UpdateWorkerIdentity(companyId, activeContractId, language, worker);
return Ok(workerInfo);
}
the activeContractId coming from the [FromRoute] is well sent but the worker is still null.
The worker sent from the body is well sent as you can see here in the payload:
and the Content-Type of the header is well application/JSON.
Anyone has an idea?
Everything on the server side looks OK for a simple endpoint.
However, based on the [HttpPut("{activeContractId}")] route template the request on the client side should be refactored to match the expected template
updateWorkerIdentity(worker: WorkerRead) : Observable<WorkerRead> {
const url = 'workerinfo/' + worker.activeContract.id;
return this.httpClient.put<WorkerRead>(url , worker);
}
I suspect that the httpClient will internally stringify the payload before sending.
The above code assumes the controller is defined
[Route("[controller]")]
public class WorkerInfoController : Controller {
//...
//PUT workerinfo/123456
[HttpPut("{activeContractId}")]
public async Task<IActionResult> Put([FromRoute] string activeContractId, [FromBody] WorkerRead worker) {
var companyId = GetCompanyId();
var period = GetPeriod();
var language = GetLanguage();
var workerInfo = await _workerInfoService.UpdateWorkerIdentity(companyId, activeContractId, language, worker);
return Ok(workerInfo);
}
}
I am working on an ASP.NET Core 2.0 RESTful API. I have a scenario where I need to use an HTTPGet method to call an action on my API controller and I need to extract a username and password value that will be used to call another 3rd party API. The username and password are not related to the current logged in user Identity, they are just values I want to send to another API from within my own API, but I do not want to just pass them in a query string.
Can I use basic authentication in the client to add the username and password to the HttpRequestMessage authentication header and then extract that header in my ASP.NET Core 2.0 API controller action?
My client wold have something like this in the code that will call the API
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, relativeUrl);
var byteArray = new UTF8Encoding().GetBytes(string.Format($"username:password"));
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
and, my API controller action would start something like this;
[HttpGet()]
public IActionResult GetUploadedFileList([FromQuery]int pageNumber, [FromQuery]int pageSize)
{
//Extract Authentication header values for username and password
}
Can anyone provide an example of how to get the Authorization header from the HTTPGet request
I realize I can easily do this with an HTTPPost [FromBody] but my use case calls for this method to be an HTTGet.
Thanks in advance for any help.
EDIT 1 - SOLUTION
I was able to get the code below to work, thanks to some hints from this link. Although this seems like a lot of work, so if anyone has a better or cleaner solution, please post your example.
[HttpGet()]
public IActionResult GetUploadedFiles([FromQuery]int pageNumber, [FromQuery]int pageSize)
{
string username = string.Empty;
string password = string.Empty;
if (Request.Headers.TryGetValue("Authorization", out StringValues authToken))
{
string authHeader = authToken.First();
string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim();
Encoding encoding = Encoding.GetEncoding("iso-8859-1");
string usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));
int seperatorIndex = usernamePassword.IndexOf(':');
username = usernamePassword.Substring(0, seperatorIndex);
password = usernamePassword.Substring(seperatorIndex + 1);
}
else
{
return BadRequest("Missing Authorization Header.");
}
//Build FilesUploadedListRequest
FilesUploadedListRequest filesUploadedListRequest = new FilesUploadedListRequest
{
Username = username,
Password = password,
PageNumber = pageNumber,
PageSize = pageSize
};
//Call GetUploadedFilesList
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;
Task<FilesUploadedListResponse> FilesUploadedListResponse = _clientService.GetListOfUploadedFilesAsync(filesUploadedListRequest, cancellationToken);
//Return results
if (filesUploadedListResponse.Result.Success)
{
return Ok(filesUploadedListResponse.Result);
}
return StatusCode(filesUploadedListResponse.Result.StatusCode, filesUploadedListResponse.Result.Reason);
}
ASP.NET Core supports a [FromHeader] attribute for action parameters, similar to [FromBody] and [FromQuery]. So adding a [FromHeader]string authorization arg to your action will shave a couple lines off your solution, as well as make the method more testable since you can avoid accessing the Request object.