> ## Documentation Index
> Fetch the complete documentation index at: https://apidocs.royalti.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Real-Time Events Guide

> Connect via WebSocket to receive instant file processing status updates without polling

## Overview

Royalti.io provides real-time WebSocket events so your application can receive instant updates about file processing status. Instead of polling the job status endpoint, you can subscribe to events and get immediate notifications when processing starts, progresses, completes, or fails.

### Why Use WebSocket Events?

* **Instant Updates**: Receive status changes immediately without polling
* **Progress Tracking**: Get granular progress updates (every 10%) during processing
* **User-Scoped**: Events are sent only to the user who uploaded the file
* **Efficient**: Reduces API calls and improves user experience
* **Reliable**: Built on Socket.io with automatic reconnection

***

## Prerequisites

### Authentication

You need a valid JWT access token to connect to the WebSocket server. Use the same token you use for REST API calls.

### Socket.io Client

Install the Socket.io client for your platform:

<Tabs>
  <Tab title="Node.js">
    ```bash theme={null}
    npm install socket.io-client
    ```
  </Tab>

  <Tab title="Python">
    ```bash theme={null}
    pip install python-socketio
    ```
  </Tab>

  <Tab title="Browser">
    ```html theme={null}
    <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
    ```
  </Tab>
</Tabs>

***

## Quick Start

<Steps>
  <Step title="Connect to WebSocket">
    Establish a WebSocket connection with your JWT token:

    ```javascript Node.js theme={null}
    import { io } from 'socket.io-client';

    const socket = io('https://api.royalti.io', {
      auth: { token: 'your-jwt-access-token' },
      path: '/socket.io'
    });

    socket.on('connect', () => {
      console.log('Connected to Royalti.io WebSocket');
    });

    socket.on('connect_error', (error) => {
      console.error('Connection failed:', error.message);
    });
    ```
  </Step>

  <Step title="Subscribe to File Processing Events">
    Listen for file processing status updates:

    ```javascript Node.js theme={null}
    // Processing started
    socket.on('file:processing:started', (data) => {
      console.log(`Processing started: ${data.fileName}`);
      // Update UI to show processing state
    });

    // Progress updates (every ~10%)
    socket.on('file:processing:progress', (data) => {
      console.log(`Progress: ${data.progress}% - ${data.message}`);
      // Update progress bar
    });

    // Processing completed
    socket.on('file:processing:completed', (data) => {
      console.log(`Completed: ${data.fileName} in ${data.metadata.processingTimeMs}ms`);
      // Show success notification
    });

    // Processing failed
    socket.on('file:processing:failed', (data) => {
      console.error(`Failed: ${data.error}`);
      // Show error notification
    });
    ```
  </Step>

  <Step title="Upload a File">
    Upload a royalty file - events will automatically be sent to your WebSocket connection:

    ```javascript Node.js theme={null}
    // Request upload URL
    const response = await fetch('https://api.royalti.io/file/get-upload-url', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        autoDetect: true,
        fileMetaData: {
          originalname: 'spotify_royalties_2024_01.csv',
          mimetype: 'text/csv'
        }
      })
    });

    // Upload will trigger WebSocket events automatically
    ```
  </Step>
</Steps>

***

## Event Types

Royalti.io emits four types of file processing events:

| Event                       | When Emitted         | Contains                            |
| :-------------------------- | :------------------- | :---------------------------------- |
| `file:processing:started`   | Processing begins    | File ID, name, initial status       |
| `file:processing:progress`  | Every \~10% progress | Progress percentage, status message |
| `file:processing:completed` | Processing succeeds  | Final stats, processing time        |
| `file:processing:failed`    | Processing fails     | Error message, failure details      |

### Event Payload Structure

All events share a common payload structure:

```typescript theme={null}
interface FileProcessingEvent {
  fileId: string;          // UUID of the file
  fileName: string;        // Original filename
  status: 'started' | 'progress' | 'completed' | 'failed';
  progress?: number;       // 0-100 percentage
  message?: string;        // Human-readable status
  error?: string;          // Error message (failed only)
  metadata?: {
    rowsProcessed?: number;
    source?: string;
    accountingPeriod?: string;
    salePeriod?: string;
    processingTimeMs?: number;
  };
}
```

***

## Progress Stages

Progress updates are emitted at key processing stages. The exact stages depend on the file type:

### CSV/Text Files

| Progress | Stage                            |
| :------- | :------------------------------- |
| 0%       | Processing started               |
| 20%      | Configuration loaded             |
| 30-40%   | Table ready, validation complete |
| 50%      | BigQuery upload starting         |
| 80%      | BigQuery upload complete         |
| 100%     | Processing complete              |

### ZIP Files

| Progress | Stage                       |
| :------- | :-------------------------- |
| 0%       | Processing started          |
| 10%      | Downloading ZIP             |
| 30%      | ZIP downloaded              |
| 50%      | Extracting files            |
| 80%      | Files queued for processing |
| 100%     | Complete                    |

### Excel Files

| Progress | Stage                    |
| :------- | :----------------------- |
| 0%       | Processing started       |
| 20%      | Creating sheet processor |
| 50%      | Sheets discovered        |
| 100%     | Complete                 |

***

## Code Examples

### Complete Node.js Example

```javascript Node.js theme={null}
import { io } from 'socket.io-client';

class RoyaltiWebSocket {
  constructor(accessToken) {
    this.socket = io('https://api.royalti.io', {
      auth: { token: accessToken },
      path: '/socket.io',
      reconnection: true,
      reconnectionAttempts: 5,
      reconnectionDelay: 1000
    });

    this.setupEventHandlers();
  }

  setupEventHandlers() {
    this.socket.on('connect', () => {
      console.log('Connected to Royalti.io');
    });

    this.socket.on('disconnect', (reason) => {
      console.log('Disconnected:', reason);
    });

    this.socket.on('file:processing:started', (data) => {
      this.onProcessingStarted(data);
    });

    this.socket.on('file:processing:progress', (data) => {
      this.onProcessingProgress(data);
    });

    this.socket.on('file:processing:completed', (data) => {
      this.onProcessingCompleted(data);
    });

    this.socket.on('file:processing:failed', (data) => {
      this.onProcessingFailed(data);
    });
  }

  onProcessingStarted(data) {
    console.log(`[${data.fileId}] Started: ${data.fileName}`);
  }

  onProcessingProgress(data) {
    console.log(`[${data.fileId}] ${data.progress}% - ${data.message}`);
  }

  onProcessingCompleted(data) {
    console.log(`[${data.fileId}] Completed in ${data.metadata?.processingTimeMs}ms`);
    console.log(`  Rows processed: ${data.metadata?.rowsProcessed}`);
  }

  onProcessingFailed(data) {
    console.error(`[${data.fileId}] Failed: ${data.error}`);
  }

  disconnect() {
    this.socket.disconnect();
  }
}

// Usage
const ws = new RoyaltiWebSocket('your-jwt-token');
```

### Python Example

```python Python theme={null}
import socketio

sio = socketio.Client()

@sio.event
def connect():
    print('Connected to Royalti.io')

@sio.event
def disconnect():
    print('Disconnected')

@sio.on('file:processing:started')
def on_started(data):
    print(f"Started: {data['fileName']}")

@sio.on('file:processing:progress')
def on_progress(data):
    print(f"Progress: {data['progress']}% - {data.get('message', '')}")

@sio.on('file:processing:completed')
def on_completed(data):
    print(f"Completed: {data['fileName']}")
    if 'metadata' in data:
        print(f"  Processing time: {data['metadata'].get('processingTimeMs')}ms")

@sio.on('file:processing:failed')
def on_failed(data):
    print(f"Failed: {data.get('error', 'Unknown error')}")

# Connect with authentication
sio.connect(
    'https://api.royalti.io',
    auth={'token': 'your-jwt-token'},
    socketio_path='/socket.io'
)

# Keep connection alive
sio.wait()
```

### React Hook Example

```javascript React theme={null}
import { useEffect, useState, useCallback } from 'react';
import { io } from 'socket.io-client';

export function useFileProcessing(accessToken) {
  const [files, setFiles] = useState({});
  const [connected, setConnected] = useState(false);

  useEffect(() => {
    if (!accessToken) return;

    const socket = io('https://api.royalti.io', {
      auth: { token: accessToken },
      path: '/socket.io'
    });

    socket.on('connect', () => setConnected(true));
    socket.on('disconnect', () => setConnected(false));

    socket.on('file:processing:started', (data) => {
      setFiles(prev => ({
        ...prev,
        [data.fileId]: { ...data, status: 'processing' }
      }));
    });

    socket.on('file:processing:progress', (data) => {
      setFiles(prev => ({
        ...prev,
        [data.fileId]: { ...prev[data.fileId], ...data }
      }));
    });

    socket.on('file:processing:completed', (data) => {
      setFiles(prev => ({
        ...prev,
        [data.fileId]: { ...data, status: 'completed' }
      }));
    });

    socket.on('file:processing:failed', (data) => {
      setFiles(prev => ({
        ...prev,
        [data.fileId]: { ...data, status: 'failed' }
      }));
    });

    return () => socket.disconnect();
  }, [accessToken]);

  return { files, connected };
}

// Usage in component
function FileUploader() {
  const { files, connected } = useFileProcessing(accessToken);

  return (
    <div>
      <div>Status: {connected ? 'Connected' : 'Disconnected'}</div>
      {Object.values(files).map(file => (
        <div key={file.fileId}>
          {file.fileName}: {file.progress}% - {file.message}
        </div>
      ))}
    </div>
  );
}
```

***

## Best Practices

### Error Handling

Always handle connection errors and implement reconnection logic:

```javascript Node.js theme={null}
const socket = io('https://api.royalti.io', {
  auth: { token: accessToken },
  path: '/socket.io',
  reconnection: true,
  reconnectionAttempts: 5,
  reconnectionDelay: 1000,
  reconnectionDelayMax: 5000
});

socket.on('connect_error', (error) => {
  if (error.message === 'Authentication failed') {
    // Token may be expired - refresh it
    refreshToken().then(newToken => {
      socket.auth.token = newToken;
      socket.connect();
    });
  }
});
```

### Token Refresh

Update your token when it expires without losing the connection:

```javascript Node.js theme={null}
socket.on('disconnect', (reason) => {
  if (reason === 'io server disconnect') {
    // Server disconnected us - likely auth issue
    refreshToken().then(newToken => {
      socket.auth.token = newToken;
      socket.connect();
    });
  }
  // Otherwise Socket.io will automatically try to reconnect
});
```

### Cleanup

Always disconnect when your component unmounts or user logs out:

```javascript Node.js theme={null}
// On logout or component unmount
socket.disconnect();
```

***

## Combining with Notifications

WebSocket events provide real-time progress, while the notification system provides persistent records. Use both for a complete solution:

```javascript Node.js theme={null}
// Real-time progress via WebSocket
socket.on('file:processing:progress', (data) => {
  updateProgressBar(data.fileId, data.progress);
});

// Persistent notification on completion
socket.on('notification', (notification) => {
  if (notification.type === 'ROYALTY_FILE_PROCESSED') {
    addToNotificationCenter(notification);
  }
});
```

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Connection fails with 'Authentication failed'">
    **Cause**: Your JWT token is invalid or expired.

    **Solution**: Ensure you're using a valid access token. If using refresh tokens, exchange for a new access token before connecting.
  </Accordion>

  <Accordion title="Not receiving events after file upload">
    **Cause**: WebSocket connection may have dropped or file was uploaded by a different user.

    **Solution**:

    1. Verify your WebSocket is connected (`socket.connected`)
    2. Events are user-scoped - only the uploader receives them
    3. Check that you're listening to the correct event names
  </Accordion>

  <Accordion title="Receiving duplicate events">
    **Cause**: Multiple WebSocket connections or event handlers registered.

    **Solution**: Ensure you only create one socket connection per session and clean up event handlers when disconnecting.
  </Accordion>

  <Accordion title="Progress jumps from 0% to 100%">
    **Cause**: File processing completed very quickly (small file) or progress events are being batched.

    **Solution**: This is normal for small files. For larger files, you should see intermediate progress updates.
  </Accordion>
</AccordionGroup>

***

## API Reference

For REST API endpoints used with file uploads, see:

* [Get Upload URL](/api-reference/file/post-file-get-upload-url) - Request a signed URL for file upload
* [File Detection](/api-reference/file/post-file-detection-id) - Trigger source detection
* [Confirm Detection](/api-reference/file/post-file-confirm-detection) - Confirm and start processing
* [Processing Status](/api-reference/file/get-file-processing-id) - Poll job status (alternative to WebSocket)

***

## Support

If you encounter issues with WebSocket events:

* Check our [API Status Page](https://status.royalti.io) for service status
* Contact support at [api@royalti.io](mailto:api@royalti.io)
* Include your `fileId` and timestamp when reporting issues
