Skip to main content
Register an endpoint and CSBoard POSTs order state to it as it changes, instead of you polling for it. The body is byte-identical to what GET /v1/orders/:id returns, so if you have already integrated the pull API there is no second format to learn. Authentication for management calls: any valid read key. Registering an endpoint moves no money, so it does not need a trading key.

Quick start

1

Register your endpoint

The response contains a secret starting csb_whsec_. It is shown once. Store it before you close the terminal — there is no endpoint that returns it again, only one that rotates it.
2

Send yourself a test event

This delivers inline and hands you back the real HTTP status and error text we saw from your server, so a failing endpoint is diagnosable in one call rather than by reading your own logs.
3

Verify the signature and ACK fast

Return 2xx as soon as you have verified and persisted the event. Do the actual work afterwards — see Respond fast below.

Verifying the signature

Every request carries: The signed material is "{t}.{raw_request_body}" and the algorithm is HMAC-SHA256 keyed with your webhook secret. Compare in constant time, and reject anything whose t is more than 5 minutes from your clock.
Verify against the raw request body bytes, before any JSON parse. A framework that re-serialises the body (reordering keys, changing whitespace) will produce a different MAC and every event will look forged. In Express, that means express.raw({ type: 'application/json' }) on this route.
The timestamp is inside the signed material on purpose: an attacker who captures a delivery cannot refresh t to get past your tolerance check without also forging the MAC.

Event payload

Every delivery has the same envelope. data is exactly the object GET /v1/orders/:id returns.

Envelope fields

Order fields

Item fields

A hold order is not a stuck order. The items are bought and paid for; Steam will not let them move until hold_until. If the account is set to auto-claim they release on their own, otherwise call POST /v1/orders/{id}/claim once hold_until has passed.

Delivery semantics

At-least-once, deduped by id. We may deliver the same event more than once — after a restart, or when your endpoint ACKs a request we had already timed out on. The id is stable per (order, state), so storing processed ids and ignoring repeats is the entire integration. It is also echoed in X-CSBoard-Event-Id, so you can dedupe before parsing the body. Ordering is not guaranteed. Retries mean an older state can arrive after a newer one. Do not treat the webhook as a state machine you advance — treat each event as a snapshot and compare data.updated_at against what you have stored, discarding anything older. Every event carries the complete order, so a single event is always enough to render the current truth. Retries. Any response outside 2xx, a redirect, a connection failure, or no answer within 10 seconds counts as a failure. Failed deliveries retry on a widening ladder — 10s, 30s, 1m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h — up to 12 attempts, spanning roughly 24 hours. An endpoint down for a working day still receives everything once it comes back. Auto-disable. After 50 consecutive failures the endpoint is switched off and we stop attempting. Nothing is deleted — re-enable with PATCH /v1/webhooks/{id} and queued events resume.

Respond fast

ACK within 10 seconds. Verify the signature, write the event to your own queue or table, return 200, and do the real work asynchronously. If you process inline — updating a user balance, calling Steam, sending a message — a slow dependency turns into a timeout on our side, a retry, and a duplicate you now have to handle under load.

Security

  • HTTPS only. The payload carries order and delivery data.
  • Endpoints must be publicly routable. URLs that resolve to private, loopback, link-local or CGNAT ranges are rejected at registration and re-checked before every send, because DNS can be re-pointed after the fact.
  • Redirects are not followed. A 3xx counts as a failure — register the final URL.
  • Rotating the secret: POST /v1/webhooks/{id}/rotate-secret. Queued deliveries are signed with whichever secret is current at send time, so accept both values for a minute or rotate during a quiet window.
  • The secret is a signing key, not a credential. It grants no access to your account. It still belongs in your secret store, not your repo.

Managing endpoints

GET /v1/webhooks/{id}/deliveries is the self-serve answer to “did you actually send it?” — including the failed attempts and the exact error text your endpoint returned. Check it before opening a ticket; it is usually faster than we are.

Errors


Still want to poll?

Nothing is removed. GET /v1/orders and GET /v1/orders/info carry the same fields, including hold_until and per-item tradable_at, and remain the right tool for reconciliation sweeps. The recommended shape is push for latency and a periodic pull for truth — a nightly GET /v1/orders over the last 24 hours catches anything a permanently broken endpoint would otherwise have lost.