Documentation

API Reference

Automate sub-user creation, bandwidth allocation, and usage tracking for your own clients. API access is available to approved resellers — request it from API Keys in your dashboard once you're a reseller.

GuidesHow reselling works →Automating provisioning →Auto-topup →IP whitelisting →Pricing your service →

Machine-readable spec (OpenAPI 3): /api/v1/openapi.json — import it into Postman, Insomnia or a code generator.

Getting access

The API is only available to reseller accounts, and only once API access has been approved for that account — plain customer accounts can't use it, since there's nothing for it to automate on a non-reseller account.

  1. Apply to become a reseller from the homepage or your account settings, if you aren't one already.
  2. Once approved, request API access from the API Keys page in your dashboard — a short note on what you plan to automate is all that's needed.
  3. Once that's approved too, generate a key from the same page.

Authentication

Every request needs your API key in the Authorization header, as a bearer token. Keys are prefixed gp_live_ and are only ever shown once, at creation time — GlobProxy doesn't store the plaintext key afterward, only a hash of it, so if you lose it you'll need to revoke and create a new one.

Authorization: Bearer gp_live_xxxxxxxxxxxxxxxxxxxxxxxx

A request with a missing, malformed, revoked, or otherwise invalid key gets a plain 401 — the response body doesn't distinguish between "no such key" and "key revoked" so a leaked key can't be used to probe your account's key history. Each key has a scope and an optional expiry, both chosen when you create it: read-only keys can only make GET requests (any other method returns 403 insufficient_scope), and a key can be set to expire after 30 days, 90 days, 180 days or a year — an expired key behaves exactly like a revoked one. You can hold up to 25 active keys, so give each integration its own and revoke it independently. The API key list in your dashboard also shows when and from which IP each key was last used.

Scopes

FieldTypeDescription
readscopeGET requests only.
writescopeEverything except allocation: create, change and delete sub-users, top-ups, whitelists, webhooks. Includes read.
allocatescopeResellers only. Move bandwidth between accounts (the /reseller/allocations endpoints). Never implied by write. A key with this scope MUST have an IP allowlist of single addresses or blocks no wider than /24 (IPv4) or /48 (IPv6) — without one, or with a broader block such as a /8, every call is refused with 403 insufficient_scope — because API keys do not go through the 2FA step-up the dashboard applies to the same action. It can be granted without write ("Read + allocate only" in the dashboard) for a bot that should only move bandwidth.

IP allowlist

A key can be restricted to up to 20 source addresses: IPv4 or IPv6 addresses, or CIDR blocks (203.0.113.0/24, 2001:db8:1234::/48; IPv4 prefixes /8 or longer, IPv6 /32 or longer). Write an IPv6 address in any valid spelling — it is compared as a number, not as text. Requests from anywhere else get 403 ip_not_allowed. An empty list means any address.

Managing keys

On the API Keys page you can edit a key's label, IP allowlist, access level and expiry in place (narrowing applies instantly; widening a key asks for 2FA or your password), rotateit (a replacement with the same settings is issued and the old key keeps working for 24 hours so you can switch over — or choose “Revoke the old key now” if it leaked; that works even when you already hold 25 active keys), revoke one or all of them, and see each key's activity: every change (POST / PATCH / DELETE) made with it in the last 30 days, with the IP, status and request id. The same data is at GET /api/v1/account/activity. A key with an expiry sends you an e-mail and an api_key_expiring webhook 7 days before it lapses.

Every response carries an X-Request-Id header — quote it when contacting support. Note that a read key can read sub-user proxy passwords (the same data as the dashboard): protect read-only keys like any other.

Base URL

https://globproxy.com

Errors

Errors are JSON: { "error": "<human message>", "code": "<machine code>" }. Match on code (stable) rather than the message, which may be reworded.

{ "error": "Insufficient bandwidth. Requested 50GB is more than you have available. Purchase more to continue.", "code": "insufficient_bandwidth" }

Status codes

FieldTypeDescription
400Bad RequestMissing or invalid parameters — see the error message for which one.
401UnauthorizedMissing, malformed, or revoked API key.
402Payment RequiredNot enough bandwidth in your account for the request (sub-user creation, top-up).
403ForbiddenAccount suspended, API access not enabled, read-only key used for a write, or an action not permitted on this resource.
404Not FoundThe resource doesn't exist, or doesn't belong to your account.
409ConflictThe sub-user is still being provisioned (whitelist, usage history), or an identical Idempotency-Key request is still in flight.
422UnprocessableIdempotency-Key reused with a different request body.
429Too Many RequestsRate limit exceeded — wait for the Retry-After interval.
500Internal Server ErrorSomething went wrong on our end — safe to retry once; open a support ticket if it persists.
502Bad GatewayThe upstream proxy provider is temporarily unavailable. Nothing was changed — safe to retry.
503Service UnavailableWe couldn't verify your account right now (a transient error on our side). The key is fine — retry after a few seconds; don't delete or re-issue it.

Error codes

FieldTypeDescription
unauthorized401Missing, malformed, revoked, or expired key.
insufficient_scope403The key lacks the scope the endpoint needs (read-only key used for a write, or no "allocate" scope / no narrow IP allowlist on an allocation call).
account_suspended403The account is suspended.
reseller_required / api_access_disabled403The account is not a reseller, or API access has not been approved yet.
forbidden403Not allowed on this resource (e.g. deleting a reseller-allocated sub-user).
invalid_request400A parameter is missing or invalid; the message says which.
insufficient_bandwidth402Your available balance cannot cover the request.
not_found404The resource doesn't exist or isn't yours.
payload_too_large413The request body is over the 1 MiB limit.
conflict / idempotency_conflict409 / 422See status codes above. On money-moving calls a 409 can also mean an earlier request with the same Idempotency-Key finished with an unknown outcome — check the resource, then retry with a NEW key.
ip_not_allowed403The request didn't come from one of the key's allowed IP addresses.
service_unavailable503Transient failure verifying the account; safe to retry.
rate_limited429Too many requests.
upstream_unavailable502The proxy provider could not be reached; nothing changed.
internal_error500Unexpected server error.

Rate limits

Limits are per API key: 120 requests per minute overall, and a tighter 30 per minute for requests that change something (POST, PATCH, DELETE), since those provision real resources. Over the limit you get 429 with code: "rate_limited" and a Retry-After header — back off for that many seconds (always a full minute: the limits are per-minute windows). The limit that was hit is also sent as X-RateLimit-Limit. There is also a generous per-address ceiling that only affects clients sending large volumes of invalid keys. /usage/history has its own, tighter limit of 10 per minute per key (each call fans out to many provider requests). Live usage on /usage is refreshed from the provider at most every 30 seconds per sub-user, so polling it faster just returns the same number.

Every response includes X-RateLimit-Limit: the per-minute limit that applies to that request (120 for a read, 30 for a change, 10 for /usage/history). The limiter cannot report how much of the current window is left, so on a 429 Retry-After is the full 60-second window. Each item in a bulk request counts as one write. Requests with invalid keys are also limited per source address, so a misconfigured client hammering with a bad key is slowed down.

Treat your key like a password regardless — never expose it in client-side JavaScript, a public repo, or a mobile app bundle; proxy calls to it through your own backend if you need to trigger them from a browser or app.

Pagination

List endpoints (/sub-users, /orders) accept limit and offset and return total, limit, offset and has_more. Sub-users default to 100 per page (max 500); orders default to 50 (max 200). Keep requesting with offset += limit until has_more is false.

Time-ordered logs (/webhooks/:id/deliveries, /account/activity, /reseller/allocations) are newest-first and use a cursor: when has_more is true, pass next_before as before and next_before_id as before_id to get the next, older page (sending both keeps rows that share a timestamp from being skipped). Cursor values are UTC timestamps ending in Z, so they can be pasted into a URL as they are.

Idempotency

Send an Idempotency-Key header (8–128 characters: letters, digits and _ - : .) and a retry of the same request — after a timeout or dropped connection — returns the original response (marked Idempotent-Replayed: true) instead of doing the work twice. Keys are remembered for 24 hours and scoped to the endpoint: reusing one with a different body or on a different endpoint returns 422 idempotency_conflict. Only successful calls are replayed; a failed attempt (for example a 402) frees the key so you can retry after fixing the cause.

FieldTypeDescription
POST /sub-usersoptionalA retry can never create a second sub-user, even if it races the original.
POST /sub-users/:id/releaseoptionalA retry never releases twice; an unknown earlier outcome answers 409 like top-ups.
POST /sub-users/:id/topupoptionalA retry never tops up twice. If an earlier attempt with the key died with an unknown outcome you get 409 — check gb_allocated, then retry with a new key.
POST /sub-users/bulk, /sub-users/bulk-topupREQUIREDItem i claims the derived key "<key>#i". A call where every item succeeded is stored and replayed. If any item failed, nothing is stored: re-send the IDENTICAL request with the same key and only the failed items run again — finished ones come back unchanged and a top-up is never applied twice. Changed items need a new key.
POST /reseller/allocations, /allocations/:id/reclaimREQUIREDSame unknown-outcome rule as top-up: a transfer is never applied twice.
curl -X POST https://globproxy.com/api/v1/sub-users \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Idempotency-Key: client-a-2026-10-18-001" \
  -H "Content-Type: application/json" \
  -d '{ "label": "Client A", "gb_allocated": 5 }'

Endpoints

GET/api/v1/account/bandwidthBandwidth balance

Your account's overall bandwidth balance — not per sub-user, see the usage endpoint below for that. allocated_gb is what's already reserved into sub-users; available_gb is what you can still create or top up with.

curl https://globproxy.com/api/v1/account/bandwidth \
  -H "Authorization: Bearer YOUR_KEY"

Response

{
  "total_gb": 500,
  "used_gb": 128.4,
  "allocated_gb": 210,
  "available_gb": 371.6,
  "expires": "never"
}
GET/api/v1/accountAccount and key info

Who this key belongs to and what it may do — scopes, allowed IPs and expiry — so an integration can check itself (and warn before the key lapses) instead of guessing from 403s.

curl https://globproxy.com/api/v1/account \
  -H "Authorization: Bearer YOUR_KEY"

Response

{
  "account": { "id": "9c1e...", "email": "you@example.com", "role": "reseller" },
  "api_key": { "id": "b72d...", "key_prefix": "gp_live_a1b2c3", "label": "billing-bot", "scopes": ["read", "write"],
               "allowed_ips": ["203.0.113.0/24"], "expires_at": "2027-01-10T00:00:00.000Z", "created_at": "2026-10-10T09:00:00.000Z" },
  "features": { "password_rotation": true }
}

features reports optional endpoints that are switched on per deployment — password_rotation: false (an operator kill switch; on by default) means rotate-password answers 503.

GET/api/v1/account/usageAccount usage (daily)

Account-wide data usage per UTC day for the last days days (1–365, default 30), oldest first, gaps filled with 0. Served from our own ledger, so it is cheap, includes deleted sub-users, and is not subject to the tighter /usage/history limit. The ledger is updated every few minutes; first_recorded_day is where it begins (earlier days read 0 because nothing was recorded).

curl "https://globproxy.com/api/v1/account/usage?days=7" \
  -H "Authorization: Bearer YOUR_KEY"

Response

{
  "from": "2026-10-17", "to": "2026-10-23", "days": 7, "total_gb": 12.4081,
  "first_recorded_day": "2026-09-02",
  "usage": [ { "date": "2026-10-17", "gb": 1.2 }, { "date": "2026-10-18", "gb": 0 } ]
}
GET/api/v1/account/activityAPI activity log

Every change (POST / PATCH / DELETE) made with your account's keys in the last 30 days — which key, from which IP, which path, the status and the X-Request-Id. Read-only requests are not logged. Query: key_id, limit (max 200), before (cursor).

curl "https://globproxy.com/api/v1/account/activity?limit=20" \
  -H "Authorization: Bearer YOUR_KEY"

Response

{
  "activity": [
    { "id": 812, "key_id": "b72d...", "key_prefix": "gp_live_a1b2c3", "key_label": "billing-bot", "method": "POST",
      "path": "/api/v1/sub-users/3f1a.../topup", "status": 200, "ip": "203.0.113.7", "request_id": "5e0c...", "created_at": "2026-10-18T09:30:00.000Z" }
  ],
  "has_more": false, "next_before": null, "next_before_id": null
}
GET/api/v1/sub-usersList sub-users

All proxy credentials (sub-users) on your account, newest first. Query parameters: limit, offset (see Pagination) and optional filters: status (pending, active or suspended), label (case-insensitive contains), country (2-letter code), session_type (rotating or sticky), protocol (http or socks5), created_after and created_before (ISO 8601).

curl https://globproxy.com/api/v1/sub-users \
  -H "Authorization: Bearer YOUR_KEY"

Response

{
  "sub_users": [
    {
      "id": "3f1a...",
      "label": "Client A",
      "status": "active",
      "gb_allocated": 5,
      "gb_used": 1.2,
      "gb_remaining": 3.8,
      "country": "us",
      "state": "california",
      "city": null,
      "asn": null,
      "session_type": "rotating",
      "protocol": "http",
      "sticky_duration": null,
      "endpoint_count": 1,
      "host": "proxy.globproxy.com",
      "port": 9000,
      "username": "gp_1760000000000_ab12cd34",
      "password": "0b8f6c0e-...",
      "connection_username": "gp_1760000000000_ab12cd34-type-residential-country-us-state-california",
      "auto_topup": { "enabled": false, "threshold_gb": null, "amount_gb": null },
      "activated_at": "2026-08-01T12:00:00.000Z",
      "created_at": "2026-08-01T12:00:00.000Z"
    }
  ],
  "total": 1,
  "limit": 100,
  "offset": 0,
  "has_more": false
}

A working proxy connection is host:port with connection_username and password — use connection_username (not the raw username) because it has your targeting baked in. The password is the same one shown in your dashboard — treat API keys with the same care as the passwords they can read. For a sticky session, generate a fresh -session-* suffix per connection rather than reusing this one indefinitely; that logic lives client-side, this endpoint just returns the base credential.

POST/api/v1/sub-usersCreate sub-user

Creates a new proxy credential and reserves the requested bandwidth from your account balance atomically — a burst of concurrent creates can't oversubscribe your balance. Supports Idempotency-Key (see above).

curl -X POST https://globproxy.com/api/v1/sub-users \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Client A",
    "gb_allocated": 5,
    "country": "us",
    "state": "california",
    "protocol": "http",
    "session_type": "rotating"
  }'

Body parameters

FieldTypeDescription
gb_allocatednumberRequired. Bandwidth to reserve for this sub-user: between 0.01 and 100000.
labelstringOptional. Your own name for this credential, up to 100 characters.
countrystring2-letter ISO code (e.g. "us"). Required if state, city, or asn is set.
statestringLowercase, no spaces/punctuation. Requires country.
citystringLowercase, no spaces/punctuation. Requires country. state and city can both be set together.
asnstring | numberNumeric ASN, e.g. "13868". Requires country, and must be an ASN that exists in that country — otherwise 400 (a session pinned to an ASN with no peers could never connect).
session_typestring"rotating" (default) or "sticky".
protocolstring"http" (default) or "socks5".
sticky_durationintegerMinutes (1–1440) the sticky session stays on the same peer. Only used when session_type is "sticky". Default 10.
endpoint_countintegerInformational — how many endpoints you intend to generate from this credential, 1–100. Default 1.
auto_topupobjectOptional. Turn on auto-topup at creation: { "enabled": true, "threshold_gb": 1, "amount_gb": 5 } — same rules as PATCH /auto-topup.

Response — 201 Created

{
  "id": "3f1a...",
  "label": "Client A",
  "status": "active",
  "host": "proxy.globproxy.com",
  "port": 9000,
  "username": "gp_1760000000000_ab12cd34",
  "password": "0b8f6c0e-...",
  "connection_username": "gp_1760000000000_ab12cd34-type-residential-country-us-state-california",
  "gb_allocated": 5,
  "country": "us"
  // ...same fields as the list endpoint
}

status can come back "pending" instead of "active" if the upstream provisioning step is briefly unavailable — the credential and bandwidth reservation are still created and activation is retried automatically in the background (typically within a few minutes). While pending, username, password and connection_username are null, and pending_reason says why it isn't live yet: "capacity" (network capacity is being added), "unavailable" (a temporary outage) or "error". Subscribe to the session_activated webhook, or poll GET /api/v1/sub-users/:id, to know when it's live.

GET/api/v1/sub-users/:idGet sub-user

One sub-user, in the same shape as a list entry (including password and connection_username).

curl https://globproxy.com/api/v1/sub-users/SUB_USER_ID \
  -H "Authorization: Bearer YOUR_KEY"
PATCH/api/v1/sub-users/:idUpdate sub-user

Changes the label or connection settings. Targeting lives in the connection username, so this takes effect immediately and doesn't touch bandwidth (use /topup for that). Send only the fields you want to change; send null to clear a targeting field. Changing country also clears a state / city you don't send in the same request, since the old ones belong to the old country. Sub-users allocated by a reseller, and suspended ones, can't be edited (403).

curl -X PATCH https://globproxy.com/api/v1/sub-users/SUB_USER_ID \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "label": "Client A (EU)", "country": "de", "city": "berlin" }'

Body parameters

FieldTypeDescription
labelstring | nullUp to 100 characters.
country / state / city / asnstring | nullSame rules as creation; validated together with the values already on the sub-user.
session_typestring"rotating" or "sticky". Changes the port.
protocolstring"http" or "socks5". Changes the port.
sticky_durationinteger | nullMinutes, 1–1440 (sticky only).
endpoint_countintegerInformational, 1–100.

Returns the updated sub-user.

GET/api/v1/sub-users/:id/usageGet sub-user usage

Current usage for a single sub-user, synced live from the upstream provider when the credential is active.

curl https://globproxy.com/api/v1/sub-users/SUB_USER_ID/usage \
  -H "Authorization: Bearer YOUR_KEY"

Response

{
  "id": "3f1a...",
  "label": "Client A",
  "status": "active",
  "protocol": "http",
  "session_type": "rotating",
  "gb_allocated": 5,
  "gb_used": 1.2,
  "gb_remaining": 3.8,
  "country": "us",
  "state": "california",
  "city": null,
  "host": "proxy.globproxy.com",
  "port": 9000,
  "username": "gp_1760000000000_ab12cd34",
  "connection_username": "gp_1760000000000_ab12cd34-type-residential-country-us-state-california",
  "usage_synced_at": "2026-10-18T09:30:00.000Z"
}

Re-read from the provider at most every 30 seconds per sub-user; usage_synced_at says when the number was last refreshed.

GET/api/v1/sub-users/:id/endpointsProxy list (endpoints)

A ready-made proxy list, built the same way as the dashboard's export. A sticky sub-user returns count stable endpoints (default: its endpoint_count, max 1000), each with its own session id so each keeps its IP for the sub-user's sticky duration. A rotating sub-user returns one endpoint — every connection to it already gets a fresh IP. format=txt returns plain text, one host:port:username:password line per endpoint. 409 while the sub-user is pending or suspended.

curl "https://globproxy.com/api/v1/sub-users/SUB_USER_ID/endpoints?count=3" \
  -H "Authorization: Bearer YOUR_KEY"

Response

{
  "sub_user_id": "3f1a...", "protocol": "http", "session_type": "sticky", "rotating": false, "count": 3,
  "endpoints": [
    { "host": "proxy.globproxy.com", "port": 10000,
      "username": "gp_abc123-type-residential-country-us-lifetime-10-session-FVvegZQ7",
      "password": "…", "session_id": "FVvegZQ7",
      "url": "http://gp_abc123-type-residential-country-us-lifetime-10-session-FVvegZQ7:…@proxy.globproxy.com:10000" },
    { "host": "proxy.globproxy.com", "port": 10000,
      "username": "gp_abc123-type-residential-country-us-lifetime-10-session-bQ2V1sK3",
      "password": "…", "session_id": "bQ2V1sK3", "url": "…" }
  ]
}
GET/api/v1/sub-users/:id/usage/historyUsage history

Bandwidth and request counts over time for one sub-user. days is 1–90 (default 7); granularity is day (default) or hour (up to 30 days). 409 if the sub-user hasn't been activated yet.

curl "https://globproxy.com/api/v1/sub-users/SUB_USER_ID/usage/history?days=7&granularity=day" \
  -H "Authorization: Bearer YOUR_KEY"

Response

{
  "id": "3f1a...",
  "from": "2026-10-11T09:30:00.000Z",
  "to": "2026-10-18T09:30:00.000Z",
  "granularity": "day",
  "total_gb": 1.2,
  "points": [
    { "period_start": "2026-10-11T00:00:00.000Z", "gb": 0.31, "successful_requests": 4120, "failed_requests": 37, "success_rate": 99.1 }
  ]
}
DELETE/api/v1/sub-users/:idDelete sub-user

Deletes a sub-user and releases whatever portion of its bandwidth reservation was never used back to your account balance. Already-consumed usage isn't refunded — only the unused remainder.

curl -X DELETE https://globproxy.com/api/v1/sub-users/SUB_USER_ID \
  -H "Authorization: Bearer YOUR_KEY"

Response

{ "success": true, "gb_released": 3.8 }

If the upstream provider can't be reached the delete fails with 502 and nothing is changed — retry it; a retry after a partial failure completes cleanly. A sub-user that a reseller allocated to you (rather than one you created yourself) can't be deleted through the API, same as in the dashboard — 403 if you try.

POST/api/v1/sub-users/:id/topupTop up sub-user

Adds bandwidth to a sub-user you already have, instead of creating a new one. Reserves the amount from your balance atomically, same guarantee as creation.

curl -X POST https://globproxy.com/api/v1/sub-users/SUB_USER_ID/topup \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "gb_amount": 5 }'

Body parameters

FieldTypeDescription
gb_amountnumberRequired. GB to add: between 0 (exclusive) and 100000.

Response

{ "success": true, "gb_allocated": 10, "gb_added": 5 }

gb_allocated is the sub-user's new total cap, not the amount just added. 402 if your account balance can't cover the top-up. Supports Idempotency-Key (see above).

POST/api/v1/sub-users/bulkCreate sub-users in bulk

Up to 10 sub-users per call, each created independently (one failing doesn't stop or undo the others). Each item takes the same fields as POST /sub-users. Idempotency-Key is required; each item counts as one write.

curl -X POST https://globproxy.com/api/v1/sub-users/bulk \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Idempotency-Key: onboarding-batch-2026-10-18" \
  -H "Content-Type: application/json" \
  -d '{ "items": [ { "label": "Client A", "gb_allocated": 5 }, { "label": "Client B", "gb_allocated": 10, "country": "DE" } ] }'

Response

{
  "results": [
    { "index": 0, "status": 201, "sub_user": { "id": "3f1a...", "label": "Client A", "...": "..." } },
    { "index": 1, "status": 402, "error": { "code": "insufficient_bandwidth", "message": "Insufficient bandwidth..." } }
  ],
  "succeeded": 1, "failed": 1
}

Re-sending the same request (same key) returns the same result without creating anything twice. To retry only the failed items, send them under a new key.

POST/api/v1/sub-users/:id/pausePause / resume a sub-user

POST /sub-users/:id/pause stops a sub-user from using bandwidth while keeping its allocation, credentials and settings; POST /sub-users/:id/resume turns it back on. Both are idempotent (repeating them answers 200 with changed: false). Only a sub-user you paused can be resumed this way — one suspended by GlobProxy answers 403 (see can_resume on the sub-user). A paused sub-user is not affected by account-level suspension cycles. Fires session_suspended / session_resumed with source: "customer".

curl -X POST https://globproxy.com/api/v1/sub-users/SUB_USER_ID/pause \
  -H "Authorization: Bearer YOUR_KEY"

{ "id": "…", "status": "suspended", "changed": true }
POST/api/v1/sub-users/bulk-actionDelete, pause or resume in bulk

Up to 20 sub-users per call: { "action": "delete" | "pause" | "resume", "ids": [...] }. Items are independent and each counts as one write. Every action is safe to repeat, so no Idempotency-Key is needed — if a request times out, send it again (items already deleted report 404). Response: { results: [{ index, id, status, … }], succeeded, failed }; deletes include gb_released.

curl -X POST https://globproxy.com/api/v1/sub-users/bulk-action \
  -H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" \
  -d '{ "action": "pause", "ids": ["SUB_USER_ID_1", "SUB_USER_ID_2"] }'
GET/api/v1/locationsTargeting values: countries, states, cities, ISPs

Find out which values are valid before creating a sub-user. GET /locations lists countries (code → country); GET /locations/:country lists its states with their cities (state / city; add ?cities=false to skip the city lists); GET /locations/:country/asns lists ISPs (asn; empty where ISP targeting isn't offered, 404 for a code that is not a country).

curl https://globproxy.com/api/v1/locations/us/asns \
  -H "Authorization: Bearer YOUR_KEY"

{ "country": "us", "asns": [ { "asn": 7922, "name": "AS7922 COMCAST-7922" }, … ] }
POST/api/v1/sub-users/bulk-topupTop up sub-users in bulk

Up to 20 independent top-ups per call. Idempotency-Key is required; each item counts as one write (a call refused with 429 for lack of budget may already have used part of it, so wait for the Retry-After interval before retrying). If any item fails the call is not stored, so re-sending the same request with the same key re-runs only the failed items. If an earlier attempt with the same key finished with an unknown outcome, this answers 409 instead of re-running it — check the sub-users' gb_allocated, then retry with a new key.

curl -X POST https://globproxy.com/api/v1/sub-users/bulk-topup \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Idempotency-Key: monthly-topup-2026-10" \
  -H "Content-Type: application/json" \
  -d '{ "items": [ { "id": "SUB_USER_ID_1", "gb_amount": 5 }, { "id": "SUB_USER_ID_2", "gb_amount": 2.5 } ] }'

Response

{
  "results": [
    { "index": 0, "id": "SUB_USER_ID_1", "status": 200, "gb_allocated": 15, "gb_added": 5 },
    { "index": 1, "id": "SUB_USER_ID_2", "status": 404, "error": { "code": "not_found", "message": "Sub-user not found" } }
  ],
  "succeeded": 1, "failed": 1
}
POST/api/v1/sub-users/:id/releaseRelease unused bandwidth

Returns bandwidth a sub-user hasn't used to your account balance, without deleting it — the reverse of /topup. The sub-user's live usage plus a small safety margin always stays, so you can only release what is genuinely unused.

curl -X POST https://globproxy.com/api/v1/sub-users/SUB_USER_ID/release \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "gb_amount": 2 }'

Response

{ "success": true, "gb_allocated": 3, "gb_released": 2, "max_releasable_gb": 0.41 }

400 with the releasable amount if you ask for more than is unused, 409 if it would immediately re-trigger auto-topup (lower the amount or turn auto-topup off first), 502 if live usage couldn't be confirmed (nothing is changed). Reseller-allocated sub-users return 403. Accepts an Idempotency-Key header, like top-ups: a retry returns the original response instead of releasing twice.

POST/api/v1/sub-users/:id/rotate-passwordRotate password

Replaces the sub-user's proxy password; the previous one stops working immediately. Returns the new password (it is also what GET /sub-users/:id now shows). Returns 503 while password rotation isn't enabled for the platform.

curl -X POST https://globproxy.com/api/v1/sub-users/SUB_USER_ID/rotate-password \
  -H "Authorization: Bearer YOUR_KEY"

Response

{ "success": true, "password": "5d0c1e7a-..." }
PATCH/api/v1/sub-users/:id/auto-topupConfigure auto-topup

Turns automatic top-up on or off for a sub-user — when its remaining bandwidth drops to threshold_gb, amount_gb is added automatically from your balance, checked on the same cycle that syncs usage. If your balance can't cover the full amount_gb, the sub-user is topped up with whatever the balance can cover and you're notified (auto_topup_failed, reason partial_funding); with no balance at all nothing is added and the same event is sent with reason insufficient_balance. Both numbers are capped at 100000.

curl -X PATCH https://globproxy.com/api/v1/sub-users/SUB_USER_ID/auto-topup \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": true, "threshold_gb": 1, "amount_gb": 5 }'

Body parameters

FieldTypeDescription
enabledbooleanRequired. Turn auto-topup on or off.
threshold_gbnumberRequired when enabling. Remaining GB that triggers a top-up. Can be 0.
amount_gbnumberRequired when enabling. GB added per trigger, must be > 0 and greater than threshold_gb (otherwise the sub-user would be topped up on every check).

Send { "enabled": false } to turn it off — threshold_gb/amount_gb aren't required (or stored) in that case.

GET/api/v1/sub-users/:id/sessionsList sticky sessions

Named sticky sessions on a sticky sub-user. Each has its own connection_username that pins an exit IP for the sub-user's sticky duration.

curl https://globproxy.com/api/v1/sub-users/SUB_USER_ID/sessions \
  -H "Authorization: Bearer YOUR_KEY"

Response

{
  "sessions": [
    { "id": "9c2e...", "label": "scraper-1", "pinned": false, "collapsed": false,
      "session_token": "aB3dE5gH",
      "connection_username": "gp_...-type-residential-country-us-lifetime-10-session-aB3dE5gH",
      "created_at": "2026-10-18T09:00:00.000Z" }
  ]
}
POST/api/v1/sub-users/:id/sessionsCreate sticky session
curl -X POST https://globproxy.com/api/v1/sub-users/SUB_USER_ID/sessions \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "label": "scraper-1" }'

201 with the new session. 400 if the sub-user isn't sticky or already has 200 active sessions.

PATCH/api/v1/sub-users/:id/sessions/:sessionIdUpdate / terminate session
curl -X PATCH https://globproxy.com/api/v1/sub-users/SUB_USER_ID/sessions/SESSION_ID \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "label": "scraper-1b", "pinned": true }'

Accepts label, pinned, collapsed and terminate: true. DELETE /sub-users/:id/sessions/:sessionId removes a session outright.

GET/api/v1/ordersPurchase history

Your bandwidth purchases, newest first (read-only). Query: limit, offset, and optional status (pending, paid, failed, refunded, cancelled), created_after and created_before (ISO 8601). One order: GET /api/v1/orders/:id (same fields, plus billed_to — the buyer block printed on the invoice).

Each order carries what its invoice says: the plan, how it was paid (payment_method card or mpesa, with charged_currency / charged_amount), the tax contained in the price (prices are tax-inclusive; null when none applies), invoice_number and invoice_url (opens for the signed-in account owner, not for API keys), and one entry in credit_notes per refund.

curl https://globproxy.com/api/v1/orders?status=paid \
  -H "Authorization: Bearer YOUR_KEY"

Response

{
  "orders": [
    { "id": "5d0b...", "gb_amount": 100, "amount_usd": 249, "status": "paid", "plan": "growth",
      "payment_method": "card", "charged_currency": "USD", "charged_amount": 249,
      "tax": { "label": "VAT", "rate_pct": 16, "included_usd": 34.34 }, "auto_recharge": false, "dispute": null,
      "invoice_number": "INV-000123", "invoice_url": "https://globproxy.com/invoice/5d0b...",
      "refunded_usd": 0, "refunded_at": null, "credit_notes": [],
      "created_at": "2026-10-01T08:00:00.000Z", "paid_at": "2026-10-01T08:01:10.000Z" }
  ],
  "total": 1, "limit": 50, "offset": 0, "has_more": false
}
POST/api/v1/ordersBuy bandwidth

Starts a purchase and returns a hosted card-checkout link — send it to whoever pays. Nothing is charged until they complete the payment; the bandwidth is then credited automatically (a bandwidth_credited webhook with source purchase, and the order turns paid in GET /orders/:id). Body: plan (micro, starter, growth, scale or custom with a whole-number gbfrom 1 to 499). Card only — M-Pesa is available in the dashboard. Starting a checkout cancels the account's other unpaid orders, as the buy page does. Idempotency-Key is optional: a retry returns the same order and link. Needs the write scope.

curl -X POST https://globproxy.com/api/v1/orders \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Idempotency-Key: restock-2026-10-24" \
  -H "Content-Type: application/json" \
  -d '{ "plan": "custom", "gb": 37 }'

Response (201)

{
  "order": { "id": "5d0b...", "gb_amount": 37, "amount_usd": 103.6, "status": "pending", "created_at": "2026-10-24T08:00:00.000Z" },
  "checkout_url": "https://checkout.paystack.com/..."
}
GET/api/v1/pricingPrices

The plans POST /orders sells and the custom range, in USD. Add ?gb=37 to price exactly that quantity — the same calculation the checkout charges with.

curl "https://globproxy.com/api/v1/pricing?gb=37" \
  -H "Authorization: Bearer YOUR_KEY"

Response

{
  "currency": "USD",
  "plans": [ { "slug": "micro", "name": "Micro", "gb": 1, "price_per_gb": 4.5, "total_usd": 4.5 }, … ],
  "custom": { "min_gb": 1, "max_gb": 499 },
  "quote": { "gb": 37, "total_usd": 103.6, "price_per_gb": 2.8 }
}
GET/api/v1/reseller/allocationsAllocation history (resellers)

Your bandwidth transfers to clients, newest first — the same data as the dashboard's Transfers page. Query: limit (max 200), before (cursor). Needs a read key.

curl "https://globproxy.com/api/v1/reseller/allocations?limit=20" \
  -H "Authorization: Bearer YOUR_KEY"

Response

{
  "transfers": [
    { "id": "e4a1...", "recipient_email": "client@example.com", "gb_amount": 25, "gb_reclaimed": 0, "gb_net": 25,
      "status": "completed", "note": "October top-up", "created_at": "2026-10-18T09:30:00.000Z" }
  ],
  "has_more": false, "next_before": null, "next_before_id": null
}
POST/api/v1/reseller/allocationsAllocate bandwidth to a client (resellers)

Moves GB from your stock to a registered client account — the same operation as the dashboard, with the same notifications and webhooks (bandwidth_allocated to you, bandwidth_credited to the client). Requires a key with the allocate scope and an IP allowlist, and an Idempotency-Key. Unknown, suspended and deleted accounts all answer the same 404, so this can't be used to discover who has an account.

curl -X POST https://globproxy.com/api/v1/reseller/allocations \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Idempotency-Key: alloc-client-42-2026-10" \
  -H "Content-Type: application/json" \
  -d '{ "recipient_email": "client@example.com", "gb_allocated": 25, "note": "October top-up" }'

Body parameters

FieldTypeDescription
recipient_emailstringRequired. The client's GlobProxy account e-mail. If that address has no confirmed account yet, an invitation is created instead (see below).
gb_allocatednumberRequired. At least 0.1, at most 3 decimal places.
notestringOptional, up to 200 characters.

Response (201)

{ "transfer_id": "e4a1...", "status": "active", "pending_id": null, "expires_at": null, "gb": 25, "recipient_email": "client@example.com" }

For an address with no confirmed account the call still succeeds: the GB is reserved out of your stock, the person is e-mailed an invitation, and you get "status": "pending_signup" with a pending_id and an expires_at (30 days). When they sign up and confirm that address the bandwidth is added automatically and you receive a bandwidth_allocated webhook with the new transfer_id. An unclaimed invitation returns to your stock when it expires; list your invitations with GET /reseller/invitations lower it with PATCH /reseller/invitations/:id or withdraw it early with DELETE /reseller/invitations/:id (the pending_id), or do either from your dashboard's Transfers page.

402 insufficient_bandwidth if your stock can't cover it. Shares the dashboard's per-reseller throttle.

POST/api/v1/reseller/allocations/:id/reclaimReclaim an allocation (resellers)

Takes back the unused part of a transfer (a typo'd e-mail, a cancelled order). Only bandwidth the client has not used, assigned to a sub-user or since topped up past can move back, and only within the reclaim window — the same rules as the dashboard. gb is optional (default: everything reclaimable): send no body at all to reclaim everything, but a body that is present must be a valid JSON object — a malformed one is a 400 and is never read as "reclaim everything". Needs the allocate scope and an Idempotency-Key; the client receives a bandwidth_reclaimed webhook and an e-mail.

curl -X POST https://globproxy.com/api/v1/reseller/allocations/TRANSFER_ID/reclaim \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Idempotency-Key: reclaim-e4a1-001" \
  -H "Content-Type: application/json" \
  -d '{ "gb": 10 }'

Response

{ "transfer_id": "e4a1...", "reclaimed_gb": 10, "remaining_gb": 15 }
GET/api/v1/reseller/settingsStock and low-stock alert (resellers)

Your stock position and the level that triggers your low-stock e-mail. reserved_in_invitations_gb is stock held for invitations nobody has claimed yet — it is already out of available_gb. Change the alert level with PATCH /api/v1/reseller/settings and a body of { "low_stock_gb": 50 } (null or 0 turns it off; needs a key with the write scope).

curl https://globproxy.com/api/v1/reseller/settings \
  -H "Authorization: Bearer YOUR_KEY"

Response

{ "total_gb": 500, "allocated_gb": 120, "available_gb": 380, "reserved_in_invitations_gb": 20, "low_stock_gb": 50 }
GET/api/v1/reseller/clientsClient roster (resellers)

One row per account you have sent bandwidth to — what they were sent, what you reclaimed, and how much of that they have used. The same data as the dashboard's Sub-users page and its CSV export. Query parameters: q (contains-match on email or name), filter (all, unused — nothing used yet, exhausted — everything you sent is used, or idle — no transfer in 30 days), sort (recent, sent, used, remaining or name), limit (max 100) and offset. Most recent activity first by default.

curl "https://globproxy.com/api/v1/reseller/clients?q=acme" \
  -H "Authorization: Bearer YOUR_KEY"

Response

{
  "clients": [
    { "email": "ops@acme.example", "name": "Acme Ops", "account": "active", "transfers": 3,
      "gb_sent": 75, "gb_reclaimed": 5, "gb_net": 70, "gb_used": 41.2, "gb_unassigned": 28.8,
      "last_transfer_at": "2026-10-18T09:30:00.000Z", "last_note": "October top-up" }
  ],
  "total": 1, "limit": 50, "offset": 0, "has_more": false
}
GET/api/v1/reseller/invitationsList invitations (resellers)

Invitations created when you allocated to an address with no confirmed account yet. Optional status (pending | claimed | cancelled | expired); without it every state from the last 90 days is returned. id is the pending_id from the allocation response.

curl "https://globproxy.com/api/v1/reseller/invitations?status=pending" \
  -H "Authorization: Bearer YOUR_KEY"

Response

{
  "invitations": [
    { "id": "9c2e...", "recipient_email": "new.client@example.com", "gb": 25, "note": null, "status": "pending",
      "created_at": "2026-10-18T09:30:00.000Z", "expires_at": "2026-11-17T09:30:00.000Z", "resolved_at": null }
  ],
  "total": 1, "limit": 50, "offset": 0, "has_more": false
}
PATCH/api/v1/reseller/invitations/:idLower an invitation (resellers)

Lowers the amount reserved on an invitation nobody has claimed; the difference returns to your available balance immediately. The expiry and reminder count are unchanged. To raise it, allocate to the address again; to remove it, use DELETE. Answers 400 if the new amount is not smaller, 404 once the invitation was claimed, cancelled or expired. Needs the allocate scope.

curl -X PATCH https://globproxy.com/api/v1/reseller/invitations/PENDING_ID \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"gb": 10}'

Response

{ "success": true, "gb": 10, "returned_gb": 15 }
DELETE/api/v1/reseller/invitations/:idCancel an invitation (resellers)

Withdraws an invitation nobody has claimed; the reserved stock returns to your available balance immediately. Needs the allocate scope. Answers 404 once the invitation was claimed, cancelled or expired — so repeating a call after a timeout can never return stock twice.

curl -X DELETE https://globproxy.com/api/v1/reseller/invitations/PENDING_ID \
  -H "Authorization: Bearer YOUR_KEY"

Response

{ "success": true, "returned_gb": 25 }
POST/api/v1/reseller/invitations/:id/remindRemind an invitee (resellers)

E-mails the invitee again about an invitation nobody has claimed. One reminder every 24 hours (429 until then) and at most three per invitation (409 after that). Only addresses without a usable account are mailed. If the e-mail provider rejects the message the call returns 502 and the reminder is not counted. Needs the allocate scope.

curl -X POST https://globproxy.com/api/v1/reseller/invitations/PENDING_ID/remind \
  -H "Authorization: Bearer YOUR_KEY"

Response

{ "success": true, "reminders_left": 2, "expires_at": "2026-11-20T09:30:00.000Z" }
GET/api/v1/sub-users/:id/whitelistList whitelisted IPs

IP addresses currently allowed to use this sub-user's credentials.

curl https://globproxy.com/api/v1/sub-users/SUB_USER_ID/whitelist \
  -H "Authorization: Bearer YOUR_KEY"

Response

{
  "ips": [
    { "ip": "203.0.113.4", "description": "Office IP" }
  ]
}
POST/api/v1/sub-users/:id/whitelistAdd whitelisted IP
curl -X POST https://globproxy.com/api/v1/sub-users/SUB_USER_ID/whitelist \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "ip": "203.0.113.4", "description": "Office IP" }'

Body parameters

FieldTypeDescription
ipstringRequired. A single IPv4 or IPv6 address — no CIDR ranges.
descriptionstringRequired. 1–100 characters, e.g. "Office IP".

Returns the sub-user's full updated whitelist, same shape as the GET above.

DELETE/api/v1/sub-users/:id/whitelistRemove whitelisted IP
curl -X DELETE "https://globproxy.com/api/v1/sub-users/SUB_USER_ID/whitelist?ip=203.0.113.4" \
  -H "Authorization: Bearer YOUR_KEY"

ip is a query parameter here, not a body field.

POST/api/v1/sub-users/:id/whitelist/bulkBulk add whitelisted IPs
curl -X POST https://globproxy.com/api/v1/sub-users/SUB_USER_ID/whitelist/bulk \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "ip": "203.0.113.4", "description": "Office IP" },
      { "ip": "198.51.100.9", "description": "Home IP" }
    ]
  }'

Up to 150 IPs per request. All-or-nothing — the first invalid entry rejects the whole batch.

DELETE/api/v1/sub-users/:id/whitelist/bulkBulk remove whitelisted IPs
curl -X DELETE https://globproxy.com/api/v1/sub-users/SUB_USER_ID/whitelist/bulk \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "ips": ["203.0.113.4", "198.51.100.9"] }'

A POST-shaped body on a DELETE — deliberate, so it can carry an arbitrary-length list rather than being limited to what fits in a query string.

Webhooks

Get an HTTPS POST when something happens on your account. Create endpoints in Settings → Webhooks or over the API (below). Each endpoint subscribes to the events it wants; up to 10 endpoints per account. Adding, changing, rotating or deleting an endpoint — from the dashboard or with an API key — is recorded in your account's security log and e-mailed to you (at most one e-mail per kind of change every 10 minutes). Events caused by your own request are first delivered right after that request returns; events raised by background checks (session_low, session_exhausted, auto_topup_*, session_activated, bandwidth_threshold_crossed) can arrive up to about five minutes after the cause. Endpoint URLs must be public https:// addresses on port 443 (or 8443) — private, loopback and link-local destinations (including hostnames that resolve to them), GlobProxy's own domains and redirects are rejected.

Events

FieldTypeDescription
session_createdeventA sub-user was created (dashboard or API). data: credential_id, label, protocol, session_type, gb_allocated, country, state, city, asn, status, source.
session_activatedeventA pending sub-user finished provisioning and is now live. data: credential_id, label, gb_allocated, status.
session_deletedeventA sub-user was deleted. data: credential_id, label, gb_allocated, gb_used, gb_released, source.
session_topped_upeventGB were added to a sub-user (manual top-up). data: credential_id, label, gb_added, gb_allocated, source.
session_releasedeventUnused GB were returned from a sub-user to your balance (the /release endpoint or the dashboard). data: credential_id, label, gb_released, gb_allocated, source.
session_loweventA sub-user has used 80% or 95% of its allocation (sent once per tier; not sent while auto-topup is able to refill it). data: credential_id, label, threshold_pct, used_pct, gb_allocated, gb_used, gb_remaining.
session_exhaustedeventA single sub-user used its entire allocation and has stopped connecting (sent once per exhaustion; not sent while auto-topup is able to refill it — it IS sent if auto-topup is on but your balance cannot fund it). data: credential_id, label, gb_allocated, gb_used.
auto_topup_triggeredeventAuto-topup added bandwidth. data: credential_id, gb_added, gb_allocated, partial (true when your balance could only cover part of the configured amount).
auto_topup_failedeventAuto-topup could not fully run (sent once until it recovers). reason is insufficient_balance, partial_funding or session_limit. data: credential_id, reason, gb_allocated, gb_used, amount_gb (and gb_added for partial_funding).
session_suspendedeventGlobProxy suspended a sub-user (abuse or policy action), the whole account was suspended, or you paused it. data: credential_id, label, source ("admin", "account" or "customer").
session_resumedeventA suspended sub-user was re-enabled (or the account reinstated). data: credential_id, label, source ("admin", "account" or "customer").
auto_topup_updatedeventAuto-topup settings were turned on, off or changed on a sub-user. data: credential_id, enabled, threshold_gb, amount_gb (null when turned off), source ("api" or "dashboard").
session_updatedeventA sub-user's settings were edited in place (label, targeting, protocol, session type, sticky lifetime, endpoint count). data: credential_id, label, changed (the fields), protocol, session_type, sticky_duration, country, state, city, asn, endpoint_count, source.
session_password_rotatedeventA sub-user's proxy password was rotated (the new password is never included). data: credential_id, label.
whitelist_changedeventIPs were added to or removed from a sub-user's whitelist. data: credential_id, action ("added" / "removed"), count, source.
bandwidth_threshold_crossedeventThe share of your account that is COMMITTED (allocated into sub-users) crossed 80% or 95%. data: threshold_pct, allocated_pct (what triggers it), used_pct (GB actually consumed ÷ total), available_gb, total_gb, used_gb, allocated_gb. Checked every 5 minutes. Endpoints that do not also subscribe to bandwidth_exhausted get it once more at 100% (threshold_pct 100). (used_pct used to carry the allocated figure; it now means what it says.)
bandwidth_exhaustedeventAvailable (unallocated) bandwidth reached zero. data: allocated_pct, used_pct, available_gb, total_gb, used_gb, allocated_gb.
bandwidth_creditedeventBandwidth was added to the account. data: gb_credited, source ("purchase", "reseller_allocation", "referral_bonus" or "admin_adjustment"), plus order_id and amount_usd for purchases, transfer_id for allocations, role ("referrer" / "referred") for referral bonuses.
bandwidth_debitedeventUnallocated bandwidth was removed from the account. data: gb_debited, source ("admin_adjustment", or "referral_bonus_reversed" with role referrer/referred when a refund took a referral GB bonus back), plus total_gb and available_gb for admin_adjustment. Purchase refund clawbacks arrive as bandwidth_refunded and reseller reclaims as bandwidth_reclaimed.
bandwidth_allocatedeventYou allocated bandwidth to a client (resellers). data: transfer_id, recipient_email, gb, note.
reseller_invitation_createdeventYou allocated to an address with no confirmed account yet (resellers); the stock is reserved. data: pending_id, recipient_email, gb, total_gb, expires_at, topped_up (true when added to an invitation already open for that address).
reseller_invitation_expiredeventAn invitation was not claimed within 30 days and its stock returned to you (resellers). data: pending_id, recipient_email, gb.
reseller_invitation_cancelledeventAn invitation was cancelled and its stock returned to you (resellers). data: pending_id, recipient_email, gb, source ("dashboard", "api" or "support").
bandwidth_reclaimedeventUnused bandwidth was reclaimed from your account by the reseller who allocated it. data: transfer_id, gb_reclaimed, gb_remaining.
bandwidth_refundedeventA purchase was refunded and its bandwidth taken back. data: order_id, refund_usd, gb_clawed_back, gb_not_recoverable, fully_refunded.
api_key_createdeventAn API key was created (or rotated). data: key_id, label, key_prefix, scopes, expires_at, plus rotated_from (the replaced key's id) when it came from a rotation.
api_key_revokedeventAn API key was revoked, including an old key revoked immediately by a rotation with no grace window. data: key_id, label, key_prefix, reason — "manual" (one key revoked), "revoke_all" (the panic button) or "rotated" (plus replaced_by, the new key id).
api_key_expiringeventAn API key expires within 7 days. data: key_id, label, key_prefix, expires_at.
api_key_updatedeventAn API key was edited. data: key_id, label, key_prefix, changed (the fields), widened (true when the key became more powerful: a scope added, IP restrictions relaxed or the expiry extended), scopes, expires_at. The key itself is never sent.
webhook_endpoint_disabledeventOne of your endpoints was switched off automatically (50 failures in a row, a 410 Gone, or an address that is no longer allowed). Sent to your OTHER endpoints. data: webhook_id, url, reason.
reseller_low_stockeventYour available reseller stock fell below the low-stock level you set (resellers); sent once until stock rises above it again. data: available_gb, low_stock_gb.

Subscribe to ["*"] to receive every event, including ones added later (choose “All events” in the dashboard); without it a new event reaches only the endpoints that name it.

The same catalog, with each event's data keys, is machine-readable at GET /api/v1/webhooks/events.

Payload

POST https://your-server.com/hooks/globproxy
Content-Type: application/json
X-GlobProxy-Event: session_created
X-GlobProxy-Event-Id: 6f1c2d84-...        (same id on every retry — use it to de-duplicate)
X-GlobProxy-Delivery: 0a9b...             (this delivery's id)
X-GlobProxy-Timestamp: 1760780000
X-GlobProxy-Signature-V2: t=1760780000,v1=3c5e...
X-GlobProxy-Signature: 9d41...            (legacy: HMAC of the body alone)
X-GlobProxy-Signature-V2-Previous: t=...,v1=...   (only during a secret-rotation grace window — see below)

{
  "id": "6f1c2d84-...",
  "event": "session_created",
  "api_version": "v1",
  "created_at": "2026-10-18T09:30:00.000Z",
  "data": { "credential_id": "3f1a...", "gb_allocated": 5, "status": "active", "source": "api" }
}

Verifying the signature

Compute HMAC-SHA256 with your endpoint's secret (whsec_…) over `${t}.${rawBody}` using the raw, unparsed request body, compare it to v1 in constant time, and reject timestamps more than ~5 minutes old.

// Node.js
import crypto from 'node:crypto'

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map(p => p.split('=')))
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false      // replay window
  const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex')
  return expected.length === parts.v1.length &&
         crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
}
# Python
import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
// Go
func verify(raw []byte, header, secret string) bool {
	parts := map[string]string{}
	for _, p := range strings.Split(header, ",") {
		if kv := strings.SplitN(p, "=", 2); len(kv) == 2 {
			parts[kv[0]] = kv[1]
		}
	}
	ts, err := strconv.ParseInt(parts["t"], 10, 64)
	if err != nil || math.Abs(float64(time.Now().Unix()-ts)) > 300 {
		return false
	}
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(parts["t"] + "."))
	mac.Write(raw)
	return hmac.Equal([]byte(hex.EncodeToString(mac.Sum(nil))), []byte(parts["v1"]))
}
<?php // PHP
function verify(string $raw, string $header, string $secret): bool {
    $parts = [];
    foreach (explode(',', $header) as $p) {
        [$k, $v] = array_pad(explode('=', $p, 2), 2, '');
        $parts[$k] = $v;
    }
    if (abs(time() - (int)($parts['t'] ?? 0)) > 300) return false;
    $expected = hash_hmac('sha256', ($parts['t'] ?? '') . '.' . $raw, $secret);
    return hash_equals($expected, $parts['v1'] ?? '');
}
# Ruby
require 'openssl'
require 'rack/utils'

def verify(raw_body, header, secret)
  parts = header.split(',').map { |p| p.split('=', 2) }.to_h
  return false if (Time.now.to_i - parts['t'].to_i).abs > 300
  expected = OpenSSL::HMAC.hexdigest('SHA256', secret, "#{parts['t']}.#{raw_body}")
  Rack::Utils.secure_compare(expected, parts['v1'].to_s)
end

Delivery, retries and auto-disable

Managing webhooks over the API

FieldTypeDescription
GET /api/v1/webhooksreadList endpoints (the secret is never listed).
POST /api/v1/webhookswriteBody: url, event_types[] (or ["*"] for every event), description?. Returns the secret once.
GET / PATCH / DELETE /api/v1/webhooks/:idread / writePATCH accepts url, event_types, description, enabled.
POST /api/v1/webhooks/:id/testwriteSends a signed "test" event now (one per 10 seconds per account).
POST /api/v1/webhooks/:id/rotate-secretwriteIssues a new secret (returned once). Body (optional): grace_minutes 0–4320 — keep the old secret signing the -Previous header for that long. Default: the old secret stops immediately.
GET /api/v1/webhooks/eventsreadThe event catalog: every subscribable event with its description and data keys.
GET /api/v1/webhooks/:id/deliveriesreadDelivery log, newest first. Optional limit (max 100), status (succeeded | retrying | failed), event_type (one event name) and before and before_id (cursor — pass next_before and next_before_id when has_more is true).
GET /api/v1/webhooks/:id/deliveries/:deliveryIdreadOne delivery in full: the exact event body (payload) and your endpoint's response_body (first 500 characters).
POST /api/v1/webhooks/:id/deliveries/redeliver-failedwriteCatch-up after an outage: re-sends the endpoint's failed deliveries, oldest first (same ids and bodies, fresh signatures). Body (optional): limit (1–10, default 5). Stops after two failures in a row; call again while more_failed is true. Each delivery counts as one write.
POST /api/v1/webhooks/:id/deliveries/:deliveryId/redeliverwriteRe-sends a stored event (same id and body, fresh signature).
curl -X POST https://globproxy.com/api/v1/webhooks \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/hooks/globproxy", "event_types": ["session_created", "session_activated", "bandwidth_exhausted"] }'

Geo-targeting reference

Targeting is encoded directly in connection_username, in a fixed order: type, country, state and/or city, asn, then (sticky sessions only) lifetime and session id.

FieldTypeDescription
country2-letter ISO code, lowercaseRequired if state, city, or asn is set.
state / citylowercase, no spaces or punctuationBoth can be set together — they are not mutually exclusive.
asnnumeric stringe.g. "13868".

Invalid values (wrong length, disallowed characters) get rejected with a 400 at creation time rather than silently dropped or passed through.

Ports by protocol / session type

FieldTypeDescription
http, rotating9000
http, sticky10000
socks5, rotating11000
socks5, sticky12000

Already have access? Manage your keys from API Keys in your dashboard. Questions not covered here — support@globproxy.com.