Deposits
Flow
create session → show deposit address → user pays → DEPOSIT webhook → you credit1. Create a deposit session
curl -X POST https://api.ensopay.io/session/deposit \
-H "Authorization: Bearer <OPERATOR_SECRET_KEY>" -H "Content-Type: application/json" \
-d '{ "externalUserId": "user-123", "lang": "en", "platformFiatCurrency": "USD", "userBalance": "0", "amount": "100", "redirectUrl": "https://cashier.example.com/deposit/return?order=order-123" }'
# → { "status": "success", "message": "Deposit session created", "data": { "url": "https://widget.ensopay.io/?sid=<SESSION_ID>", "sessionRef": "<SESSION_REF>" } }
# Store `sessionRef` — the DEPOSIT webhook echoes it so you can correlate the event to this session.Required: externalUserId, lang, platformFiatCurrency, userBalance (string, e.g. "0"), and amount — the expected deposit amount in your platform fiat. Optional: email (forwarded to KYC-requiring providers), operatorReference (your own order id — echoed on the webhook; a repeated value returns the same session instead of opening a new one), redirectUrl (the return address for this deposit session's full-page widget).
redirectUrl overrides the Redirect URL configured in your operator panel for this new session only; it does not change that setting or your webhook URL. Omit it or send null to use the panel default. If neither exists, the session has no return URL. Existing integrations do not need to change.
Send an absolute http:// or https:// URL that a browser can parse, at most 2048 characters, without embedded credentials (user:password@host) or literal NUL (U+0000) characters. Localhost is accepted for development. The domain may differ from the panel default, so you can send your current domain on each new session. Empty strings, whitespace, relative/protocol-less URLs, other schemes, invalid browser hostnames, non-string values and overlong URLs return 400 without creating a session. The supplied URL, including its query string and fragment, is stored unchanged; navigation encoding rules are described below.
Reusing an operatorReference for the same operator returns the original session and its original return URL, even if the new request or panel setting has a different URL. Use a new reference, or create a new session without a reference, to use a new return address. This request override is supported by /session/deposit only, not /session/withdraw.
externalUserId is your user id; it links every deposit back to the user. A deposit is an on-chain payment, so the amount the user actually sends can differ from amount — we echo your amount back on the DEPOSIT webhook as sessionAmount so you can reconcile it against the received value before crediting.
The widget url is short-lived — create the session when the user is ready to transact, not far in advance. An expired one returns SESSION_EXPIRED (see Errors); just create a new session. This applies to both deposit and withdraw sessions.
2. Embed the widget — drop the url returned in step 1 into an <iframe>; it drives the entire flow (network/token pick, address, QR, live status):
<iframe
src="https://widget.ensopay.io/?sid=<SESSION_ID>"
width="420" height="640" allow="clipboard-write">
</iframe>Use the exact data.url from the session response. Brand it to your look with a custom theme created in your panel, then append &theme=<slug>. There are no embedding restrictions — see operator-demo.ensopay.io for a live working embed.
Handle completion — the widget shows its own final screen (deposit confirmed, withdrawal request received, failed, etc.) with a Close button. Pressing it — or dismissing the widget — posts a close message to the parent page. Listen for it to close your iframe/modal (the same widget and message power both deposit and withdraw):
window.addEventListener('message', (event) => {
if (event.data?.type === 'EVENT' && event.data.payload?.event === 'close') {
const reason = event.data.payload.data?.reason;
// reason: deposit_success | withdraw_queued | deposit_failed | user_dismissed
// hide the iframe / return the user to your page.
// The webhook remains the source of truth for the actual result.
}
});Prefer a full-page redirect over an iframe? Send the user to the returned widget url as a full page instead. When they press Close or dismiss the widget, it navigates to the session's redirectUrl (request override or panel fallback), adding status=success or status=cancelled to the query string. An existing status parameter is replaced; query parameters are reserialized using standard browser URL handling, and the fragment is retained. This is not an automatic redirect when a deposit confirms. Without a return URL, the widget attempts to close the window.
Unlike the stored session URL, the navigation URL is not byte-for-byte preserved. For example, a space encoded as %20 can become +; invalid UTF-8 query bytes such as %FF become a replacement character (%EF%BF%BD). Use valid UTF-8 query values and do not rely on the original raw query encoding for signed return URLs.
In an iframe, the widget sends the close event and does not navigate to redirectUrl; your host page manages navigation. A redirect's status is not proof of payment — verify the signed DEPOSIT webhook before crediting.
3. On confirmation you receive a signed DEPOSIT webhook → verify it, credit amountInDollar to externalUserId.
Missed a deposit? Very rarely a payment may not be auto-detected (an unusual token route, a chain hiccup, a user sending to the wrong network, etc.). If a user insists they paid but wasn't credited, get their transaction hash and send it to us (or your admin enters it in the panel — see Management Panel). We verify it on-chain and replay it as a manual deposit, which fires the same signed DEPOSIT webhook — so you credit the user exactly as you would for any other deposit. No special handling on your side.