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.
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
GET /private/v1/tokensUser-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:
| Field | Use |
|---|---|
token | Hand to the embedded UI component as its auth credential. Don't log it. |
expiration | Refresh before it expires; embedded components also surface refresh hooks. |
permissions | Map of permission key → { granted, description, denyReason? }. Drive your UI: only render trading controls if permissions.trade.granted. Show denyReason to explain a "no". |
gates | Onboarding 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
/embed/v1/* exposesYou 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.
| Method | Path | Purpose |
|---|---|---|
GET | /embed/v1/payment/methods | List available payment methods (proxies to the funds system). |
POST | /embed/v1/payment/init-provider | Initialize a payment provider session. |
POST | /embed/v1/payment/deposit | Start a deposit. |
POST | /embed/v1/payment/deposit-result | Finalize a deposit. |
POST | /embed/v1/payment/withdraw | Start a withdrawal. |
POST | /embed/v1/payment/withdraw-result | Finalize a withdrawal. |
GET | /embed/v1/terms | Same payload as /private/v1/terms. |
GET | /embed/v1/terms/USERID | Whether this user has accepted the current terms. |
POST | /embed/v1/terms/USERID | Record acceptance. |
GET | /embed/v1/wallet | The 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 pot. Same shape as /private/v1/wallets. |
GET | /embed/v1/token/validate | Validate 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:
- On session start, fetch a token.
- Schedule a refresh just before
expiration. - When refreshing, fetch a new token from
GET /private/v1/tokensand pass it to the embedded component. - 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-staging-api.betprophet.co/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.
Updated about 5 hours ago
