Embedded UI (tokens + `/embed/v1`)

ProphetX provides drop-in UI components that handle trading and payment flows inside your frontend. They authenticate to ProphetX directly using a short-lived JWT that your backend mints for each user session, then call /embed/v1/* routes from the user's browser.

Your only responsibility on the backend is to mint those tokens. You don't call /embed/v1 yourself.

Auth on GET /tokens: user-scoped (sub + subsig) — see Authentication.

Which widgets does my integration need?

There are roughly four widgets available — Deposit, Withdraw, Onboarding, KYC. Which ones you embed depends on your fund type and how much of the user flow you want to own:

WidgetINDIVIDUAL ISVsSTANDING ISVs
DepositRequired. The private ISV API exposes no deposit endpoint — the only path money can enter a user's wallet is through this modal (calling /embed/v1/payment/*).Not used for end-user funding (the standing fund is funded out-of-band).
WithdrawRequired. Same reason — there is no POST /withdraw on the private API; the modal is the only path out.Not used for end-user payouts.
OnboardingOptional. You can build your own onboarding UI on top of POST /users and the terms endpoints, or embed this component to let ProphetX walk the user through it. Most teams own onboarding themselves so they can match their brand and lifecycle.
KYCOptional. You can poll GET /users/USERID/kyc-status and render your own KYC state, or embed this widget to let ProphetX show the status and any retry/support affordances.

For an INDIVIDUAL deployment, the practical minimum is: own onboarding + KYC display, embed Deposit + Withdraw (because there's no API alternative for money movement). Talk through the end-to-end user flow with your ProphetX contact before locking it in — there are a few sequencing edge cases (e.g. when to surface deposit prompts vs. KYC retries) that are easier to align on once rather than discovering after launch.


1. How the pieces fit together

Your frontend                    ProphetX-hosted embedded components
     │                                       │
     │  fetches a token                      │
     ├──► your backend                       │
     │       │                               │
     │       │ GET /private/v1/tokens        │
     │       └──────────────────────────────►│
     │                                       │
     │  token + permissions + gates          │
     │◄──────────────────────────────────────│
     │                                       │
     │  init component with token            │
     ├──► embedded UI                        │
     │                                       │
     │                                       │  /embed/v1/* calls
     │                                       ├──► ProphetX API
     │                                       │
     │                                       │  validates token, returns
     │                                       │◄──── responses

The token is the only ProphetX credential that ever touches the browser. It's short-lived and scoped to a single user session.


2. GET /private/v1/tokens

User-scoped. No body.

Response (TokenResponse):

{
  "token": "eyJhbGciOi...",
  "isvId":  "00000000-0000-0000-0000-000000000000",
  "userId": "00000000-0000-0000-0000-000000000000",
  "expiration": "2026-05-12T12:05:00Z",
  "permissions": {
    "trade":    { "granted": true,  "description": "Can submit orders" },
    "withdraw": { "granted": false, "description": "Can withdraw funds", "denyReason": "KYC pending" }
  },
  "gates": {
    "kyc":   { "completed": true,  "description": "KYC verification" },
    "terms": { "completed": false, "description": "Accept current terms" }
  }
}

What to do with each field:

FieldUse
tokenHand to the embedded UI component as its auth credential. Don't log it.
expirationRefresh before it expires; embedded components also surface refresh hooks.
permissionsMap of permission key → { granted, description, denyReason? }. Drive your UI: only render trading controls if permissions.trade.granted. Show denyReason to explain a "no".
gatesOnboarding gates the user must clear. Use these to drive an onboarding flow that walks them through any completed: false gate.

permissions and gates are maps with arbitrary keys — new ones can appear without an API version bump. Don't hard-code an exhaustive switch; iterate the keys you got.


3. What /embed/v1/* exposes

You don't call these directly, but here's what the embedded UI is doing under the hood so you know what's reachable from a logged-in user's browser session.

MethodPathPurpose
GET/embed/v1/payment/methodsList available payment methods (proxies to the funds system).
POST/embed/v1/payment/init-providerInitialize a payment provider session.
POST/embed/v1/payment/depositStart a deposit.
POST/embed/v1/payment/deposit-resultFinalize a deposit.
POST/embed/v1/payment/withdrawStart a withdrawal.
POST/embed/v1/payment/withdraw-resultFinalize a withdrawal.
GET/embed/v1/termsSame payload as /private/v1/terms.
GET/embed/v1/terms/USERIDWhether this user has accepted the current terms.
POST/embed/v1/terms/USERIDRecord acceptance.
GET/embed/v1/walletThe calling user's Wallet row. Always user-scoped — the token's sub decides whose wallet is returned, so the component cannot read another user's wallet or the ISV's standing fund. Same shape as /private/v1/wallets.
GET/embed/v1/token/validateValidate an embed token and return the same TokenResponse payload.

The terms routes are a straight passthrough to the private equivalents in User Onboarding (users, KYC, terms), so you can either handle T&Cs on your own backend or let the embedded UI handle them.

The payment routes are the only path for deposits and withdrawals — the private ISV API doesn't expose money-movement endpoints. Your frontend embeds the payment component, and the component calls /embed/v1/payment/* itself.


4. Token rotation

Tokens are short-lived (the expiration claim). Refresh them before they expire. A common pattern:

  1. On session start, fetch a token.
  2. Schedule a refresh just before expiration.
  3. When refreshing, fetch a new token from GET /private/v1/tokens and pass it to the embedded component.
  4. On the user logging out, drop the token. It can't be revoked from your side, but it will expire shortly.

Don't try to reuse a token across users or sessions — one token, one user, one short window.


5. Curl

BASE="https://isv-api.staging.prophetx.dev/private/v1"

# Mint a token for a user
curl "$BASE/tokens" -H "Authorization: Bearer $JWT_USER"

The response goes back to your frontend over your own authenticated session with the user — not directly to the browser as a redirect from this API.