APIGeoIP.RU

Платформа IP-аналитики и антифрода

What is user_id and how to generate it correctly

Why user_id exists

Fraud signals become useful only when events are connected to the same logical user over time. If user identity changes every request, country switch, ASN anomalies, or impossible travel checks become noisy and unreliable.

Requirements for a production grade user_id

  • Stable: same business user always gets the same value.
  • Unique: two users must not share the same identifier.
  • Non reversible: do not expose raw email, phone, or external IDs.
  • Versioned: use prefixes like uid_v1_ for future migrations.
  • Deterministic: identical input and secret produce identical output.

Recommended formula

uid_v1_ + base64url(HMAC_SHA256(namespace + ":" + external_user_key, secret))

This gives strong consistency while protecting internal business identifiers.

Common implementation mistakes

  1. Creating random UUID on every request.
  2. Using plain email as user_id.
  3. Using device id as a user identity.
  4. Running different algorithms in different services.
  5. Changing format without version prefix.

JavaScript (Node.js)

import crypto from "node:crypto";
function toBase64Url(buf) {
  return buf.toString("base64").replace(/+/g, "-").replace(///g, "_").replace(/=+$/g, "");
}
export function buildUserId(externalUserKey, secret, namespace = "geoip_prod") {
  const msg = `${namespace}:${externalUserKey}`;
  const digest = crypto.createHmac("sha256", secret).update(msg, "utf8").digest();
  return `uid_v1_${toBase64Url(digest).slice(0, 32)}`;
}

Python

import base64, hashlib, hmac
def build_user_id(external_user_key: str, secret: str, namespace: str = "geoip_prod") -> str:
    msg = f"{namespace}:{external_user_key}".encode("utf-8")
    digest = hmac.new(secret.encode("utf-8"), msg, hashlib.sha256).digest()
    return "uid_v1_" + base64.urlsafe_b64encode(digest).decode("ascii").rstrip("=")[:32]

PHP

<?php
function build_user_id(string $externalUserKey, string $secret, string $namespace = "geoip_prod"): string {
    $msg = $namespace . ":" . $externalUserKey;
    $digest = hash_hmac("sha256", $msg, $secret, true);
    $b64 = rtrim(strtr(base64_encode($digest), "+/", "-_"), "=");
    return "uid_v1_" . substr($b64, 0, 32);
}

Go

func BuildUserID(externalUserKey, secret, namespace string) string {
  mac := hmac.New(sha256.New, []byte(secret))
  mac.Write([]byte(namespace + ":" + externalUserKey))
  b64u := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
  if len(b64u) > 32 { b64u = b64u[:32] }
  return "uid_v1_" + b64u
}

Java

public static String build(String externalUserKey, String secret, String namespace) throws Exception {
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
    byte[] digest = mac.doFinal((namespace + ":" + externalUserKey).getBytes(StandardCharsets.UTF_8));
    String b64u = Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
    return "uid_v1_" + b64u.substring(0, Math.min(32, b64u.length()));
}

C#

public static string Build(string externalUserKey, string secret, string ns = "geoip_prod") {
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var digest = hmac.ComputeHash(Encoding.UTF8.GetBytes($"{ns}:{externalUserKey}"));
    var b64u = Convert.ToBase64String(digest).Replace("+", "-").Replace("/", "_").TrimEnd((char)61);
    if (b64u.Length > 32) b64u = b64u.Substring(0, 32);
    return $"uid_v1_{b64u}";
}

Integration example

GET /api/?ip=8.8.8.8&user_id=uid_v1_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Authorization: Bearer YOUR_API_KEY

Связаться с нами

Telegram: @apigeoip