POST /widget/session
curl --request POST \
--url https://domain/widget/session \
--header 'Authorization: <authorization>' \
--header 'Content-Type: <content-type>' \
--data '
{
"partner_user_id": "<string>",
"user_email": "<string>",
"network": "<string>",
"currency": "<string>",
"amount": "<string>",
"type": "<string>",
"redirect_url": "<string>",
"client_ip": "<string>",
"address": "<string>"
}
'import requests
url = "https://domain/widget/session"
payload = {
"partner_user_id": "<string>",
"user_email": "<string>",
"network": "<string>",
"currency": "<string>",
"amount": "<string>",
"type": "<string>",
"redirect_url": "<string>",
"client_ip": "<string>",
"address": "<string>"
}
headers = {
"Authorization": "<authorization>",
"Content-Type": "<content-type>"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': '<content-type>'},
body: JSON.stringify({
partner_user_id: '<string>',
user_email: '<string>',
network: '<string>',
currency: '<string>',
amount: '<string>',
type: '<string>',
redirect_url: '<string>',
client_ip: '<string>',
address: '<string>'
})
};
fetch('https://domain/widget/session', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://domain/widget/session",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'partner_user_id' => '<string>',
'user_email' => '<string>',
'network' => '<string>',
'currency' => '<string>',
'amount' => '<string>',
'type' => '<string>',
'redirect_url' => '<string>',
'client_ip' => '<string>',
'address' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"Content-Type: <content-type>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://domain/widget/session"
payload := strings.NewReader("{\n \"partner_user_id\": \"<string>\",\n \"user_email\": \"<string>\",\n \"network\": \"<string>\",\n \"currency\": \"<string>\",\n \"amount\": \"<string>\",\n \"type\": \"<string>\",\n \"redirect_url\": \"<string>\",\n \"client_ip\": \"<string>\",\n \"address\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<authorization>")
req.Header.Add("Content-Type", "<content-type>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://domain/widget/session")
.header("Authorization", "<authorization>")
.header("Content-Type", "<content-type>")
.body("{\n \"partner_user_id\": \"<string>\",\n \"user_email\": \"<string>\",\n \"network\": \"<string>\",\n \"currency\": \"<string>\",\n \"amount\": \"<string>\",\n \"type\": \"<string>\",\n \"redirect_url\": \"<string>\",\n \"client_ip\": \"<string>\",\n \"address\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://domain/widget/session")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = '<content-type>'
request.body = "{\n \"partner_user_id\": \"<string>\",\n \"user_email\": \"<string>\",\n \"network\": \"<string>\",\n \"currency\": \"<string>\",\n \"amount\": \"<string>\",\n \"type\": \"<string>\",\n \"redirect_url\": \"<string>\",\n \"client_ip\": \"<string>\",\n \"address\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"merchant_transaction_id": "<string>",
"widget_id": "<string>",
"address": "<string>",
"signature": "<string>",
"init_token": "<string>",
"init_token_type": "<string>",
"currency": "<string>",
"currencies": [
"<string>"
],
"fix_currency": true,
"network": "<string>",
"networks": [
"<string>"
],
"fix_network": [
"<string>"
],
"amount": "<string>",
"fix_amount": true,
"fiat_currency": "<string>",
"fiat_currencies": [
"<string>"
],
"fix_fiat_currency": true,
"fix_fiat_amount": true,
"type": "<string>",
"lang": "<string>",
"theme": "<string>",
"redirect_url": "<string>"
}API Reference
POST /widget/session
Create a widget session and get parameters to launch the Proof widget
POST
/
widget
/
session
POST /widget/session
curl --request POST \
--url https://domain/widget/session \
--header 'Authorization: <authorization>' \
--header 'Content-Type: <content-type>' \
--data '
{
"partner_user_id": "<string>",
"user_email": "<string>",
"network": "<string>",
"currency": "<string>",
"amount": "<string>",
"type": "<string>",
"redirect_url": "<string>",
"client_ip": "<string>",
"address": "<string>"
}
'import requests
url = "https://domain/widget/session"
payload = {
"partner_user_id": "<string>",
"user_email": "<string>",
"network": "<string>",
"currency": "<string>",
"amount": "<string>",
"type": "<string>",
"redirect_url": "<string>",
"client_ip": "<string>",
"address": "<string>"
}
headers = {
"Authorization": "<authorization>",
"Content-Type": "<content-type>"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': '<content-type>'},
body: JSON.stringify({
partner_user_id: '<string>',
user_email: '<string>',
network: '<string>',
currency: '<string>',
amount: '<string>',
type: '<string>',
redirect_url: '<string>',
client_ip: '<string>',
address: '<string>'
})
};
fetch('https://domain/widget/session', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://domain/widget/session",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'partner_user_id' => '<string>',
'user_email' => '<string>',
'network' => '<string>',
'currency' => '<string>',
'amount' => '<string>',
'type' => '<string>',
'redirect_url' => '<string>',
'client_ip' => '<string>',
'address' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"Content-Type: <content-type>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://domain/widget/session"
payload := strings.NewReader("{\n \"partner_user_id\": \"<string>\",\n \"user_email\": \"<string>\",\n \"network\": \"<string>\",\n \"currency\": \"<string>\",\n \"amount\": \"<string>\",\n \"type\": \"<string>\",\n \"redirect_url\": \"<string>\",\n \"client_ip\": \"<string>\",\n \"address\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<authorization>")
req.Header.Add("Content-Type", "<content-type>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://domain/widget/session")
.header("Authorization", "<authorization>")
.header("Content-Type", "<content-type>")
.body("{\n \"partner_user_id\": \"<string>\",\n \"user_email\": \"<string>\",\n \"network\": \"<string>\",\n \"currency\": \"<string>\",\n \"amount\": \"<string>\",\n \"type\": \"<string>\",\n \"redirect_url\": \"<string>\",\n \"client_ip\": \"<string>\",\n \"address\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://domain/widget/session")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = '<content-type>'
request.body = "{\n \"partner_user_id\": \"<string>\",\n \"user_email\": \"<string>\",\n \"network\": \"<string>\",\n \"currency\": \"<string>\",\n \"amount\": \"<string>\",\n \"type\": \"<string>\",\n \"redirect_url\": \"<string>\",\n \"client_ip\": \"<string>\",\n \"address\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"merchant_transaction_id": "<string>",
"widget_id": "<string>",
"address": "<string>",
"signature": "<string>",
"init_token": "<string>",
"init_token_type": "<string>",
"currency": "<string>",
"currencies": [
"<string>"
],
"fix_currency": true,
"network": "<string>",
"networks": [
"<string>"
],
"fix_network": [
"<string>"
],
"amount": "<string>",
"fix_amount": true,
"fiat_currency": "<string>",
"fiat_currencies": [
"<string>"
],
"fix_fiat_currency": true,
"fix_fiat_amount": true,
"type": "<string>",
"lang": "<string>",
"theme": "<string>",
"redirect_url": "<string>"
}Creates a session for one widget transaction and returns every parameter the widget needs.
This endpoint must be called from your backend server, not from the browser. Your server adds the
client_token, reads the user’s real IP from the incoming request, and forwards it as client_ip. The response parameters are then passed to the frontend to launch the widget.
The response is shaped to be passed directly to proofWidget.run({...}). Do not modify the returned values; pass them through verbatim.
Request
Headers
string
required
Bearer token. Format:
Bearer <client_token>.string
required
application/jsonstring
The request
Origin header must match one of your registered origins, or it is rejected with 403 forbidden. See Onboarding → Authentication.Body
string
required
Your stable internal user identifier. Used for KYC mapping and transaction attribution.
string
required
User’s email. Used to find or create the user identity.
string
required
Blockchain network for the crypto leg. Examples:
TRC20, ERC20, BEP20.string
required
Crypto currency. Must be one of the currencies enabled for your integration. Proof confirms the list with you at onboarding.
string
required
Crypto amount as a decimal string (e.g.
"100", "99.50").string
"buy" or "sell". Overrides the partner default from widget_config.type. Off-ramp (sell) must be enabled for your integration.string
Overrides the partner default
redirect_url for this session only.string
The end user’s real IP address, as seen by your backend from the incoming request (
X-Forwarded-For or the connection’s remote address). Mixed into the session signature. Recommended — omitting it degrades fraud-signal quality.string
Destination wallet address. Send this only if Proof told you to during onboarding. Otherwise omit it — Proof resolves the address for you and any value you send is ignored.
Response
string
Stable identifier for this session. Save it — every status surface keys off it.
string
Your widget identifier. Pass as
widgetId to the widget.string
Resolved destination address. Pass as
address to the widget verbatim.string
Cryptographic signature, format
v2:<128-hex-chars>. Computed by Proof. Pass as signature to the widget verbatim.string
Single-use session token. Single-use, expires after first use or one hour. Pass as
initToken to the widget.string
Pass as
initTokenType to the widget.string
Crypto currency. Echo of the request.
string[]
Currencies offered in the widget’s currency selector. From
widget_config.currencies.boolean
Always
true. The currency is locked at the value you requested.string
Network. Echo of the request.
string[]
Networks offered in the widget’s network selector. From
widget_config.networks.string[]
Always
[network] — the network is locked at the value you requested.string
Crypto amount. Echo of the request.
boolean
Always
true. The amount is locked at the value you requested.string
Default fiat currency, from
widget_config.fiat_currency.string[]
Fiat currencies offered, from
widget_config.fiat_currencies.boolean
Always
false. The user can change the fiat currency inside the widget.boolean
Always
true.string
buy or sell. Resolved from the request body or widget_config.type.string
Widget language, from
widget_config.lang.string
Widget theme, from
widget_config.theme.string
Redirect URL after the transaction completes. Resolved from the request or
widget_config.redirect_url.Examples
# Called from your backend server
curl -X POST https://DOMAIN/widget/session \
-H "Authorization: Bearer <client_token>" \
-H "Content-Type: application/json" \
-d '{
"partner_user_id": "user-123",
"user_email": "user@example.com",
"network": "TRC20",
"currency": "USDT",
"amount": "100",
"type": "buy",
"client_ip": "203.0.113.42"
}'
// userIp — read from the incoming request on your server,
// e.g. req.headers["x-forwarded-for"]?.split(",")[0] || req.socket.remoteAddress
const session = await fetch("https://DOMAIN/widget/session", {
method: "POST",
headers: {
"Authorization": "Bearer <client_token>",
"Content-Type": "application/json"
},
body: JSON.stringify({
partner_user_id: "user-123",
user_email: "user@example.com",
network: "TRC20",
currency: "USDT",
amount: "100",
type: "buy",
client_ip: userIp
})
}).then(r => r.json());
Response
{
"merchant_transaction_id": "550e8400-e29b-41d4-a716-446655440000",
"widget_id": "fb359d09-fff6-4b1f-906c-b9062b135065",
"address": "TRx1a2b3c4d5e6f...",
"signature": "v2:a1b2c3d4e5f6...",
"init_token": "eyJhbGciOi...",
"init_token_type": "sdk_partner_authorization",
"currency": "USDT",
"currencies": ["USDT"],
"fix_currency": true,
"network": "TRC20",
"networks": ["TRC20", "ERC20"],
"fix_network": ["TRC20"],
"amount": "100",
"fix_amount": true,
"fiat_currency": "EUR",
"fiat_currencies": ["EUR", "USD"],
"fix_fiat_currency": false,
"fix_fiat_amount": true,
"type": "buy",
"lang": "en",
"theme": "light",
"redirect_url": "https://app.example.com/done"
}
Errors
See Error Handling. The most common session-creation errors:| HTTP | error | code | Cause |
|---|---|---|---|
| 400 | invalid_request | validation_error | A required field is missing or has the wrong type |
| 400 | invalid_request | currency_not_allowed | currency is not enabled for your integration |
| 400 | wallet_not_supported | WALLET_NOT_SUPPORTED | No active wallet for the requested currency + network — contact Proof to provision one |
| 401 | unauthorised | invalid_token | client_token is missing or inactive |
| 403 | forbidden | forbidden_origin | The request Origin is not on your allowlist |
| 502 | upstream_error | provider_error | An external service returned an error; retry with the same body |