Skip to main content

Trade Lifecycle

Every trade follows a state machine. Understanding the status transitions is critical for correctly handling callbacks and updating your users.

Trade Statuses

StatusDescription
initiatedTrade created. For deposits, the Steam offer has not been sent yet. For withdrawals, this is the approval gate — the merchant wallet has not been debited yet.
pendingWithdrawals only. The supplier is sourcing the item / creating the purchase.
activeSteam trade offer has been sent to the user — awaiting their acceptance.
holdUser accepted and the items are in Steam’s hold window (typically 7 days). Carries offerID and holdEndDate.
completedTrade finished successfully (hold expired with no reversal).
canceledTrade was canceled before completion — admin/user cancel, or a deposit offer that was auto-canceled after sitting unaccepted past its window.
declinedThe end user actively declined (or let expire) the Steam trade offer. Deposits: no items received, no credit. Withdrawals: delivery didn’t happen; your merchant wallet is refunded minus a 2% buyer-decline penalty (capped at $9).
failedTrade failed at some point in the process (offer expired, merchant rejected the initiated callback, supplier purchase failed, error).
revertedA previously completed trade was reversed by the upstream supplier or the end user (see revertedBy).
There is no partial status. pending and active are emitted on withdrawals as the supplier sources the item and the Steam offer is sent. Statuses are atomic per trade — for multi-item withdrawals, each item carries its own status field but the trade-level status is always one of the values above.

Deposit Status Flow

initiated

    ├──► canceled        (canceled before offer sent)


active ──────────► failed   (offer expired / account restricted)
    │       │
    │       ├──► declined  (user declined the offer)
    │       │
    │       └──► canceled  (auto-canceled / admin or user cancel)


  hold ───────────► failed


completed

    └──► reverted

What triggers each transition:

  • initiated → active: Steam trade offer sent to the user — sitting in their Steam inbox, awaiting acceptance
  • initiated → failed / canceled: Bot dispatch failed or the trade was canceled before the offer left
  • active → hold: User accepted, items received, Steam hold period started
  • active → failed: Trade offer expired or the user’s account has restrictions
  • active → declined: User actively declined the Steam trade offer
  • active → canceled: Offer auto-canceled after sitting unaccepted past its window, or an admin/user canceled it
  • hold → completed: 7-day hold ended, no reversal detected
  • hold → failed: Items reversed by Steam during the hold period
  • completed → reverted: Trade was reversed after completion (uncommon; check revertedBy)
active ≠ “user accepted”. active means we sent the trade offer and it’s sitting in the user’s Steam inbox. The user-accepted moment is active → hold.
Rust deposits skip the hold step. Rust items don’t have a 7-day Steam protection window, so a successful Rust deposit transitions active → completed directly.

Withdrawal Status Flow

initiated

    ├──► canceled        (merchant or admin cancellation)

    ├──► failed          (merchant rejected /initiated callback, or supplier failure)


pending ──────► failed   (supplier purchase failed)


active ───────► failed     (Steam offer expired / account restricted)
    │       └──► declined  (user declined the Steam offer)


  hold ───────► failed
    │       └──► reverted

completed

    └──► reverted

What triggers each transition:

  • initiated → pending: Merchant approved via the initiated callback; AssetPay starts sourcing the item from the supplier
  • initiated → failed: Merchant rejected via callback (4xx response or rejection body), the merchant has no callback URL configured (MERCHANT_NO_CALLBACK_URL), or the supplier purchase failed
  • initiated → canceled: Trade explicitly canceled before processing
  • pending → active: Item sourced, Steam trade offer sent to the user — awaiting acceptance
  • pending → failed: Supplier purchase could not be completed
  • active → hold: User accepted the Steam trade offer, Steam hold period started
  • active → failed: The Steam offer expired or the recipient’s account is restricted
  • active → declined: The end user actively declined the Steam offer (your merchant wallet is refunded)
  • hold → completed: 7-day hold ended, no reversal detected
  • hold → failed: Items reversed by Steam during the hold period
  • hold → reverted: Trade was reversed by the supplier or user during the hold period (check revertedBy)
  • completed → reverted: Trade was reversed after the hold completed
A withdrawal moves initiated → pending → active → hold → completed. Not every trade emits every intermediate status, so treat pending and active as progress signals and drive balance changes off initiated (deduct) and the terminal states.
Rust withdrawals normally skip the hold step. Rust items have no 7-day reversal-protection window, so a successful Rust withdrawal usually transitions active → completed directly, with no holdEndDate. The exception is a Steam security escrow (recipient has no mobile authenticator), which surfaces as hold. CS2 withdrawals always go through hold, which carries offerID + holdEndDate.

The 7-Day Hold

Steam enforces a 7-day reversal window on CS2 trades. During this window, items can be clawed back. This affects how you should handle balance credits. The holdEndDate field on the trade object tells you exactly when the hold expires. Without instant deposits: Don’t credit any balance until completed. Simple and safe. With instant deposits (recommended): When a deposit enters hold, the trade includes:
  • preCredit: amount to credit immediately (calculated from collateral)
  • pendingCredit: remaining amount to credit after the hold
Credit preCredit on hold, then credit pendingCredit on completed. If the trade gets reverted, reverse both. Rust deposits skip the hold entirely — credit the full amount on completed.

Terminal States

These statuses are final and won’t change:
  • completed: The trade is done. Balance has been settled. (Can still transition to reverted if Steam reverses the trade afterward.)
  • canceled: The trade was canceled before processing started. No balance settlement.
  • declined: The end user declined the trade offer. Deposits: no items received, no settlement. Withdrawals: delivery didn’t happen — your merchant wallet is auto-refunded minus a 2% buyer-decline penalty (capped at $9), so this refunds slightly less than failed.
  • failed: The trade didn’t go through. For withdrawals where you previously deducted the user’s balance, refund it.
  • reverted: The trade was reversed after being settled. Check revertedBy to understand who initiated it.

The revertedBy Field

When a trade reaches reverted, the revertedBy field tells you who caused it:
ValueMeaning
supplierThe marketplace supplier reversed the trade (withdrawals only)
userThe end user reversed the trade
For deposits, reversals typically happen when Steam reverses the trade post-completion — revertedBy is set to user in that case. For withdrawals, either party can reverse after acceptance.

Bot Info

During certain stages, the trade object includes botInfo with details about the Steam bot handling the trade:
{
  "botInfo": {
    "name": "AssetPay Bot #3",
    "avatar": "https://...",
    "steamId": "76561198...",
    "joined": "2024-01-15T00:00:00.000Z"
  }
}
You can show this in your UI so users know which bot to expect the trade offer from. You probably don’t want to expose all internal statuses to your users. Here’s a suggested mapping:
Internal StatusUser-facing LabelDescription to Show
initiatedProcessingYour trade is being set up
pendingProcessingSourcing your item
activeIn ProgressTrade offer sent, waiting for you to accept it
holdHeld (7 days)Item held for Steam’s protection period
completedCompletedTrade finished
canceledCancelledTrade was cancelled before processing
declinedDeclinedYou declined the trade offer
failedFailedTrade didn’t go through
revertedReversedTrade was reversed