# Build a web app

> Ship Senddy in a browser app with social login, session tokens and gasless deposits. The production checklist.

Source: https://www.senddy.com/docs/guides/web-app
Last modified: 2026-10-07T18:10:55.000Z

---

This is the shape of a production integration, taken from apps running on Senddy today, including HOODS on Robinhood Chain. Read it once before you build, and again before you launch.

## The shape

```
Embedded wallet ──signs──▶ private keys (derived on the device, never sent anywhere)
        │
        ├──signs──▶ deposit authorization ──▶ Senddy relay ──▶ pool
        │
        └──user's login token──▶ your /api/session-token ──▶ Senddy /session ──▶ sst_ token
                                                                     │
                              @senddy/client ◀───────────────────────┘
```

Three facts shape everything else:

1. **Users never need gas.** Deposits are a signature that Senddy relays. Sends and swaps are proofs that Senddy relays.
2. **The browser holds no API key.** It holds a short-lived session token your server mints per user.
3. **Private keys come from a wallet signature and the pool address.** They're derived on the device and never leave it.

## 1. Packages

```bash
npm install @senddy/client @senddy/core viem
```

Plus your login provider. Embedded-wallet providers such as Privy work well, because every user gets a wallet without a browser extension.

## 2. Login and wallet

Any provider that gives you a viem `WalletClient` for the user works. Two settings matter:

- **Prefer embedded wallets.** Key derivation signs typed data at the start of each session. With an external wallet that's a popup every login.
- **Allow your chain.** Robinhood Chain (4663) must be enabled in your provider's dashboard.

## 3. Session tokens

Add one server route that verifies the user's login and mints a Senddy session token. [Authentication](/docs/developers/authentication) has the code. Pass the provider to the client as `sessionToken`.

## 4. The client

```typescript
const senddy = createSenddyClient({
  publicClient,
  walletClient,          // the user's embedded wallet
  chainId,
  pool,                  // must be exactly right: see below
  apiUrl,
  subgraphUrl,
  assets,                // per-asset EIP-712 domains, for chains with more than one asset
  defaultAsset,
  sessionToken,          // your provider from step 3
});

await senddy.init();
```

**The pool address is the most important line in the integration.** Keys are derived from it. A wrong value doesn't throw: the user derives a different identity and sees an empty balance. Take it from [Choose a chain](/docs/developers/chains) or from `@senddy/core`, never by hand.

## 5. Deposits

You'll usually want both flows from [Move money in and out](/docs/guides/deposits):

- **Already in the wallet:** `senddy.deposit(amount, { token })`. One signature, no gas.
- **"Send funds to this address":** a one-time address for new users. Your server needs a route that receives the deposit request, checks the user's signature against the token's EIP-712 domain, and relays it. Keep that check: it's what stops your route being used as an open relay.

If you adapt an existing deposit route to another chain, make sure the EIP-712 domain uses that chain's ID. A hardcoded chain ID rejects every valid deposit.

## 6. Content Security Policy

`connect-src` needs your instance's origins. For Robinhood Chain:

```
https://api-rh.senddy.com  wss://api-rh.senddy.com
https://api.goldsky.com
```

plus your RPC provider and your login provider.

## Production checklist

| Check | Why |
|---|---|
| Show proof progress | Proving takes 10 to 30 seconds in a browser. A real progress bar keeps users from leaving |
| Show withdrawal fees before the first deposit | `getExitFeeBps(token)`. Users should never discover a fee on the way out |
| Show amounts in the asset first | Show the dollar value beside it, never a rate alone |
| Seed a new pool with more than one token | Very small first deposits can fall below the pool's minimum share count |
| Use one login provider app per product | Keeps your users, settings and allowed origins separate from anyone else's |
| Give the site its own API key | So your traffic and rate limits are yours alone |
