Proxy-seller

Webhooks

Updated:

Instead of polling, you can receive the events of an order on your HTTPS endpoint. Webhooks carry the same events as Order events. The event log stays available with or without webhooks, so you can always fall back to polling.

Set up

  • Pass callback_url in Place an order, or set it later with Update order.

  • The response returns webhook_secret (whsec_…) once. Store it: it signs every webhook.

  • Changing callback_url issues a new secret. The previous secret keeps signing webhooks for 24 hours: during this time X-PS-Signature carries two signatures.

Request

Property

Value

Method

POST to callback_url

Headers

Content-Type: application/json; charset=utf-8, User-Agent: ProxySeller-Webhooks/1, X-PS-Timestamp, X-PS-Signature

Body

One event, as in Order events; attempt is the delivery attempt number (1, 2, …)

Success

Any 2xx within 5 seconds. Redirects are not followed, the response body is not read

Delivery

At least once, in no guaranteed order. Deduplicate by event_id

POST /hooks/proxy HTTP/1.1
Host: agent.example.com
Content-Type: application/json; charset=utf-8
User-Agent: ProxySeller-Webhooks/1
X-PS-Timestamp: 1762077838
X-PS-Signature: v1=5f0c2a…9e

{"event_id":"evt_01JZ8QA2C4E6G8J0K2M4P6R8T0","type":"order.active","created_at":"2026-11-02T10:03:58Z","api_version":"v1","order_id":"ord_01JZ8Q7K5N2R4T6V8X0Z2B4D6F","wallet":"0x857b06519e91e3a54538791bdbb0e22373e36b66","attempt":1,"data":{"lines":[{"line_id":"1","status":"active","delivered":2,"qty":2}],"expires_at":"2026-12-02T23:59:59Z"}}

Verify the signature

X-PS-Signature: v1=hex(HMAC-SHA256(key, timestamp + "." + raw_body))

  • key — the bytes (UTF-8) of the whole webhook_secret string, including the whsec_ prefix.

  • timestamp — the value of X-PS-Timestamp, Unix seconds.

  • raw_body — the body exactly as received, before any JSON parsing.

  • During 24 hours after a secret change the header is v1=<current>,v1=<previous>: accept the request if any of them matches.

  • Reject requests whose timestamp differs from your clock by more than 300 seconds.

Python

import hashlib
import hmac
import time

def verify(secret: str, timestamp: str, signature_header: str, raw_body: bytes, tolerance: int = 300) -> bool:
    if abs(time.time() - int(timestamp)) > tolerance:
        return False
    expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    candidates = [part.strip()[3:] for part in signature_header.split(",") if part.strip().startswith("v1=")]
    return any(hmac.compare_digest(expected, candidate) for candidate in candidates)

Node.js

import crypto from 'node:crypto';

function verify(secret, timestamp, signatureHeader, rawBody, toleranceSeconds = 300) {
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest('hex');
  return signatureHeader.split(',').map((s) => s.trim()).filter((s) => s.startsWith('v1='))
    .some((s) => s.length === expected.length + 3
      && crypto.timingSafeEqual(Buffer.from(s.slice(3)), Buffer.from(expected)));
}

Retries

Rule

Value

First attempt

Not earlier than 30 seconds after the event

Retries

After 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h: 8 attempts in about 44.6 hours

Too old

An event older than about 45.6 hours is not sent anymore

Circuit breaker

After 3 failures in a row the other events of the order wait for the next attempt

Disabling

If the last attempt of an event fails and the URL has not accepted any webhook since that event was created, delivery to the URL is disabled. The order itself is not affected

To enable a disabled URL, send it again with Update order: the same URL at most once per hour, a new URL at any time. Missed events are always in Order events.

callback_url rules

Checked when the quote is issued and in PATCH. A violation returns 400 invalid_request:

Rule

detail

Printable ASCII, up to 2048 characters

/callback_url: must be a printable URL up to 2048 characters

https only

/callback_url: only https is allowed

No login or password in the URL

/callback_url: credentials in URL are not allowed

No fragment

/callback_url: fragment is not allowed

Port 443 only

/callback_url: only port 443 is allowed

A public DNS name, or a canonical IPv4 address, or an IPv6 address in brackets

/callback_url: host must be a public DNS name, /callback_url: IPv4 address must be a canonical dotted quad

All addresses of the host are public

/callback_url: host resolves to a private or reserved address, /callback_url: host does not resolve

Not a host of the service itself

/callback_url: host is not allowed

DNS is checked again before every delivery; the request goes only to the checked addresses.

On this page