Proof tracks the full lifecycle of every transaction. Use polling or WebSocket to stay updated.
Polling
curl https://DOMAIN/widget/transactions/{merchant_transaction_id} \
-H "Authorization: Bearer <client_token>"
Response:
{
"merchant_transaction_id": "550e8400-e29b-41d4-a716-446655440000",
"partner_user_id": "user-123",
"type": "buy",
"status": "completed",
"currency": "USDT",
"network": "TRC20",
"crypto_amount": "99.50",
"fiat_currency": "EUR",
"fiat_amount": "100.00",
"created_at": "2026-04-01T10:00:00Z",
"updated_at": "2026-04-01T10:03:45Z"
}
Recommended interval: every 5–10 seconds while status is pending or processing. Stop on terminal states.
Transaction Statuses
| Status | Description |
|---|
pending | Payment initiated, waiting for card processor confirmation |
processing | Payment confirmed, crypto being allocated and sent |
completed | Transaction complete. Crypto delivered (on-ramp) or fiat sent (off-ramp). |
failed | Payment or processing failed. User can retry with a new session. |
cancelled | User cancelled inside the widget |
completed, failed, and cancelled are terminal states. Stop polling when you receive one of these.
WebSocket
For real-time updates without polling, connect to the WebSocket endpoint:
const ws = new WebSocket(
"wss://DOMAIN/ws?token=<client_token>&user_id=<partner_user_id>"
);
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.event === "tx.update") {
console.log("Transaction:", msg.merchant_transaction_id);
console.log("Status:", msg.status);
}
};
ws.onclose = () => {
// Reconnect with exponential backoff
};
Message format:
{
"event": "tx.update",
"merchant_transaction_id": "550e8400-...",
"partner_user_id": "user-123",
"type": "buy",
"status": "completed",
"currency": "USDT",
"fiat_currency": "EUR",
"fiat_amount": "100.00",
"crypto_amount": "99.50",
"occurred_at": "2026-04-01T10:03:45Z"
}
Use WebSocket for the best user experience in web apps. Fall back to polling if the connection drops. Always implement reconnection with exponential backoff.
The widget also fires status updates inside the browser as the user progresses:
proofWidget.run({
// ... other params
onStatusChange: function(data) {
console.log(data.status); // widget-level status
console.log(data.merchantTransactionId);
}
});
data.status | Meaning |
|---|
new | Widget opened, user has not started payment |
pending | Payment initiated |
paid | Payment confirmed |
order_failed | Payment failed (card declined, 3DS failure) |
completed | Transaction complete |
cancelled | User closed widget before completing |
onStatusChange fires inside the browser and is informational only. For authoritative business logic, use the Proof Proxy API polling or WebSocket — do not rely solely on onStatusChange.
Retry Behavior
If a payment fails, the user can retry inside the widget without requesting a new session (as long as the init_token is still valid). Each retry may produce a new provider-level transaction identifier, but from your perspective the merchant_transaction_id stays the same throughout all retry attempts.