Event Catalogue
Six event types are emitted in v1. Five are tied to a merchant order
(purchase.*), one is tied to a balance top-up (balance.deposited).
All payloads share the same envelope:
{
"event": "<event_type>",
"event_id": "<uuid>",
"occurred_at": "<ISO-8601 UTC, ms precision, Z suffix>",
"data": { /* event-specific */ }
}occurred_at is always UTC with .fffZ suffix — for example
2026-05-05T12:34:56.789Z. Use it for ordering when two events for the same purchase
arrive close together (see Ordering below).
Money fields (
price,amount,balance_after,refunded_amount) are JSON numbers with up to two decimals — never strings. Nullable fields (image_url,failure_message,txid,reason,target_steam_id) are omitted from the JSON when null, not serialized asnull.
purchase.created
Fires immediately after POST /api/v1/merchant/buy succeeds. Use this to mark
the order as accepted in your system; payment from the merchant balance has already
been deducted.
{
"event": "purchase.created",
"event_id": "11111111-1111-1111-1111-111111111111",
"occurred_at": "2026-05-05T12:34:56.789Z",
"data": {
"purchase_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"custom_id": "your-order-42",
"item": {
"market_hash_name": "AK-47 | Redline (Field-Tested)",
"image_url": "https://steamcommunity-a.akamaihd.net/economy/image/..."
},
"price": 12.34,
"target_steam_id": "76561198000000000"
}
}purchase.sent_to_steam
Fires when our worker has dispatched the trade offer on Steam. The buyer must accept it; this is not a terminal state.
{
"event": "purchase.sent_to_steam",
"data": {
"purchase_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"custom_id": "your-order-42",
"trade_offer_id": "5123456789",
"trade_offer_url": "https://steamcommunity.com/tradeoffer/5123456789/"
}
}purchase.completed
Terminal success. The buyer accepted the trade and Steam reported the item delivered.
{
"event": "purchase.completed",
"data": {
"purchase_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"custom_id": "your-order-42",
"completed_at": "2026-05-05T12:40:00.000Z"
}
}purchase.failed
Terminal failure before the trade reached Steam, or after Steam reported a non-recoverable
error. failure_code is a stable machine-readable identifier;
failure_message is human-readable and may change.
If the failure resulted in an automatic refund, refunded_amount is the amount returned
to your merchant balance (USD, JSON number, ≤ 2 decimals). Otherwise the field is omitted.
{
"event": "purchase.failed",
"data": {
"purchase_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"custom_id": "your-order-42",
"failure_code": "PROVIDER_OUT_OF_STOCK",
"failure_message": "Item is no longer available from the provider",
"refunded_amount": 12.34
}
}purchase.refunded
Fires when an order is refunded after a purchase.failed event — for example a
manual refund issued by support after we already reported the failure.
A failure that auto-refunds at the same time emits a single purchase.failed
with refunded_amount set; you will not also receive purchase.refunded in that case.
{
"event": "purchase.refunded",
"data": {
"purchase_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"custom_id": "your-order-42",
"refunded_amount": 12.34,
"balance_after": 987.66,
"reason": "Manual refund (support request)"
}
}purchase.reverted_by_buyer
Fires when a purchase was already completed (skin delivered) and the buyer then rolled
the accepted trade back during Steam’s Trade Protection hold (up to several days after delivery).
Per policy this is treated as buyer fault: no refund is issued — refunded_amount is 0.
The purchase moves to a terminal failed state.
{
"event": "purchase.reverted_by_buyer",
"data": {
"purchase_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"custom_id": "your-order-42",
"failure_code": "BUYER_STEAM_ROLLBACK",
"failure_message": "Buyer reverted accepted trade (Steam Trade Protection rollback). No refund.",
"refunded_amount": 0
}
}purchase.reverted_by_seller
Fires when a purchase was already completed and the seller (provider side) rolled the
accepted trade back during the protection hold. You are refunded in full — refunded_amount
is the purchase price, credited back to your merchant balance.
{
"event": "purchase.reverted_by_seller",
"data": {
"purchase_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"custom_id": "your-order-42",
"failure_code": "SELLER_STEAM_ROLLBACK",
"failure_message": "Seller reverted accepted trade (Steam Trade Protection rollback). Full refund.",
"refunded_amount": 12.34
}
}Reverts can arrive after
purchase.completed. A completed purchase is not final until Steam’s Trade Protection hold passes. Handle these two events idempotently on top of an already-completed order. When we cannot determine who caused the rollback, you receive a plainpurchase.failed(withrefunded_amount: null) and the case is reviewed manually — apurchase.refundedfollows if support issues a refund.
balance.deposited
Fires when a top-up to your merchant balance has been confirmed. Currently all deposits
flow through Heleket; provider_uuid is the Heleket payment UUID and is stable across
retries — use it for deduplication (the purchase.* events use (purchase_id, event),
balance events do not have a purchase). network is tron or bsc (matches the
USDT-TRC20 / USDT-BEP20 deposit address you funded). txid is the on-chain
transaction id when available; the field is omitted from the payload otherwise.
{
"event": "balance.deposited",
"data": {
"amount": 100.00,
"currency": "USD",
"balance_after": 1100.00,
"network": "tron",
"txid": "0xabc...",
"provider": "heleket",
"provider_uuid": "11111111-2222-3333-4444-555555555555"
}
}Ordering guarantees
Events for the same purchase follow the natural lifecycle:
purchase.created → purchase.sent_to_steam → purchase.completed
↘ purchase.failed [→ purchase.refunded]A revert can arrive after completed: Steam may roll a delivered trade back
during its protection window (up to ~9 days post-delivery). When that happens you
receive a terminal revert event after you already saw completed:
purchase.completed → purchase.reverted_by_buyer
↘ purchase.reverted_by_sellerSo completed is not guaranteed to be the last event for a purchase. Do not treat
it as final for fulfilment — always be ready to handle a later reverted_by_* for an
order you already marked done, and reconcile your books accordingly.
But:
- No global ordering between purchases. Two unrelated orders can deliver in any order.
- No strict ordering within a purchase under retry. If
sent_to_steamis being retried (slow merchant endpoint) andcompletedfires while it’s still queued, the receiver might see them out of order. Always treatdata.purchase_id+ the event type as authoritative; do not assume the previous event has already been processed. - Use
occurred_atas a tiebreaker when you need to reconstruct a sequence. The field is monotonic for a given purchase (set when the event is queued, not when it’s delivered), with millisecond precision. - A failed delivery is retried (see Retry policy);
the retried request keeps the same
event_idand body. OnlyX-TimestampandX-Signaturechange between attempts.
Versioning
Forward compatibility rules for v1:
- We may add new fields to existing payloads without notice. Treat unknown fields as optional.
- We may add new event types. Receivers MUST ignore unknown
X-Eventvalues (return 2xx, do not 400) — failing on an unknown event type would create false alarms in our retry pipeline. - We will never silently change the meaning of an existing field. Any breaking change
ships under a new event type or a new
eventvalue. - Schema changes are tracked in the Changelog.