> ## Documentation Index
> Fetch the complete documentation index at: https://docs.skinloop.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Player balance deposits

> Accept variable-value Rust and CS2 deposits into your own player balance without changing product-order checkout.

Let players choose skins and deposit their accepted value into your own balance
system. Your server creates the session and credits the player after verified
completion. The hosted checkout uses the normal Skinloop design and shows a
**Balance** total, without order overpayment or a standing limits block.

This is separate from store-order checkout. It does not provide a player wallet,
betting engine, withdrawals or instant credit against protected CS2 trades.
Existing store integrations and `payment.*` webhooks stay unchanged.

## Required setup

1. Use an active Skinloop merchant account and a server-side API key with `deposits:create` and `deposits:read`. Enable Rust, CS2 or both on that key.
2. In **Webhooks**, create an HTTPS endpoint and select **Also receive player deposit events**. Completion and reversal subscriptions are required before creating sessions. Keep the signing secret on your server.
3. Add your return domain to the allowed checkout origins under **API & Webhooks**. Use your own authenticated player ID, not a player ID supplied unchecked by the browser.

Never place a merchant API key, signing secret, player email or private account data in the hosted URL.

## Request body

`POST /v1/merchant-api/deposit-sessions`

| Field | Use |
| - | - |
| `merchantReference` | Unique reference for this deposit request |
| `playerReference` | Player ID verified by your authenticated server |
| `allowedGames` | Rust, CS2 or both, within the key's permissions |
| `limits` | Minimum and maximum in integer USD cents; enforced even though no limits block is shown in checkout |
| `expiresInSeconds` | Session lifetime, from 300 to 86400 seconds; default 1800 |
| `successUrl`, `cancelUrl` | Return destinations on approved origins |

## Example request

```bash theme={null}
curl --request POST "$SKINLOOP_API/v1/merchant-api/deposit-sessions" \
  --header "Authorization: Bearer $SKINLOOP_API_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: player-deposit-request-unique-123" \
  --data '{
    "merchantReference": "deposit-request-unique-123",
    "playerReference": "player_123",
    "allowedGames": ["rust", "cs2"],
    "limits": {"min": 500, "max": 50000, "currency": "USD"},
    "expiresInSeconds": 1800,
    "successUrl": "https://your-store.example/wallet",
    "cancelUrl": "https://your-store.example/wallet"
  }'
```

All amounts are **integer USD cents**. This example allows $5 to $500. Provider eligibility and availability still apply; it does not promise that every item or amount is accepted.

Store the returned `id`, `creditReference`, `merchantReference` and `playerReference` in your database before opening `hostedUrl`. Retry the same request with the same idempotency key if the network response is lost. A changed request with that key, or a reused merchant reference for another session, is rejected.

Session expiry is 30 minutes by default, configurable from 5 minutes to 24 hours. It limits starting a deposit, not settlement of a trade already opened.

## Valuation and checkout

The player chooses skins first. Skinloop issues a short-lived server valuation, valid for at most two minutes and never past the session expiry. Confirmation checks the exact items, game, current prices and inclusive limits again.

Expired valuations and changed prices require refreshing the inventory and confirming the current selection. A browser cannot set the accepted amount, switch the receiving merchant, change the player reference or attach a second trade to the same session.

Once a trade has been reserved, an idempotent retry returns that original trade and accepted value. It does not reprice an existing deposit or create a replacement trade.

## States and spendable credit

| State | Meaning | Credit the player? |
| - | - | - |
| `created` | Waiting for an eligible selection | No |
| `expired` | No trade was opened before session expiry | No |
| `pending` | Trade requested or awaiting a verified outcome; uncertainty is not failure | No |
| `hold` | CS2 trade protection | No |
| `completed` | Provider-confirmed completion with the corresponding Skinloop accounting entry | Yes, once |
| `failed` | Definitively rejected, declined, canceled, expired or failed trade | No |
| `reverted` | Provider-confirmed reversal | Do not credit; reconcile any earlier credit |

Rust is not credited merely because a trade was created. CS2 remains pending during protection. A countdown ending, a success redirect or a screenshot is never payment confirmation. Instant spendable credit against a CS2 hold is not part of this product.

## Signed notifications

Player deposits emit only these dedicated events:

* `deposit.pending`, including a transition into `hold`.
* `deposit.completed`.
* `deposit.failed`, with a reason such as `canceled` or `declined`.
* `deposit.reverted`.

They use the existing [webhook signing and retry mechanism](https://docs.skinloop.io/webhooks/overview.md). Verify the signature against the **raw request body** using your endpoint secret. Deliveries retry when your receiver is unavailable, up to the configured retry limit; exhausted deliveries remain available for investigation and replay. Treat delivery as **at least once**, not exactly once and not necessarily ordered.

Example event:

```json theme={null}
{
  "version": "1",
  "id": "unique-event-uuid",
  "type": "deposit.completed",
  "createdAt": "2026-10-19T10:00:00.000Z",
  "data": {
    "depositId": "ds_0123456789abcdefghijklmnopqrstuv",
    "creditReference": "ds_0123456789abcdefghijklmnopqrstuv",
    "merchantReference": "deposit-request-unique-123",
    "playerReference": "player_123",
    "sequence": 2,
    "game": "rust",
    "status": "completed",
    "amount": {"value": 1250, "currency": "USD"},
    "canCreditPlayer": true
  }
}
```

The event ID identifies a notification. **`creditReference` identifies the one player deposit**, including retries and any later reversal. The actual accepted value is the amount to reconcile, not the session minimum or maximum.

## Status lookup

```bash theme={null}
curl "$SKINLOOP_API/v1/merchant-api/deposit-sessions/$DEPOSIT_ID" \
  --header "Authorization: Bearer $SKINLOOP_API_KEY"
```

## Credit the player once

Your receiver must:

1. Verify the webhook signature and identify the saved deposit belonging to your merchant.
2. Read the authenticated session status for recovery or out-of-order delivery. On lookup failure, retry later; do not assume success.
3. In one database transaction, lock your deposit record and verify the stored player reference, currency and accepted amount.
4. Credit only for current `completed` status and `canCreditPlayer: true`. Enforce a unique constraint on `creditReference` and insert the credit and balance change atomically.
5. Track event IDs and sequence numbers so duplicate or older notifications cannot restore an outdated state. A reversal needs its own unique compensating entry, not deletion of the original credit. If the deposit was never credited, a reversal must not debit the player.
6. Acknowledge only after durable processing, or after durably queuing the event for your own worker.

Do not use the product-order receiver to credit players or fulfill a store order from a `deposit.*` event. Skinloop prevents duplicate trade creation and emits durable, unique events. Your server must enforce the single credit in the player ledger it controls.

The public checkout projection omits player and merchant references. Keep the authenticated recovery response on your server.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.