Skip to main content
The Crypto Shop Backend emits Socket.io events to notify clients of transaction confirmations in real-time. This guide covers all available events and their payloads.

Event Types

Client → Server Events

Events that clients emit to the server:
EventDescriptionPayload
join-userJoin user-specific room for notificationsuserId: string

Server → Client Events

Events that the server emits to clients:
EventDescriptionTrigger
transaction:confirmedTransaction confirmed on blockchainTransaction listener detects confirmation

join-user Event

Clients emit this event to join a user-specific room and receive personalized notifications.

Client Implementation

import { io } from 'socket.io-client';

const socket = io('http://localhost:3000');

// Join user room after authentication
const userId = '507f1f77bcf86cd799439011'; // From your auth system
socket.emit('join-user', userId);

Server Handler

~/workspace/source/src/config/socket.js:17-20
socket.on('join-user', (userId) => {
  socket.join(`user:${userId}`);
  console.log(`[Socket] User ${userId} joined room user:${userId}`);
});
Payload:
userId: string // MongoDB ObjectId as string
Example:
socket.emit('join-user', '507f1f77bcf86cd799439011');
Users must join their room before receiving transaction:confirmed events. The room name follows the pattern user:{userId}.

transaction:confirmed Event

Emitted when a blockchain transaction reaches the required number of confirmations (21 blocks).

Server Implementation

~/workspace/source/src/config/socket.js:37-45
export const emitTransactionConfirmed = (userId, orderId, txHash) => {
  const io = getIO();
  io.to(`user:${userId}`).emit('transaction:confirmed', {
    orderId,
    txHash,
    message: 'Your purchase has been confirmed. You can now place new orders.',
    timestamp: new Date()
  });
};

Event Flow

Payload Structure

interface TransactionConfirmedPayload {
  orderId: string;      // MongoDB ObjectId of the confirmed order
  txHash: string;       // TRON transaction hash
  message: string;      // User-friendly message
  timestamp: Date;      // When the confirmation occurred
}

Example Payload

{
  "orderId": "65f8a9b2c3d4e5f6a7b8c9d0",
  "txHash": "7f3e8d9c2b1a5f4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e",
  "message": "Your purchase has been confirmed. You can now place new orders.",
  "timestamp": "2026-03-04T15:30:45.123Z"
}

Client Event Listeners

React Example

OrderPage.jsx
import { useEffect, useState } from 'react';
import { io } from 'socket.io-client';
import { toast } from 'react-toastify';

function OrderPage() {
  const [socket, setSocket] = useState(null);
  const userId = '507f1f77bcf86cd799439011'; // From auth context

  useEffect(() => {
    // Connect to Socket.io
    const newSocket = io('http://localhost:3000');
    setSocket(newSocket);

    // Join user room
    newSocket.emit('join-user', userId);

    // Listen for transaction confirmations
    newSocket.on('transaction:confirmed', (data) => {
      console.log('Transaction confirmed:', data);
      
      // Show success notification
      toast.success(data.message);
      
      // Update UI
      updateOrderStatus(data.orderId, 'completed');
      
      // Optionally redirect or refresh
      // window.location.href = `/orders/${data.orderId}`;
    });

    // Cleanup
    return () => {
      newSocket.off('transaction:confirmed');
      newSocket.disconnect();
    };
  }, [userId]);

  return (
    <div>
      {/* Your order page UI */}
    </div>
  );
}

Vue.js Example

OrderPage.vue
<script setup>
import { onMounted, onUnmounted, inject } from 'vue';
import { useToast } from 'vue-toastification';

const socket = inject('socket');
const toast = useToast();
const userId = '507f1f77bcf86cd799439011'; // From auth store

onMounted(() => {
  // Join user room
  socket.emit('join-user', userId);
  
  // Listen for confirmations
  socket.on('transaction:confirmed', (data) => {
    toast.success(data.message);
    console.log('Order confirmed:', data.orderId);
  });
});

onUnmounted(() => {
  socket.off('transaction:confirmed');
});
</script>

Vanilla JavaScript

const socket = io('http://localhost:3000');
const userId = '507f1f77bcf86cd799439011';

// Join user room
socket.emit('join-user', userId);

// Listen for transaction confirmations
socket.on('transaction:confirmed', (data) => {
  // Show notification
  showNotification({
    title: 'Transaction Confirmed!',
    message: data.message,
    type: 'success'
  });
  
  // Update order card in the DOM
  const orderCard = document.querySelector(`[data-order-id="${data.orderId}"]`);
  if (orderCard) {
    orderCard.classList.add('confirmed');
    orderCard.querySelector('.status').textContent = 'Completed';
  }
  
  // Log transaction details
  console.log('Transaction Hash:', data.txHash);
  console.log('Confirmed at:', data.timestamp);
});

Event Timing

Understanding when events are triggered:

Transaction Lifecycle

Key Timings:
  • Block time: ~3 seconds per block
  • Required confirmations: 21 blocks
  • Minimum time to confirmation: ~63 seconds
  • Listener check interval: 15 seconds (configured in /workspace/source/src/services/transactionListener.js:17)

Use Cases

1. Show Success Notification

socket.on('transaction:confirmed', (data) => {
  // Show toast notification
  toast.success(data.message, {
    duration: 5000,
    icon: '✓'
  });
});

2. Update Order Status in UI

socket.on('transaction:confirmed', (data) => {
  // Update order in state management (Redux/Vuex/etc.)
  dispatch({
    type: 'UPDATE_ORDER_STATUS',
    payload: {
      orderId: data.orderId,
      status: 'completed',
      confirmedAt: data.timestamp
    }
  });
});

3. Redirect to Order Details

socket.on('transaction:confirmed', (data) => {
  // Show notification and redirect
  toast.success('Payment confirmed! Redirecting...');
  
  setTimeout(() => {
    window.location.href = `/orders/${data.orderId}`;
  }, 2000);
});

4. Enable New Purchases

socket.on('transaction:confirmed', (data) => {
  // Re-enable purchase buttons
  const purchaseButtons = document.querySelectorAll('.purchase-btn');
  purchaseButtons.forEach(btn => {
    btn.disabled = false;
    btn.textContent = 'Buy Now';
  });
  
  // Update user's pending order status
  setHasPendingOrder(false);
});

5. Track Analytics

socket.on('transaction:confirmed', (data) => {
  // Send event to analytics
  analytics.track('Purchase Completed', {
    orderId: data.orderId,
    transactionHash: data.txHash,
    confirmedAt: data.timestamp
  });
  
  // Send to monitoring
  console.log('[Analytics] Purchase completed:', data.orderId);
});

Error Handling

Handle potential issues with event listeners:
socket.on('transaction:confirmed', (data) => {
  try {
    // Validate payload
    if (!data.orderId || !data.txHash) {
      console.error('Invalid transaction:confirmed payload:', data);
      return;
    }
    
    // Update UI
    updateOrderStatus(data.orderId, 'completed');
    
    // Show notification
    toast.success(data.message);
  } catch (error) {
    console.error('Error handling transaction:confirmed:', error);
    toast.error('Failed to process confirmation');
  }
});

// Handle connection errors
socket.on('connect_error', (error) => {
  console.error('Socket connection error:', error);
  toast.error('Real-time updates temporarily unavailable');
});

// Handle disconnections
socket.on('disconnect', (reason) => {
  console.warn('Socket disconnected:', reason);
  
  if (reason === 'io server disconnect') {
    // Server forcefully disconnected, reconnect manually
    socket.connect();
  }
});

Testing Events

Manual Testing

  1. Connect to server:
const socket = io('http://localhost:3000');
socket.on('connect', () => console.log('Connected'));
  1. Join user room:
const userId = '507f1f77bcf86cd799439011';
socket.emit('join-user', userId);
  1. Listen for events:
socket.on('transaction:confirmed', (data) => {
  console.log('Received event:', data);
});
  1. Trigger a transaction (make a purchase) and wait ~1 minute for confirmation

Simulating Events (Development)

For testing without waiting for blockchain confirmation:
server-side-test.js
import { getIO } from './src/config/socket.js';

// Emit test event
const io = getIO();
io.to('user:507f1f77bcf86cd799439011').emit('transaction:confirmed', {
  orderId: '65f8a9b2c3d4e5f6a7b8c9d0',
  txHash: 'test_transaction_hash_12345',
  message: 'TEST: Your purchase has been confirmed.',
  timestamp: new Date()
});

Best Practices

Prevent memory leaks by removing listeners when components unmount:
useEffect(() => {
  const handler = (data) => handleConfirmation(data);
  socket.on('transaction:confirmed', handler);
  
  return () => {
    socket.off('transaction:confirmed', handler);
  };
}, []);
Always validate incoming data before using it:
socket.on('transaction:confirmed', (data) => {
  if (!data?.orderId || !data?.txHash) {
    console.error('Invalid payload');
    return;
  }
  // Process valid data
});
Re-join user rooms after reconnection:
socket.on('connect', () => {
  if (userId) {
    socket.emit('join-user', userId);
  }
});
If Socket.io fails, poll the API as a fallback:
const pollOrderStatus = async (orderId) => {
  const response = await fetch(`/api/orders/${orderId}`);
  const order = await response.json();
  return order.status;
};

socket.on('connect_error', () => {
  // Fall back to polling every 15 seconds
  const interval = setInterval(() => {
    pollOrderStatus(currentOrderId);
  }, 15000);
});

Debugging

Enable Socket.io debugging for troubleshooting:
import { io } from 'socket.io-client';

const socket = io('http://localhost:3000', {
  // Enable debug logs
  debug: true
});

// Or use localStorage in browser
localStorage.debug = 'socket.io-client:*';
Server-side debugging:
DEBUG=socket.io:* npm run dev

Next Steps

Socket.io Setup

Configure Socket.io client connection

Transaction Listener

Understand transaction monitoring

Build docs developers (and LLMs) love