Webhooks
Rheo sends a signed POST to your webhook endpoints whenever something happens to an
item you pushed — a Tradera auction goes live, an item sells, image processing finishes.
Multiple endpoints
An account can register several webhook endpoints, each with its own:
- URL — where the
POSTis delivered. - Signing secret — its own
whsec_...; each endpoint signs with its own secret. - Event-type filter (
eventTypes) — an allow-list. Empty = all events. - Scope —
self(events from your own account) ormembers(events from accounts you manage as a reseller — see Reseller model).
This lets you, for example, send item.sold to your inventory service and
listing.failed to an alerting service on separate URLs with separate secrets.
Manage endpoints in the partner dashboard under Integration → Webhooks, or via the API below.
Managing endpoints
All endpoint management is done on your account (dashboard session / JWT), not with an integration API key.
| Method | Path | Description |
|---|---|---|
GET | /integration/webhooks | List your endpoints (secrets masked). |
POST | /integration/webhooks | Create an endpoint. Returns the full secret once. |
PATCH | /integration/webhooks/{id} | Update url / eventTypes / scope / active / description. |
DELETE | /integration/webhooks/{id} | Remove an endpoint. |
POST | /integration/webhooks/{id}/rotate-secret | Issue a new secret. Returns it once. |
{ "url": "https://erp.example.com/webhooks/rheo", "eventTypes": ["item.sold", "listing.failed"], "scope": "self", "description": "Inventory sync"}{ "id": "…", "url": "https://erp.example.com/webhooks/rheo", "eventTypes": ["item.sold", "listing.failed"], "scope": "self", "active": true, "failCount": 0, "secret": "whsec_9f2c…", "secretHint": "whsec_9f2…", "createdAt": "2026-05-28T10:00:00Z", "updatedAt": "2026-05-28T10:00:00Z"}Store the secret securely — it is returned only on create and rotate-secret; every
other response shows the masked secretHint. scope: "members" requires a
reseller_agreement deal.
Payload envelope
Every event has the same envelope. data.externalId is your own item id and is present
on every event, so existing integrations keep working as new fields are added.
{ "eventId": "evt_01j2k3m4n5p6q7r8s9t0", "eventType": "item.sold", "timestamp": "2026-05-27T14:32:00Z", "data": { "externalId": "ERP_PART_12345", "rheoItemId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "platform": "tradera", "salePrice": 1100, "currency": "SEK", "traderaAdId": "398271634", "account": { "rheoUserId": "9b1f…", "memberExternalId": "10" } }}The account object
Added to every payload’s data:
| Field | Description |
|---|---|
rheoUserId | The owning Rheo account (UUID). |
memberExternalId | Present only on reseller (scope: members) deliveries — the reseller’s reference for the member. Use it to route the event to the right sub-account (e.g. decrement the right yard’s stock). |
Events
All events share the envelope above. The fields listed are what each event adds to data
on top of the shared externalId / rheoItemId / platform / salePrice / currency / account.
| Event | When | Extra data fields |
|---|---|---|
item.created | A new item or container is synced via the API. | type (item | container) |
item.images_ready | Background image processing finished. | imagesProcessed |
listing.created | A Tradera auction went live. | traderaAdId, traderaAdUrl |
listing.ended | A Tradera auction closed with no winning bid. | traderaAdId |
listing.failed | Rheo could not publish the Tradera auction. | error |
item.sold | The item sold (Tradera hammer price, or a direct Rheo sale). | traderaAdId |
platform is one of tradera, rheo, rheo_stripe, rheo_swish. salePrice is 0
for every non-sale event.
Signature verification
Every delivery includes an X-Rheo-Signature header:
X-Rheo-Signature: sha256=a4b2c9d1e3f5...This is HMAC-SHA256(rawBody, secret) where secret is the signing secret of the
endpoint that received the event. With multiple endpoints, verify with that endpoint’s
own secret — not a single account-wide secret.
Reject any webhook that fails verification. Always hash the raw request body bytes; re-serializing parsed JSON changes the bytes and breaks the check.
The TypeScript and C# SDKs verify for you, and accept multiple secrets to make rotation seamless.
Delivery & retries
- Up to 5 attempts with exponential backoff (1 s, 2 s, 4 s, 8 s, 16 s).
- A delivery fails if your endpoint returns a non-2xx status or does not respond within 10 seconds.
- Each endpoint tracks consecutive failures; an endpoint is auto-disabled after
15 consecutive failures. Re-enable it (set
active: true) once your receiver is healthy — that also resets the failure count. - Deliveries are logged per endpoint (with
endpointId) in the webhook delivery log.
Best practices
Respond fast, process async. Return 200 OK immediately and handle the event in a
background job. Handlers slower than 10 s are retried.
Be idempotent. Delivery is at-least-once — you may receive an event more than once.
Deduplicate on eventId.
Verify before anything else. Check X-Rheo-Signature against the receiving
endpoint’s secret using the raw body, before any business logic.
Route by account.memberExternalId on members-scope endpoints so one reseller
endpoint can fan events out to the right member.
Testing locally
Expose a local server with a tunnel (e.g. ngrok or Cloudflare Tunnel), then register the HTTPS URL as a webhook endpoint.
ngrok http 3000# Register the HTTPS URL in Integration → Webhooks, then trigger an event:# PUT /integration/v1/items/TEST_001 (autoPublishTradera: false) then PATCH /status -> sold