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 param | Effect |
|---|---|
status | One 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_id | Restrict to events for a single purchase (none for balance.deposited). |
limit | 1–200, default 50. |
cursor | Opaque cursor returned in next_cursor — for paging older events. |
Each list item carries:
delivery_status—pending(queued, awaiting next attempt) /delivered/failed. A row may transiently exposesendingwhile a sender replica holds the claim (≤120s); the public?status=filter does not acceptsending.delivery_attempts— counter, includes any manual replays.last_response_code— your endpoint’s most recent HTTP status, or0for 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.