WebhooksOperational Notes

Operational Notes

Practical things to know when running a webhook receiver against TradeOn in production.

Monitoring deliveries

The cabinet exposes GET /api/v1/merchant/webhook-events — a cursor-paginated list of every event we tried to deliver to you, with the latest status. Useful filters:

Query paramEffect
statusOne of pending, delivered, failed. Omit to include all. Unknown values return 400 MERCHANT_INVALID_STATUS. (sending is a transient claim window, not a filterable value.)
order_idRestrict to events for a single purchase (none for balance.deposited).
limit1–200, default 50.
cursorOpaque cursor returned in next_cursor — for paging older events.

Each list item carries:

  • delivery_statuspending (queued, awaiting next attempt) / delivered / failed. A row may transiently expose sending while a sender replica holds the claim (≤120s); the public ?status= filter does not accept sending.
  • delivery_attempts — counter, includes any manual replays.
  • last_response_code — your endpoint’s most recent HTTP status, or 0 for transport errors (DNS, TLS, timeout) and pre-flight SSRF rejections.
  • created_at / delivered_at.

(The merchant cabinet exposes additional diagnostic fields like the truncated response body for support triage; only the fields above are returned by the public /webhook-events endpoint.)

A simple production health signal: alert when pending events older than ~10 minutes exceed a threshold, or when the failed count for a window grows. We do not push this data — poll the endpoint on whatever schedule fits your ops setup.

Response truncation

We store at most the first 500 characters of your response body for every attempt (used for support triage when something fails). Keep your error responses concise — long HTML error pages from a misconfigured load balancer get truncated mid-tag and become hard to read in the cabinet. A short JSON {"error":"…"} is much easier to debug.

Rotating the webhook URL

Update the URL from the merchant cabinet at merchant.tradeon.market in Settings → Webhook. Two relevant controls:

  • Update URL. Save a new URL to redirect future deliveries. Existing in-flight retries continue against the new URL.
  • Regenerate secret. Issue a fresh signing secret (shown once in the UI). The old secret is invalidated immediately on save.

The URL is validated server-side (HTTPS only, public IP, no SSRF-prone hostnames). The same validation runs on every delivery as a DNS-rebinding defence — a URL that passed validation at save time but later resolves to a private range is rejected at delivery as a terminal failure. Keep DNS pointed at a stable public IP.

Webhook secret hygiene

  • Treat the secret like a password. The cabinet shows it only once at the moment you regenerate.
  • If you suspect leakage, regenerate immediately. There is no overlap window — the old secret stops being accepted as soon as the new one is saved. Plan rotations during low-traffic windows so a brief mismatch doesn’t drop events on the floor.
  • The secret has no expiry; we do not enforce rotation.

IP whitelist

The optional IP whitelist on your TradeOn account controls who may call our API with your API key — it has no effect on webhook delivery. We egress webhooks from the TradeOn application servers; if your receiver fronts behind a firewall, the source IPs of webhook requests are not stable enough to allow-list reliably. Use the signature verification (Signature Verification) as your trust boundary.

Maintenance windows

Two patterns to expect:

  • Deploys. Each sender replica delays its first poll by 10 seconds after start to avoid thundering-herd. Events queued during a rolling deploy are picked up on the next poll cycle (5 seconds after the worker is ready). Worst-case extra latency during a deploy is in the tens of seconds.
  • Database maintenance. If we pause writes briefly, events emit but are not queued until writes resume. We catch up on the backlog within a few poll cycles. There is no configurable lag SLA in v1 — assume eventually-consistent within minutes.

Webhook delivery is best-effort within the auto-retry envelope; for accounting-grade reconciliation always pair the live stream with a daily sweep of GET /api/v1/merchant/purchases against your own ledger.