Skip to main content
ProxyJam can call a URL of yours whenever the status of one of your proxies changes — for example from active to expired. Instead of polling GET /orders, you register one endpoint, keep its signing secret, and get a signed POST with a JSON body for every change. Endpoints are managed through the API at https://api.proxyjam.com/public/v1/webhooks.

Before you start

  • Credentials. The webhook endpoints need full access: a signed-in session, or an API key created without a scope restriction. A key limited to specific scopes, and an OAuth token, are refused with 403 INSUFFICIENT_SCOPE and details.required_scope set to full-access.
  • One active endpoint per account. You can keep several endpoints, but only one of them can be enabled at a time. Creating or enabling a second one answers 409 CONFLICT.
  • A public URL. Use https. The URL may be up to 2048 characters, must not carry a username or password, and must resolve to a public address: hosts such as localhost, private ranges (10.0.0.0/8, 192.168.0.0/16, …), loopback and link-local addresses are refused with 422 VALIDATION_ERROR. To develop locally, expose your server through a tunnel with a public hostname.

Events

There is one event type today. Every endpoint subscribes to it automatically; there is no event list to configure. The body carries a version field — currently 2026-04-01 — that changes if the shape of the payload ever changes.

Payload

The body above is pretty-printed for reading. On the wire it is compact JSON with sorted keys — and the signature covers those exact bytes, so verify the raw body before you parse it.
string
The event id. It stays the same on every retry of the same event, so use it to drop duplicates. Treat it as opaque.
string
The event type, proxy.status_changed. Also sent as X-ProxyJam-Event.
string
Payload schema version, 2026-04-01.
string
When the status changed, ISO 8601 in UTC.
object
object

Request headers

Every delivery is a POST with these headers:

Verifying the signature

Each endpoint has its own signing secret, whsec_ followed by 64 hex characters. It is returned once, as signing_secret, in the response that creates the endpoint. After that the API shows only secret_preview — the first 12 and last 6 characters — so you can tell secrets apart, but cannot recover one. Store it as you would a password. The signature is computed over a string made of three parts joined by dots:
X-ProxyJam-Signature is v1= followed by the hex HMAC-SHA256 of that string, keyed with the whole signing secret, whsec_ prefix included. To verify a delivery:
1

Read the raw body

Take the request body as bytes, before any JSON parsing. Re-serialising a parsed object changes the bytes and breaks the signature.
2

Check the timestamp

Reject the request if X-ProxyJam-Timestamp is more than five minutes away from your clock. Every attempt, retries included, is signed with a fresh timestamp, so a genuine delivery is never old.
3

Recompute and compare

Build the signing string, compute the HMAC, and compare it with the header in constant time.
Answer a delivery that fails verification with 401 or 403; it is not retried.

Responding and retries

Answer with any 2xx status as soon as you have accepted the event, and do the work afterwards. Each attempt waits at most 10 seconds for your response. Redirects are not followed. A delivery gets five attempts in total. The wait before each retry starts at 30 seconds and doubles — about 30 s, 1 min, 2 min and 4 min — and would never exceed two hours. Retries are picked up by a job that runs once a minute, so expect the actual gaps to be up to a minute longer. After the fifth failed attempt the delivery is abandoned. Deleting the endpoint cancels its pending retries, and a retry that comes due while the endpoint is disabled is cancelled instead of sent. Changes that happen while no endpoint is enabled are not delivered later. Because a retry follows any attempt whose answer did not arrive in time, the same event can reach you more than once. Deduplicate on the body’s id. And since events can also arrive out of order, compare occurred_at — or fetch the order — before acting on a status.

Managing endpoints

All five endpoints live under https://api.proxyjam.com/public/v1. An id that does not exist, or belongs to another account, answers 404 NOT_FOUND. Errors use the usual error envelope.

The endpoint object

string
Endpoint id, the {endpoint_id} in the paths above.
string
Always proxy.status_changed for now.
string
Where deliveries are sent.
boolean
Whether the endpoint receives deliveries.
string
The first 12 and last 6 characters of the signing secret.
string | null
When a test delivery was last sent.
string | null
When a delivery last succeeded.

List endpoints

limit is 1–100 (default 50), offset defaults to 0. The response is {"items": [ …endpoint objects… ]}.

Create an endpoint

string
required
The URL to deliver to, 1–2048 characters. See Before you start for what is accepted.
boolean
default:"true"
Create it enabled. Pass false to create a second endpoint while another one is active.
The response is 201 with the endpoint object plus one more field:
string
The full signing secret, whsec_…. Shown in this response only — store it now.

Update an endpoint

string
A new URL, validated like the one at creation. The signing secret stays the same.
boolean
false stops deliveries: new changes are not sent, and retries that come due are cancelled. true turns it back on, provided no other endpoint is enabled; it does not replay what was missed.
Send only the fields you change. The response is the updated endpoint object.

Delete an endpoint

Pending retries for the endpoint are cancelled.

Send a test delivery

Sends one signed delivery to the endpoint right away, exactly as a real one would be signed, and waits for the result. Use it to check your URL and your signature verification before real events arrive. The endpoint must be enabled, or the call answers 409. A test delivery is easy to tell apart: meta.is_test is true, id starts with test:, proxy.status is test, proxy.previous_status is null, change_source is manual_test, and the order id and proxy address are placeholders. It gets a single attempt and is never retried. The response reports what happened:
delivery.status is delivered on success and failed otherwise — or blocked if the URL now resolves to an address that is refused, with error_code ssrf_blocked. When the request did not get an HTTP answer, error_code says why: timeout, network_error or dns_error. response_body_excerpt holds up to the first 1024 characters of what your server answered. The recorded request_headers omit X-ProxyJam-Signature.

Rotating the secret

A secret belongs to its endpoint and cannot be changed. To rotate, create a new endpoint with "enabled": false, give its secret to your receiver, then disable the old endpoint and enable the new one. Delete the old one once nothing depends on it.