Deposits and Withdrawals

Integrate the ProphetX Wallet so users can deposit and withdraw funds.

📘

Upfront verification and terms (optional)

This guide walks through a just-in-time pattern — users are prompted to accept ProphetX's terms and complete identity verification the first time they open a deposit or withdrawal. If you prefer, you can run these steps upfront during your registration flow so the wallet flows open straight into the modal.

  • useOneClickSignup — for a brand-new user: creates the ProphetX user from a phone number. There is no bundled first-deposit option — trigger a deposit separately afterward if you want that.
  • useKyc/useIdUpload — for an existing user whose automated KYC came back FAILURE.
  • Terms acceptance — there's no SDK hook for this; you host /tandc yourself in a raw iframe.

What you'll do

  1. Set up the SDK — install and configure the ProphetX SDK for your stack.
  2. Open a deposit — let them fund their account via your frontend.
  3. View transaction history — show users their recent transactions and statuses.
  4. Display the balance — show the user's current wallet balance.
  5. Add withdrawals — let them withdraw funds.

Before you begin

Complete the User Setup guide first — you'll need a created and verified ProphetX user before continuing.

Complete the Session Token Setup guide — your backend signs short-lived JWTs that authorize the embed to act on behalf of each user.

Step 1: Set up the SDK

Install the package for your stack:

npm install @prophetx/sdk-react

Then set up the provider:

The ProphetXProvider wraps your component tree. The token prop is optional — you can mount the provider at app startup and pass the token once it's available. The SDK passes it to the iframe when any flow opens. If the token expires mid-flow, onSessionExpired lets you return a fresh one. Optional theme and locale props let you brand the embed and switch language ("en" or "es").

import { useState, useEffect, useCallback } from "react";
import { ProphetXProvider } from "@prophetx/sdk-react";
import { App } from "./App";

export function Root() {
  const [token, setToken] = useState<string>();

  // Calls YOUR backend token endpoint.
  const refreshToken = useCallback(async () => {
    const res = await fetch("/api/prophetx-token");
    const { token } = await res.json();
    setToken(token);
    return token;
  }, []);

  useEffect(() => { refreshToken() }, [refreshToken]);

  return (
    <ProphetXProvider
      environment="sandbox" // "production" | "staging" | "sandbox"
      token={token}
      onSessionExpired={refreshToken}
    >
      <App />
    </ProphetXProvider>
  );
}

Step 2: Open a deposit

With the provider in place, opening a deposit is a single function call. The SDK renders a modal that handles the full deposit flow. When the user completes it, onSuccess fires with a transactionId and amount — store the transactionId on your backend to track deposit status.

🚧

User Prerequisites

When the embed opens it validates the user's gates (kyc, terms, email-verify, phone-verify, not-suspended) via GET /embed/v1/token/validate. KYC is not exempt — it's a required gate for the deposit permission exactly like the others, so a session token alone does not imply KYC is complete. If any required gate is unmet, onError fires with a single generic code, PERMISSION_DENIED, carrying a pending_gates array naming which ones:

  • pending_gates includes terms — there's no SDK hook for this; host /tandc yourself in a raw iframe (see Terms flow). Terms acceptance genuinely is enforced on deposit/withdraw — every deposit/withdraw permission requires the terms gate, so a user who's never accepted anything will get PERMISSION_DENIED with terms in pending_gates, same as any other unmet gate.
  • pending_gates includes kyc — this user already exists (they hold an embed token), so route them to useKyc/useIdUpload (document verification), not useOneClickSignup (that's only for creating a brand-new user, before any token exists).
  • pending_gates includes email-verify / phone-verify — there's no embedded flow; handle these on your side (re-send verification email, OTP UI).
  • pending_gates includes not-suspended — not user-resolvable; direct the user to support.

onGeofenceBlocked(stateCode, reason) is also wired on this flow. In the current architecture, geo-eligibility is enforced when your backend mints the embed token rather than per-request, so this shouldn't fire in normal operation with a validly-issued token — but the SDK still defines and can deliver it, so wire a handler defensively.

Full prerequisite reference →

import { useDeposit } from "@prophetx/sdk-react";

export function DepositButton() {
  const { open, close, isOpen } = useDeposit();

  return (
    <button
      onClick={() =>
        open({
          onSuccess: (transactionId, amount) => {
            console.log("Deposited", amount, "tx:", transactionId);
          },
          onError: (code, message) => {
            console.error("Deposit error:", code, message);
          },
        })
      }
    >
      Deposit
    </button>
  );
}

Step 3: View transaction history

Once a user completes a deposit or withdrawal, you'll want to give them visibility into their recent transactions — including the current status of each one.

Rather than exposing this through the SDK, transaction history is served directly by the ProphetX backend, server-to-server — there is no embed-facing transactions endpoint. Your server calls GET /private/v1/transactions, then returns the results to your client to render in your UI. See Wallets for the full request/response shape, pagination, and the TransactionType enum.

The transactionId returned by onSuccess in the deposit and withdrawal flows corresponds to the refId query parameter on that endpoint, so you can filter straight to the ledger rows for one transaction.

Step 4: Display the balance

Use the wallet hook to fetch and display the user's current balance. refetch is manual — call it on mount and after any deposit or withdrawal completes.

import { useEffect } from "react";
import { useWallet } from "@prophetx/sdk-react";

export function BalanceDisplay() {
  const { balance, loading, error, refetch } = useWallet();

  useEffect(() => { refetch() }, [refetch]);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Failed to load balance.</p>;

  return (
    <p>{balance ? `${balance.totalBalance} ${balance.currency}` : "—"}</p>
  );
}

Step 5: Add withdrawals

Withdrawals follow the same pattern as deposits — same open() call, same callbacks, same onError codes, and the same gate-driven prerequisite check described in Step 2. Before the withdraw (or deposit) modal renders, the SDK validates the user's session token against the kyc/terms/email-verify/phone-verify/not-suspended gates. If any required gate is unmet, onError fires with PERMISSION_DENIED and a pending_gates array (which may include kyc, terms, or both) — and the withdraw UI never appears.

⚠️

Withdraw specifically may also surface a literal onError("KYC_REQUIRED", message) per the frontend SDK's own source — but that code's backend origin is unconfirmed (it doesn't appear anywhere in isv-backend). See the caveat in Error Codes before building against it; treat it the same as PERMISSION_DENIED with pending_gates: ["kyc"] if it does fire.

Handle this by checking pending_gates: if terms is missing, host /tandc yourself (no SDK hook exists for it); if kyc is missing, open useKyc/useIdUpload (this user already exists, so it's re-verification, not signup). Once the relevant success signal fires, retry the withdrawal.

import { useWithdraw } from "@prophetx/sdk-react";

export function WithdrawButton() {
  const { open, close, isOpen } = useWithdraw();

  return (
    <button
      onClick={() =>
        open({
          onSuccess: (transactionId, amount) => {
            console.log("Withdrew", amount, "tx:", transactionId);
          },
          onError: (code, message) => {
            console.error("Withdraw error:", code, message);
          },
        })
      }
    >
      Withdraw
    </button>
  );
}
📘

What your user sees when a gate is missing

If only terms is pending, host /tandc yourself in an iframe (no SDK hook exists for this — see Terms flow) to show just the T&C acceptance screen. If kyc is pending — this only happens for a user re-verifying after an earlier automated FAILURE, since a session token implies a ProphetX user already exists — useKyc/useIdUpload opens IDComply's document-upload flow in a second browser tab, not a phone/OTP screen.

Document verification terminates with one of three callbacks: onKycSuccess (verified — final, no further async step), onKycPending (still under review), or onKycFailed (rejected, with a retryable flag). Build your retry-withdrawal prompt off onKycSuccess only.

Full flow reference →

Once a user has cleared verification and accepted T&C, the kyc and terms gates report completed: true, PERMISSION_DENIED stops firing for those reasons, and future deposits and withdrawals open straight into the modal. ProphetX tracks verification state on the backend — you don't need to cache or check it yourself.

Next steps

Now that you have a working integration, explore these guides to polish it:



Did this page help you?