1. Signing Requests
  2. Signing algorithm

Signing Requests

Signing algorithm

The signing algorithm is used to generate the sign value for OnlyFans API requests. It is a combination of the time, user-id, endpoint (requested url), and various other values (called "dynamic rules") that get updated periodically. If you're curious how often these values get updated, check out the Dynamic Rules Update Tracker.

In order to generate the sign value, you'll need to get the latest dynamic rules from the GET /rules endpoint.

The general format of the sign value is [start param]:[sha1 hash]:[checksum]:[end param] -> 29764:eba737fa71ae7b9201b8f5ac345153cc8f28895d:a51:66e9ac08

The signing algorithm should be implemented like this:

JavaScript

js
        function signRequest(fullUrl, userId, timestamp, dynamicRules) {
    const time = timestamp || +new Date()
    const url = new URL(fullUrl)
    const msg = [
        dynamicRules["static_param"],
        time,
        url.pathname + url.search, // "/api2/v2/users/me" or "/api2/v2/something/here?queryparam=anything"
        userId || 0 // 0 is the default user id if userId is not provided
    ].join("\n")
    const shaHash = sha1(msg);
    const hashAscii = Buffer.from(shaHash, 'ascii');

    const checksum = dynamicRules["checksum_indexes"].reduce((result, value) => result + hashAscii[value], 0) + dynamicRules["checksum_constant"];
    const sign = [dynamicRules["start"], shaHash, Math.abs(checksum).toString(16), dynamicRules["end"]].join(":")
    return { sign, time }
}

      

Python

py
        def sign_request(dynamic_rules, full_url, user_id, timestamp = None):
    time = timestamp or int(datetime.datetime.now().timestamp()*1000) # in milliseconds
    url = urllib.parse.urlparse(full_url)
    msg = '\n'.join([
        dynamic_rules["static_param"],
        str(time),
        url.path + ('?' + url.query if url.query else ''),  # "/api2/v2/users/me" or "/api2/v2/something/here?queryparam=anything"
        str(user_id or 0)  # 0 is the default user id if user_id is not provided
    ])
    sha_hash = hashlib.sha1(msg.encode()).hexdigest()
    hash_ascii = bytes(sha_hash, 'ascii')
    checksum = sum(hash_ascii[index] for index in dynamic_rules["checksum_indexes"]) + dynamic_rules["checksum_constant"]
    sign = ':'.join([dynamic_rules["start"], sha_hash, format(abs(checksum), 'x'), dynamic_rules["end"]])
    return sign, time

      

Golang

go
        type RulesSchema struct {
  StaticParam      string `json:"static_param"`
  Start            string `json:"start"`
  End              string `json:"end"`
  ChecksumConstant int    `json:"checksum_constant"`
  ChecksumIndexes  []int  `json:"checksum_indexes"`
  // ... other fields have been omitted as they are not necessary for this implementation
}

type SignRequestParams struct {
  Endpoint string
  UserID   string
  Time     string
}

type SignRequestResponse struct {
  Sign string `json:"sign"`
  Time string `json:"time"`
}

func SignRequest(params SignRequestParams, rules *RulesSchema) (SignRequestResponse, error) {
  timeStr := params.Time
  if timeStr == "" {
    timeStr = strconv.FormatInt(time.Now().Unix(), 10)
  }

  parsedURL, err := url.Parse(params.Endpoint)
  if err != nil {
    return SignRequestResponse{}, fmt.Errorf("failed to parse URL: %w", err)
  }

  path := parsedURL.Path
  query := parsedURL.RawQuery

  if query != "" {
    path = path + "?" + query
  }

  userID := "0"
  if params.UserID != "" {
    userID = params.UserID
  }

  msg := strings.Join([]string{
    rules.StaticParam,
    timeStr,
    path,
    userID,
  }, "\n")

  hash := sha1.New()
  hash.Write([]byte(msg))
  shaHash := fmt.Sprintf("%x", hash.Sum(nil))
  sha1Bytes := []byte(shaHash)

  checksum := 0
  for _, index := range rules.ChecksumIndexes {
    checksum += int(sha1Bytes[index])
  }
  checksum += rules.ChecksumConstant

  sign := strings.Join([]string{
    rules.Start,
    shaHash,
    fmt.Sprintf("%x", math.Abs(checksum)),
    rules.End,
  }, ":")

  return SignRequestResponse{
    Sign: sign,
    Time: timeStr,
  }, nil
}

      

Testing your implementation

You can test your implementation by comparing the output of your function to the sign value on a request from the OnlyFans website.

  1. Step 1

    Go to the OnlyFans website. Right click the page and select Inspect to open the developer tools. Inspect

  2. Step 2

    Navigate to the Network tab. In the search bar type api2.

  3. Step 3

    Click on any of the requests in the list.

  4. Step 4

    Scroll to the bottom and you'll see the sign value and time value.

  5. Steps
  6. Step 5

    Test your implementation by using the same time value and endpoint as the request you're trying to replicate.

If your implementation is correct, the output should be the same as the sign value in the request.