Skip to content

Webhooks

Skinslink sends webhook notifications to your server when trade statuses change. This is the recommended way to track deposit progress.

Setup

Configure your webhook URL in your merchant settings. Skinslink will send POST requests to this URL whenever a trade status changes.

You can also override the webhook URL per deposit by passing result_url when creating a deposit — that URL will take priority over the merchant-level setting.

Payload

json
{
  "sign": "b64EncodedSHA256Signature==",
  "status": "completed",
  "trade_id": 178,
  "trade_date": "2026-02-16T12:00:00Z",
  "merchant_tx_id": "order-12345",
  "steam_id": "76561198338314767",
  "offer_id": "6912345678",
  "amount": 36.25,
  "amount_currency": "usd",
  "custom_currency": "EUR",
  "custom_currency_multiplier": 1.0,
  "custom_currency_rate": 0.92,
  "custom_currency_sum": 33.35
}

Fields

FieldTypeDescription
signstringRequest signature (see Verifying Webhooks below)
statusstringTrade status: hold, canceled, failed, completed, reverted
trade_idintegerSkinslink internal trade ID
trade_datestringTrade creation timestamp (ISO 8601)
merchant_tx_idstringYour order ID (the value you passed when creating the deposit)
steam_idstringUser's Steam ID (64-bit, as string)
offer_idstringSteam trade offer ID (empty string if not yet available)
amountnumberTrade amount in USD
amount_currencystringCurrency code (currently always usd)
custom_currencystring | nullCustom currency code (only for hold/completed)
custom_currency_multipliernumber | nullCurrency multiplier
custom_currency_ratenumber | nullCurrency exchange rate
custom_currency_sumnumber | nullComputed amount in custom currency (amount × rate × multiplier)

Trade Status Flow

Webhooks are sent for the highlighted statuses only:

new ──→ pending ──→ active ──→ completed ✉
 │         │          │
 │         │          ├──→ hold ✉ ──→ completed ✉
 │         │          │              │
 │         │          │              └──→ reverted ✉
 │         │          │
 │         │          ├──→ failed ✉
 │         │          │
 │         │          └──→ canceled ✉
 │         │
 │         ├──→ failed ✉
 │         │
 │         └──→ canceled ✉

 └──→ canceled ✉
  • hold — trade is on Steam trade hold (7-day hold)
  • completed — items received, deposit credited
  • failed — trade failed (user declined, items unavailable, etc.)
  • canceled — trade was canceled
  • reverted — a previously held trade has been reversed

TIP

Statuses new, pending, and active do not trigger webhook notifications. Use polling if you need to track early status transitions.

Verifying Webhooks

Why verify?

Your webhook endpoint is a public URL — anyone who discovers it could send fake requests pretending to be Skinslink (e.g. "deposit completed, credit the user $50"). The sign field lets you cryptographically prove the webhook actually came from us and hasn't been tampered with.

Always verify the signature before processing a webhook. Without verification, an attacker could forge deposit completions and steal funds.

How it works

The signature is a Base64-encoded SHA-256 hash of your merchant_tx_id concatenated with your merchant secret:

sign = base64( sha256( merchant_tx_id + secret ) )

Your secret is known only to you and Skinslink — so only a legitimate webhook can produce the correct signature for a given merchant_tx_id.

Example (Python)

python
import hashlib, base64

expected = base64.b64encode(
    hashlib.sha256((payload["merchant_tx_id"] + secret).encode()).digest()
).decode()

if expected != payload["sign"]:
    return 403  # reject — not from Skinslink

Example (Node.js)

javascript
const crypto = require('crypto');

const expected = crypto
  .createHash('sha256')
  .update(payload.merchant_tx_id + secret)
  .digest('base64');

if (expected !== payload.sign) {
  return res.status(403).send('Invalid signature');
}

Retry Behavior

If your webhook endpoint returns a non-2xx status code, Skinslink will retry delivery with exponential backoff. We recommend:

  • Respond with 200 OK as quickly as possible
  • Process the webhook payload asynchronously
  • Implement idempotency using merchant_tx_id + status as the deduplication key

Polling as Fallback

If webhooks are not suitable for your setup, you can poll the deposit status endpoint:

bash
curl "https://api.skinslink.com/api/v1/merchant/deposit/status?merchant_tx_id=order-12345" \
  -H "X-Api-Key: your-api-key"

We recommend polling no more than once per second per deposit.