WebhooksEvent Catalogue

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 as null.

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 issuedrefunded_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 fullrefunded_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 plain purchase.failed (with refunded_amount: null) and the case is reviewed manually — a purchase.refunded follows 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_seller

So 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_steam is being retried (slow merchant endpoint) and completed fires while it’s still queued, the receiver might see them out of order. Always treat data.purchase_id + the event type as authoritative; do not assume the previous event has already been processed.
  • Use occurred_at as 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_id and body. Only X-Timestamp and X-Signature change 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-Event values (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 event value.
  • Schema changes are tracked in the Changelog.