# AssetPay TypeScript SDK

`@assetpay/assetpay-sdk` is the official TypeScript SDK for the AssetPay API. It
gives a Node.js backend typed methods for every public endpoint: CS2 and Rust
skin deposits and withdrawals, crypto cashouts, wallet and ledger reads, and the
webhook verification your settlement depends on. It is open source under the MIT
license, developed on [GitHub](https://github.com/assetpaygg/assetpay-sdk) and
published on [npm](https://www.npmjs.com/package/@assetpay/assetpay-sdk).

The REST API is still the contract, and anything that can send an HTTPS request
can integrate without the SDK. The SDK exists for the parts that are easy to get
wrong by hand: retrying a request that moves money, keeping client tokens fresh,
and verifying a signed webhook against the raw body.

## Install

```bash
npm install @assetpay/assetpay-sdk
```

It needs Node.js 22.19 or newer and has one runtime dependency. The package is
ESM, and CommonJS projects can `require()` it on the same Node versions. pnpm,
yarn and bun install it the same way.

## Quickstart: a CS2 skin deposit

Create one client with your API key and secret, then scope it to the Steam user
who is depositing. The SDK mints that user's client token locally, so there is
no extra request per user.

```ts

const ap = new AssetPay({
  apiKey: process.env.ASSETPAY_API_KEY!,
  apiSecret: process.env.ASSETPAY_API_SECRET,
  merchantId: process.env.ASSETPAY_MERCHANT_ID,
});

const user = ap.asClient({ steamId: account.steamId, tradeUrl: account.tradeUrl });

const { inventory } = await user.inventory.get({ game: 730 });
const trade = await user.trades.deposit({
  items: [{ itemId: inventory[0].id, price: inventory[0].offer!.price }],
  externalId: `deposit-${order.id}`,
});
```

Rust works the same way with `game: 252490`. Point `baseUrl` at
`https://api-staging.assetpay.gg` to run the same code against staging.

## What the SDK handles for you

- **Both scopes from one client.** Merchant calls use your API key.
  `ap.asClient()` returns the same modules scoped to one Steam user, for the
  whitelabel checkout flow where your users deposit and cash out inside your UI.
- **Client tokens.** Minted from your API secret, renewed before they expire,
  and reissued once if the API refuses one. Identities are validated before a
  token is made, and `ap.mintClientToken()` hands one to your frontend.
- **Retries that never double-spend.** Reads retry freely. Deposits, sells,
  buys and withdrawals retry only when the failure proves nothing was sent. When
  the outcome is uncertain the error says so (`ambiguous: true`), and the
  `reconcile` option settles it through your `externalId`.
- **Webhook verification.** `verifyWebhook` checks the HMAC-SHA256 signature
  over the raw body, rejects replays, accepts both secrets during a rotation and
  tells trade events apart from withdrawal approvals. It ships as its own entry
  point with no HTTP dependency, so a webhook-only service stays small.
- **Typed errors.** Every failure is an `AssetPayError` with a stable `key`, the
  HTTP status, field-level validation messages and the `requestId` to quote to
  support.
- **Pagination.** `for await` iterators over trades, crypto deposits and ledger
  transactions, each following that endpoint's own cursor style.
- **Socket.IO events.** `@assetpay/assetpay-sdk/realtime` streams trade, deposit
  and market updates, with reconnects handled. `socket.io-client` is an optional
  peer, installed only if you use it.

## Verify webhooks and approve withdrawals

Every state change is posted to your callback URL and signed with your API
secret. Withdrawals also ask your backend for approval before anything ships,
and a plain `200` counts as a yes, so approvals must be answered explicitly. A
complete Express handler:

```ts

app.post('/webhooks/assetpay', express.raw({ type: 'application/json' }), async (req, res) => {
  if (isCallbackTest(req.body, req.headers)) return res.sendStatus(200);

  let event;
  try {
    event = verifyWebhook(req.body, req.headers, { secret: process.env.ASSETPAY_API_SECRET! });
  } catch {
    return res.sendStatus(401);
  }

  if (event.type === 'withdraw.approval' || event.type === 'crypto_withdraw.approval') {
    const verdict = (await canPay(event)) ? approve() : reject('insufficient balance');
    return res.status(verdict.status).json(verdict.body);
  }

  if (!(await alreadyHandled(event.dedupeKey))) await apply(event);
  res.sendStatus(200);
});
```

The [README](https://github.com/assetpaygg/assetpay-sdk#webhooks) has the same
handler for Fastify and the Next.js App Router, the deduplication rules and the
approval timeouts.

## SDK or plain REST?

| | TypeScript SDK | REST API |
| --- | --- | --- |
| Languages | TypeScript and JavaScript on Node.js | Any language with HTTPS |
| Client tokens | Minted and refreshed for you | You call `/auth/authenticate-client` |
| Retries | Decided per endpoint, never duplicates a trade | Yours to design |
| Webhook verification | One call, rotation included | HMAC code you write and test |
| Types | Every request and response | From the OpenAPI description |

Both hit the same endpoints with the same credentials, so you can start with the
SDK and drop to `ap.raw` for anything it does not wrap yet. For another language,
generate a client from the
[OpenAPI 3.1 description](https://api.assetpay.gg/docs/public/openapi.json) or
follow the [API walkthrough](/blog/integrate-skin-deposits).

## Frequently asked questions

### Is there an official AssetPay SDK?

Yes. `@assetpay/assetpay-sdk` is the official TypeScript SDK, maintained by the
AssetPay team, open source under the MIT license on
[GitHub](https://github.com/assetpaygg/assetpay-sdk) and published on
[npm](https://www.npmjs.com/package/@assetpay/assetpay-sdk).

### Which languages does the SDK support?

TypeScript and JavaScript on Node.js 22.19 or newer. Every other language
integrates against the REST API directly; the [docs](/docs) cover every
endpoint, and the OpenAPI description generates a typed client for most stacks.

### Can I use the SDK in the browser?

No, by design. It holds your API key and secret, which must never reach a
browser. To let your frontend call the end-user endpoints, mint a client token
on your server with `ap.mintClientToken()` and pass that token to the page.

### Does it work with Next.js, Express, Fastify and NestJS?

Yes. It runs in any Node.js server. The only thing webhook verification needs
from your framework is the raw request body, and the README has the recipe for
Express, Fastify and the Next.js App Router.

### Does the SDK cost anything?

No. It is free and MIT licensed. AssetPay charges one fee per cleared trade and
nothing else, as described on the [pricing page](/pricing).

### Where do I report a bug or ask for a feature?

Open an issue on [GitHub](https://github.com/assetpaygg/assetpay-sdk/issues), or
reach the team on [Discord](/contact).

---

Ready to build? Install `@assetpay/assetpay-sdk`, [create a merchant account](/register)
for your API key, and read the [CS2 skin payment gateway](/cs2-skin-payment-gateway)
page for how deposits settle.
