Skip to main content
SideShift Connect puts a wallet inside your own product. You create a Connect account for each of your users, move money in and out of it over the API, and drop in a pre-built payout or pay-in widget so the user never leaves your page. Everything runs through one REST API at https://app.sideshift.app/api/embed, authenticated with a single server-side key.

Before you start

Connect is off by default. Until SideShift enables it for your company, every call returns 403 EMBED_NOT_ENABLED and asks you to contact support, so check this first rather than last. You also need a payment account on your company before you can generate a key at all. Until that exists, the settings page shows a “Payment account required” prompt with a setup button where the key controls would be.

API keys

Keys live in Settings → Embed Widgets, under API Keys. There are two independent slots, Live and Sandbox, each with its own Generate button. Send the key in the x-api-key header on every request:
SideShift stores only a SHA-256 hash of the key plus a short preview for display. That is why a key is shown exactly once when you generate it and cannot be recovered afterwards - nobody, including SideShift, can read it back. Generating again replaces the key in that slot.
The key is a server-side credential with full access to your accounts and transfers. It must never reach the browser. Widgets authenticate with short-lived tokens instead, precisely so the key can stay on your backend.

What the prefix actually changes

The prefix is the whole switch. The base URL, the paths, the request bodies and the response shapes are identical in both modes, and the two keys are stored against separate fields on your company, so a sandbox key can never authenticate as a live one. What does change behind it:
  • Balances are written to a separate sandbox ledger and a separate wallet, so test money and real money never mix.
  • Payment accounts are simulated, and the paymentAccountId you get back is prefixed sim_biz_ rather than being a real provider account.
  • A newly created sandbox account is seeded once with $10,000 of test funds, so you can transfer straight away without funding anything.
  • Withdrawal does not work in sandbox at all. A sandbox account has no real payment account behind it, so the withdrawable balance reads as zero and a withdrawal attempt fails with 404 NO_WHOP_ACCOUNT. Test the withdrawal flow against a live key with a small amount. See Guides for what sandbox does and does not cover.
  • Rate limit counters are tracked separately for the two modes, so load-testing in sandbox does not eat your live budget.
Start on sk_test_ and switch the environment variable when you go live.

Your first integration

1

Create an account for your user

Only email is required. Send externalId anyway: it is checked before the email lookup, so the same externalId keeps returning the same account even if the user later changes their email address. Without it, one person with two addresses becomes two separately payable Connect accounts.You get 201 on creation and 200 when the account already existed, with the same body either way:
Every Connect response is wrapped in { "success": ..., "data": ... }, so read data.sideshiftAccountId rather than the top level. Store that id - it is the handle for everything else.
2

Allow the domain you will embed on

Back in Settings → Embed Widgets, add the domains the widget is allowed to load on. A pattern matches exactly, or as *.example.com for the base domain and any subdomain.This is not optional bookkeeping. Every token is bound to one domain when it is minted, and the endpoint picks that domain from targetDomain in the body if you send one, otherwise the request’s Origin header, otherwise the first entry on your allowlist. A server-side curl sends no Origin, so with an empty allowlist there is nothing left to bind to and the next step fails with 400 DOMAIN_NOT_ALLOWED.
3

Mint a widget token

sideshiftAccountId and widgetType are both required. widgetType is payout, payin, or both.
expiresInSeconds defaults to 3600 and accepts 60 to 86400. A value outside that range is rejected with a 400 rather than clamped to the nearest bound, so range-check it before sending instead of assuming a too-large number will be trimmed.
The account has to belong to your integration. An id you did not create or link comes back as 404 “Account not found”, not 403. That is deliberate: a 403 would confirm the account exists, which would turn this endpoint into a way to probe for accounts on other platforms.
Token generation is rate limited per company and per mode, defaulting to 100 per hour. Mint one token per widget session rather than one per page render.
4

Embed the widget

Use widgetUrls from the response directly as the iframe src.
The allow attribute matters, because identity verification uses the camera and card flows may need payment permissions.The token sits in the URL path, so anything that logs full URLs logs a live credential. That is the other reason to keep lifetimes short and mint the token on your backend at the moment you render the page.

Where to go next

Embedding and customization

Sizing, overlays, the full set of theme and label parameters, and the events the widget posts back to your page.

Playground

Try the widget parameters interactively and copy out the generated snippet.