Reseller Integration Kit
Copy-paste, runnable code to resell ProxyHat proxies from inside your own product. Everything here drives the real /v1 API: a reusable API client, a complete Telegram reseller bot, and a website/storefront backend that provisions a proxy the moment a customer pays. If you want the concepts first — the traffic pool, the sub-user model, and the one trap that catches every reseller — read How to Resell ProxyHat Proxies. This page is the code.
https://api.proxyhat.com/v1 and every request carries Authorization: Bearer __API_KEY__.
Overview
The model is simple: your ProxyHat account holds one paid traffic pool. For each customer you create a sub-user — its own proxy credentials and its own byte cap that ProxyHat enforces upstream. You then hand the customer a ready connection URL and never expose your own account. Three pieces of code turn that into a product:
- A reusable API client — a thin wrapper around the four calls you make constantly.
- A Telegram reseller bot — a customer taps
/buy, you charge them, and the bot DMs back a working proxy. - A storefront backend — after your checkout confirms payment, a
POST /provisionendpoint returns the connection string to your site.
protocol://username:password@gate.proxyhat.com:port) built for their sub-user — never the API key, and never your account credentials. If a customer ever sees __API_KEY__, something is wired wrong.
A Reusable API Client
Almost everything a reseller does is four calls: check how much pool is left to sell, create a customer, mint their connection URL, and later raise their cap. Wrap them once. Note the unit conversion — traffic_limit is always in bytes (1 GB = 1000000000), and 0 means uncapped, so always pass a real positive size.
import secrets
import string
import requests
class ProxyHat:
"""Minimal server-side ProxyHat reseller client. Keep the API key secret."""
BASE = "https://api.proxyhat.com/v1"
GB = 1000 ** 3 # 1 GB in bytes
def __init__(self, api_key):
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
"Accept": "application/json",
})
def available_traffic(self):
"""Bytes still free to allocate across your account (pool left to sell)."""
r = self.session.get(f"{self.BASE}/sub-users")
r.raise_for_status()
return r.json()["meta"]["traffic_available"]
def create_customer(self, name, gb):
"""Provision one sub-user (a customer) capped at `gb` gigabytes."""
alphabet = string.ascii_letters + string.digits
password = "".join(secrets.choice(alphabet) for _ in range(20))
r = self.session.post(f"{self.BASE}/sub-users", json={
"proxy_password": password, # 9-40 alphanumeric chars
"traffic_limit": int(gb * self.GB), # bytes; 0 would mean uncapped
"name": name,
})
r.raise_for_status()
return r.json()["payload"] # includes uuid + proxy_username
def proxy_url(self, sub_user_uuid, country="us", protocol="socks5", mode="rotating"):
"""Return a ready-to-paste connection URL to hand to the customer."""
r = self.session.post(f"{self.BASE}/proxy-descriptors", json={
"sub_user_uuid": sub_user_uuid,
"protocol": protocol, # "socks5" (port 1080) or "http" (8080)
"location": {"country": country}, # ISO code, e.g. "us", "de", "gb"
"session": {"mode": mode}, # "rotating" or "sticky"
"filter": "filter-medium", # AI quality filter (recommended default)
})
r.raise_for_status()
return r.json()["payload"]["url"]
def top_up(self, uuid, gb):
"""Raise one customer's cap to `gb` GB. A pool top-up alone will NOT do
this once you have more than one customer, so this call is mandatory."""
r = self.session.put(f"{self.BASE}/sub-users/_", json={
"uuid": uuid, # target comes from the BODY
"traffic_limit": int(gb * self.GB),
})
r.raise_for_status()
return r.json()["payload"]
# --- Usage ---------------------------------------------------------------
ph = ProxyHat("__API_KEY__") # load from env in real code
print(ph.available_traffic(), "bytes free to sell")
customer = ph.create_customer("customer-acme", gb=5) # 5 GB cap
url = ph.proxy_url(customer["uuid"], country="us")
print(url) # hand this to the customer
import crypto from "node:crypto";
class ProxyHat {
static BASE = "https://api.proxyhat.com/v1";
static GB = 1000 ** 3; // 1 GB in bytes
constructor(apiKey) {
this.headers = {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
Accept: "application/json",
};
}
async #call(method, path, body) {
const res = await fetch(`${ProxyHat.BASE}${path}`, {
method,
headers: this.headers,
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) throw new Error(`${method} ${path} -> ${res.status}`);
return res.json();
}
// Bytes still free to allocate across your account (pool left to sell).
async availableTraffic() {
const { meta } = await this.#call("GET", "/sub-users");
return meta.traffic_available;
}
// Provision one sub-user (a customer) capped at `gb` gigabytes.
async createCustomer(name, gb) {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const password = Array.from({ length: 20 }, () => alphabet[crypto.randomInt(alphabet.length)]).join("");
const { payload } = await this.#call("POST", "/sub-users", {
proxy_password: password, // 9-40 alphanumeric chars
traffic_limit: Math.round(gb * ProxyHat.GB), // bytes; 0 would mean uncapped
name,
});
return payload; // includes uuid + proxy_username
}
// Ready-to-paste connection URL to hand to the customer.
async proxyUrl(subUserUuid, { country = "us", protocol = "socks5", mode = "rotating" } = {}) {
const { payload } = await this.#call("POST", "/proxy-descriptors", {
sub_user_uuid: subUserUuid,
protocol, // "socks5" (port 1080) or "http" (8080)
location: { country }, // ISO code, e.g. "us", "de", "gb"
session: { mode }, // "rotating" or "sticky"
filter: "filter-medium", // AI quality filter (recommended default)
});
return payload.url;
}
// Raise one customer cap to `gb` GB. A pool top-up alone does NOT do this
// once you have more than one customer, so this call is mandatory.
async topUp(uuid, gb) {
const { payload } = await this.#call("PUT", "/sub-users/_", {
uuid, // target comes from the BODY
traffic_limit: Math.round(gb * ProxyHat.GB),
});
return payload;
}
}
// --- Usage ---------------------------------------------------------------
const ph = new ProxyHat("__API_KEY__"); // load from env in real code
console.log(await ph.availableTraffic(), "bytes free to sell");
const customer = await ph.createCustomer("customer-acme", 5); // 5 GB cap
const url = await ph.proxyUrl(customer.uuid, { country: "us" });
console.log(url); // hand this to the customer
Build Your Own Telegram Reseller Bot
A complete, runnable bot: a customer sends /start, then /buy; you charge them; and the bot provisions a capped sub-user, mints its connection URL, and DMs it back. The two blocks below are the same bot in python-telegram-bot (v20+, async) and Telegraf for Node.
Payment is modelled as a clearly-marked pluggable hook so the ProxyHat side is 100% real: charge the customer first (Telegram Stars, crypto, or your own gateway), and only call the provisioning function once payment succeeds. Everything after that hook is production code.
# bot.py -- python-telegram-bot v20+ (async)
import os
import secrets
import string
import httpx
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes
PROXYHAT_API = "https://api.proxyhat.com/v1"
API_KEY = os.environ["PROXYHAT_API_KEY"] # reseller key -- stays on the server
BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"] # from @BotFather
GB = 1000 ** 3
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Accept": "application/json"}
def random_password(n=20):
alphabet = string.ascii_letters + string.digits
return "".join(secrets.choice(alphabet) for _ in range(n))
async def provision_proxy(name, gb, country="us"):
"""Create a capped sub-user and return its ready connection URL."""
async with httpx.AsyncClient(base_url=PROXYHAT_API, headers=HEADERS, timeout=30) as api:
# 1. Create one sub-user for this customer, capped in BYTES.
created = await api.post("/sub-users", json={
"proxy_password": random_password(),
"traffic_limit": int(gb * GB),
"name": name,
})
created.raise_for_status()
sub_user = created.json()["payload"]
# 2. Mint a ready-to-use connection URL for that sub-user.
desc = await api.post("/proxy-descriptors", json={
"sub_user_uuid": sub_user["uuid"],
"protocol": "socks5",
"location": {"country": country},
"session": {"mode": "rotating"},
"filter": "filter-medium",
})
desc.raise_for_status()
return desc.json()["payload"]["url"]
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(
"Welcome! Send /buy to get a 5 GB US residential proxy."
)
async def buy(update: Update, context: ContextTypes.DEFAULT_TYPE):
# ----- PAYMENT HOOK ---------------------------------------------------
# Charge the customer FIRST, then provision. Only reach the code below
# once payment has actually succeeded. With Telegram Stars you would send
# an invoice here (currency "XTR") and move the provisioning call into your
# PreCheckoutQuery / successful_payment handler:
# await context.bot.send_invoice(chat_id, "5 GB proxy", "...", payload="5gb",
# provider_token="", currency="XTR",
# prices=[LabeledPrice("5 GB proxy", 250)])
# For this demo we provision immediately.
# ----------------------------------------------------------------------
customer = f"tg-{update.effective_user.id}"
await update.message.reply_text("Provisioning your proxy...")
url = await provision_proxy(name=customer, gb=5, country="us")
await update.message.reply_text(
f"Your proxy is ready:\n\n`{url}`\n\nPaste it into any SOCKS5 client.",
parse_mode="Markdown",
)
def main():
app = Application.builder().token(BOT_TOKEN).build()
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("buy", buy))
app.run_polling()
if __name__ == "__main__":
main()
// bot.js -- Telegraf (Node.js)
import { Telegraf } from "telegraf";
import crypto from "node:crypto";
const PROXYHAT_API = "https://api.proxyhat.com/v1";
const API_KEY = process.env.PROXYHAT_API_KEY; // reseller key -- stays on the server
const bot = new Telegraf(process.env.TELEGRAM_BOT_TOKEN); // from @BotFather
const GB = 1000 ** 3;
const HEADERS = {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
};
function randomPassword(n = 20) {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
return Array.from({ length: n }, () => alphabet[crypto.randomInt(alphabet.length)]).join("");
}
// Create a capped sub-user and return its ready connection URL.
async function provisionProxy(name, gb, country = "us") {
// 1. Create one sub-user for this customer, capped in BYTES.
const created = await fetch(`${PROXYHAT_API}/sub-users`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
proxy_password: randomPassword(),
traffic_limit: Math.round(gb * GB),
name,
}),
});
if (!created.ok) throw new Error(`create failed: ${created.status}`);
const subUser = (await created.json()).payload;
// 2. Mint a ready-to-use connection URL for that sub-user.
const desc = await fetch(`${PROXYHAT_API}/proxy-descriptors`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
sub_user_uuid: subUser.uuid,
protocol: "socks5",
location: { country },
session: { mode: "rotating" },
filter: "filter-medium",
}),
});
if (!desc.ok) throw new Error(`descriptor failed: ${desc.status}`);
return (await desc.json()).payload.url;
}
bot.start((ctx) => ctx.reply("Welcome! Send /buy to get a 5 GB US residential proxy."));
bot.command("buy", async (ctx) => {
// ----- PAYMENT HOOK ---------------------------------------------------
// Charge the customer FIRST, then provision. Only reach the code below
// once payment has actually succeeded. With Telegram Stars you would send
// an invoice here (currency "XTR") and move the provisioning call into your
// pre_checkout_query / successful_payment handler:
// ctx.replyWithInvoice({ title: "5 GB proxy", description: "...",
// payload: "5gb", provider_token: "", currency: "XTR",
// prices: [{ label: "5 GB proxy", amount: 250 }] });
// For this demo we provision immediately.
// ----------------------------------------------------------------------
const customer = `tg-${ctx.from.id}`;
await ctx.reply("Provisioning your proxy...");
const url = await provisionProxy(customer, 5, "us");
await ctx.reply(
`Your proxy is ready:\n\n\`${url}\`\n\nPaste it into any SOCKS5 client.`,
{ parse_mode: "Markdown" }
);
});
bot.launch();
Set up and run it
Create a bot with @BotFather to get a TELEGRAM_BOT_TOKEN, and mint a ProxyHat API key under Dashboard → Settings → API keys. Keep both in the environment — never in the source.
# 1. Install dependencies
pip install "python-telegram-bot>=20" httpx
# 2. Set your secrets (never commit these)
export TELEGRAM_BOT_TOKEN="123456:ABC-your-botfather-token"
export PROXYHAT_API_KEY="__API_KEY__"
# 3. Run the bot
python bot.py
# 1. Install dependencies (package.json needs "type": "module")
npm install telegraf
# 2. Set your secrets (never commit these)
export TELEGRAM_BOT_TOKEN="123456:ABC-your-botfather-token"
export PROXYHAT_API_KEY="__API_KEY__"
# 3. Run the bot
node bot.js
name (here tg-<telegram_id>) so you can find and renew their sub-user later.
Embed in Your Website / Storefront
To sell from a web store, put a small provisioning endpoint on your backend. After your checkout confirms payment, it creates the sub-user and returns the connection string. The ProxyHat API key stays on the server; the browser only ever sees the resulting proxy URL. Below is the same POST /provision endpoint in Express (Node) and FastAPI (Python).
# main.py -- FastAPI (Python)
import os
import secrets
import string
import httpx
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
PROXYHAT_API = "https://api.proxyhat.com/v1"
API_KEY = os.environ["PROXYHAT_API_KEY"] # server-side only -- never sent to the browser
GB = 1000 ** 3
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Accept": "application/json"}
app = FastAPI()
class Order(BaseModel):
order_id: str
gb: float = 5
country: str = "us"
def random_password(n=20):
alphabet = string.ascii_letters + string.digits
return "".join(secrets.choice(alphabet) for _ in range(n))
@app.post("/provision")
async def provision(order: Order):
# 1. Confirm THIS order is paid before provisioning. Replace is_order_paid
# with your real check (Stripe, Creem, ...). Never provision unpaid.
if not await is_order_paid(order.order_id):
raise HTTPException(status_code=402, detail="Order not paid")
async with httpx.AsyncClient(base_url=PROXYHAT_API, headers=HEADERS, timeout=30) as api:
# 2. Create the customer sub-user, capped in bytes.
created = await api.post("/sub-users", json={
"proxy_password": random_password(),
"traffic_limit": int(order.gb * GB),
"name": f"order-{order.order_id}",
})
if created.status_code != 200:
raise HTTPException(status_code=502, detail="Provisioning failed")
sub_user = created.json()["payload"]
# 3. Mint the connection URL and return ONLY that to the browser.
desc = await api.post("/proxy-descriptors", json={
"sub_user_uuid": sub_user["uuid"],
"protocol": "socks5",
"location": {"country": order.country},
"session": {"mode": "rotating"},
"filter": "filter-medium",
})
if desc.status_code != 200:
raise HTTPException(status_code=502, detail="Provisioning failed")
return {"proxy_url": desc.json()["payload"]["url"]}
// server.js -- Express (Node.js)
import express from "express";
import crypto from "node:crypto";
const PROXYHAT_API = "https://api.proxyhat.com/v1";
const API_KEY = process.env.PROXYHAT_API_KEY; // server-side only -- never sent to the browser
const GB = 1000 ** 3;
const HEADERS = {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
};
const app = express();
app.use(express.json());
function randomPassword(n = 20) {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
return Array.from({ length: n }, () => alphabet[crypto.randomInt(alphabet.length)]).join("");
}
app.post("/provision", async (req, res) => {
const { orderId, gb = 5, country = "us" } = req.body;
// 1. Confirm THIS order is paid before provisioning. Replace isOrderPaid
// with your real check (Stripe, Creem, ...). Never provision unpaid.
if (!(await isOrderPaid(orderId))) {
return res.status(402).json({ error: "Order not paid" });
}
// 2. Create the customer sub-user, capped in bytes.
const created = await fetch(`${PROXYHAT_API}/sub-users`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
proxy_password: randomPassword(),
traffic_limit: Math.round(gb * GB),
name: `order-${orderId}`,
}),
});
if (!created.ok) return res.status(502).json({ error: "Provisioning failed" });
const subUser = (await created.json()).payload;
// 3. Mint the connection URL and return ONLY that to the browser.
const desc = await fetch(`${PROXYHAT_API}/proxy-descriptors`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
sub_user_uuid: subUser.uuid,
protocol: "socks5",
location: { country },
session: { mode: "rotating" },
filter: "filter-medium",
}),
});
if (!desc.ok) return res.status(502).json({ error: "Provisioning failed" });
res.json({ proxy_url: (await desc.json()).payload.url });
});
app.listen(3000, () => console.log("listening on :3000"));
On the frontend, your checkout success page simply calls your own endpoint — for example await fetch("/provision", { method: "POST", body: JSON.stringify({ orderId }) }) — and displays the returned proxy_url for the customer to copy. Because provisioning happens server-to-server, the ProxyHat API key never reaches the browser. Pass an order reference your backend can verify, not the price or byte size the client could tamper with.
Renewals & Lifecycle
After the sale you manage each customer independently: top them up when they renew, reset their meter for a new cycle, and delete them to offboard. The methods below extend the API client from earlier.
top_up per customer, or their proxy stays dead at the old limit even though you paid. The one exception (an account with a single active proxy) never applies to a reseller.
# Add these methods to the ProxyHat client from above.
def reset_usage(self, *uuids):
"""Zero the meter(s) for a new billing cycle. Does NOT refund the pool
-- the bytes a customer already spent stay debited."""
r = self.session.post(f"{self.BASE}/sub-users/reset-usage", json={"ids": list(uuids)})
r.raise_for_status()
return r.json()["payload"]
def offboard(self, uuid):
"""Delete a customer. The password is invalidated immediately (they
disconnect at once); the upstream account is torn down a few minutes later."""
r = self.session.delete(f"{self.BASE}/sub-users/_", json={"uuid": uuid})
r.raise_for_status()
return r.json()["payload"] # {"deleted": true, "status": "deleting"}
# --- A monthly renewal for one customer ----------------------------------
# (Buy more pool first if you are low on traffic_available.)
ph.top_up(uuid, gb=10) # 1) raise the cap -- MANDATORY (the trap)
ph.reset_usage(uuid) # 2) clear the meter for the new cycle
# Over-allocating beyond your pool is rejected:
try:
ph.top_up(uuid, gb=99999) # more than traffic_available
except requests.HTTPError as e:
print(e.response.status_code) # 422 -- buy more pool before allocating
// Add these methods to the ProxyHat client from above.
// Zero the meter(s) for a new billing cycle. Does NOT refund the pool.
async resetUsage(...uuids) {
const { payload } = await this.#call("POST", "/sub-users/reset-usage", { ids: uuids });
return payload;
}
// Delete a customer. Password invalidated immediately; upstream torn down soon after.
async offboard(uuid) {
const { payload } = await this.#call("DELETE", "/sub-users/_", { uuid });
return payload; // { deleted: true, status: "deleting" }
}
// --- A monthly renewal for one customer ----------------------------------
// (Buy more pool first if you are low on traffic_available.)
await ph.topUp(uuid, 10); // 1) raise the cap -- MANDATORY (the trap)
await ph.resetUsage(uuid); // 2) clear the meter for the new cycle
// Over-allocating beyond your pool throws (the #call helper turns non-2xx
// into an error): PUT /sub-users/_ -> 422 when the limit exceeds
// traffic_available. Buy more pool before allocating.
reset-usage only clears the meter so the customer can consume up to their cap again — the bytes they already used are gone from your pool.
Go to Production — Checklist
- Keep the API key server-side. It lives in an environment variable on your backend or bot host — never in frontend code, a mobile app, or anything a customer can inspect. Customers only ever receive a proxy URL.
- Always set a real byte cap.
traffic_limitis in bytes, and0means uncapped (it draws your whole pool). Multiply GB by1000000000and pass a positive number for every paid quota. - Handle 422 over-allocation. Creating or topping up above
meta.traffic_availablereturns422. Catch it, and buy more pool before retrying — you can never oversell beyond the traffic you have actually purchased. - Watch
meta.traffic_available. Read it fromGET /v1/sub-usersbefore provisioning and alert yourself when it runs low, so paid orders never fail for want of inventory. - Respect the 100 sub-users / account cap. Each account holds at most 100 sub-users. Beyond that, shard across multiple ProxyHat accounts (one API key each).
- Provisioning is async upstream. Create returns immediately, but the proxy registers a moment later — while
provisioningistruethe credentials can briefly 401. If you must be certain before delivering, pollGET /v1/sub-users?uuid=untilprovisioningisfalse. - Renew per customer. A pool top-up never raises an existing customer's cap — call
top_up(PUT /v1/sub-users) for each customer you renew. - Mind account-level guards. Changing your account password revokes every API key, and enabling 2FA makes mutating sub-user calls require a
twofa_code. Run automation on a dedicated key and keep those changes out of your integration.
Next Steps
- How to Resell ProxyHat Proxies — the model, the trap, and the dashboard / API / Telegram workflows in depth.
- Sub-Users API — full reference for create / update / delete / reset-usage / bulk operations.
- Proxy Descriptors API — every option for building a customer's connection URL.
- Connecting to a Proxy — the gateway hosts, ports, and full username grammar.