WebSockets
The WebSocket API allows you to receive real-time updates about invoice status changes and payment events.
Connecting to the WebSocket
To connect to the WebSocket for a specific invoice, use the following URL:
wss://api.splitroute.com/api/v1/ws/invoices/{invoice_id}
Replace {invoice_id} with the ID of the invoice you want to monitor.
Message Format
Messages sent through the WebSocket are in JSON format with a standardized structure:
| 1 | { |
| 2 | "timestamp": "2025-03-31T09:18:11.897603+00:00", |
| 3 | "message_type": "invoice", |
| 4 | "category": "updated", |
| 5 | "payload": { |
| 6 | "id": "INV_2025_03_62fcb6bc256f6fad7622", |
| 7 | "timestamp": "2025-03-31T09:18:11.890364+00:00", |
| 8 | "event_type": "invoice.paid", |
| 9 | "unit_amount": "2000000000000000000000000", |
| 10 | "formatted_amount": "0.000002", |
| 11 | "nominal_amount": "0.000002", |
| 12 | "is_paid": true, |
| 13 | "is_expired": false, |
| 14 | "currency": "EUR", |
| 15 | "exchange_rate": "0.8221560000" |
| 16 | } |
| 17 | } |
Top-Level Fields
| Field | Type | Description |
|---|
timestamp | string | ISO format timestamp when the message was sent (includes timezone information) |
message_type | string | Either "invoice" or "payment" |
category | string | Event category: "updated", "completed", "failed", or "payment" |
payload | object | Contains event-specific data |
Payload Fields
The payload contains a set of fields that vary depending on the event type. All events share these common fields:
| Field | Type | Description |
|---|
id | string | Invoice ID (format: INV_YYYY_MM_[alphanumeric string]) |
timestamp | string | ISO format timestamp for the event (includes timezone information) |
event_type | string | Original event type (e.g., "invoice.paid") |
unit_amount | string | Raw amount in nano units (meaning depends on event type) |
formatted_amount | string | Human-readable amount in nano (meaning depends on event type) |
Event Types and Categories
The WebSocket API sends events in the following categories:
| Category | Description |
|---|
updated | Invoice has been updated |
completed | Invoice processing is complete |
failed | Invoice has failed or expired |
payment | Payment has been confirmed |
Available Event Types
The following specific event types can be received through the WebSocket:
| Event | Description |
|---|
invoice.created | Invoice has been created |
invoice.paid | Payment has been received |
invoice.expired | Invoice has expired without payment |
invoice.forwarded | Funds have been forwarded (if using forward_account) |
invoice.done | Invoice processing is complete |
payment.confirmed | A payment transaction has been confirmed |
Event Types and Their Specific Fields
The table below shows all available fields for each event type:
| Field | invoice.paid | invoice.forwarded | invoice.done | invoice.expired | payment.confirmed |
|---|
| Category | updated | updated | completed | failed | payment |
| Message Type | invoice | invoice | invoice | invoice | payment |
id | ✓ | ✓ | ✓ | ✓ | ✓ |
timestamp | ✓ | ✓ | ✓ | ✓ | ✓ |
event_type | ✓ | ✓ | ✓ | ✓ | ✓ |
unit_amount | ✓ (received) | ✓ (forwarded) | ✓ (received) | ✓ (usually 0) | ✓ (payment) |
formatted_amount | ✓ (received) | ✓ (forwarded) | ✓ (received) | ✓ (usually 0) | ✓ (payment) |
is_paid | ✓ (true) | ✓ (true) | ✓ (true) | ✓ (false) | - |
is_expired | ✓ (false) | ✓ (false) | ✓ (false) | ✓ (true) | - |
is_overpaid | - | ✓ (varies) | - | - | - |
currency | ✓ | ✓ | ✓ | ✓ | - |
exchange_rate | ✓ | ✓ | ✓ | ✓ | - |
nominal_amount | ✓ | ✓ | ✓ | ✓ | - |
transaction_id | - | - | - | - | ✓ |
Notes:
- ✓ indicates the field is present in the payload
- Fields with fixed values show the value in parentheses, e.g., ✓ (true)
- The meaning of the amount fields changes based on the event type (shown in parentheses)
- "-" indicates the field is not present in this event type
Field Descriptions
| Field | Description |
|---|
id | Invoice or payment identifier |
timestamp | ISO-formatted timestamp of the event (with timezone information) |
event_type | The specific event type (e.g., "invoice.paid") |
unit_amount | Raw amount in nano units |
formatted_amount | Human-readable amount in nano |
is_paid | Whether the invoice has been paid |
is_expired | Whether the invoice has expired |
is_overpaid | Whether the payment amount exceeded the required amount |
currency | Fiat currency code (e.g., "EUR", "USD") |
exchange_rate | The exchange rate used for conversion |
nominal_amount | Original amount in the invoice's fiat currency |
transaction_id | Blockchain transaction hash/identifier |
Amount Field Context
The amount fields have different meanings depending on the event type:
| Event Type | Meaning of Amount Fields |
|---|
invoice.paid | Total amount received |
invoice.forwarded | Amount forwarded to destination |
invoice.done | Final received amount |
invoice.expired | Amount received before expiry (usually 0) |
payment.confirmed | Payment amount (can be partial payment) |
Example Usage
Here's an example of how to connect to the WebSocket using JavaScript:
| 1 | const invoiceId = 'INV_2025_03_62fcb6bc256f6fad7622'; |
| 2 | const socket = new WebSocket(`wss://api.splitroute.com/api/v1/ws/invoices/${invoiceId}`); |
| 3 | |
| 4 | socket.onopen = function(e) { |
| 5 | console.log('Connected to WebSocket'); |
| 6 | }; |
| 7 | |
| 8 | socket.onmessage = function(event) { |
| 9 | const message = JSON.parse(event.data); |
| 10 | console.log(`Received message type: ${message.message_type}, category: ${message.category}`); |
| 11 | |
| 12 | // Check for invoice payment |
| 13 | if (message.payload.event_type === 'invoice.paid') { |
| 14 | console.log('Invoice has been paid!'); |
| 15 | console.log(`Received amount (formatted): ${message.payload.formatted_amount}`); |
| 16 | console.log(`Received amount (raw): ${message.payload.unit_amount}`); |
| 17 | // Update your UI or take other actions |
| 18 | } |
| 19 | |
| 20 | // Check for invoice completion |
| 21 | if (message.category === 'completed') { |
| 22 | console.log('Invoice processing completed'); |
| 23 | |
| 24 | // Close the connection as we're done |
| 25 | socket.close(); |
| 26 | } |
| 27 | }; |
| 28 | |
| 29 | socket.onclose = function(event) { |
| 30 | if (event.wasClean) { |
| 31 | console.log(`Connection closed cleanly, code=${event.code} reason=${event.reason}`); |
| 32 | } else { |
| 33 | console.log('Connection died'); |
| 34 | } |
| 35 | }; |
| 36 | |
| 37 | socket.onerror = function(error) { |
| 38 | console.log(`WebSocket error: ${error.message}`); |
| 39 | }; |
Real-World Message Examples
| 1 | { |
| 2 | "timestamp": "2025-03-31T09:18:04.221064+00:00", |
| 3 | "message_type": "payment", |
| 4 | "category": "payment", |
| 5 | "payload": { |
| 6 | "id": "INV_2025_03_62fcb6bc256f6fad7622", |
| 7 | "timestamp": "2025-03-31T09:18:04.211013+00:00", |
| 8 | "event_type": "payment.confirmed", |
| 9 | "unit_amount": "1000000000000000000000000", |
| 10 | "formatted_amount": "0.000001", |
| 11 | "transaction_id": "2C561F447FA7F0D986B2671FBDB42925F83FE3D4732FB69B400ED0488EA98622" |
| 12 | } |
| 13 | } |
| 1 | { |
| 2 | "timestamp": "2025-03-31T09:18:11.897603+00:00", |
| 3 | "message_type": "invoice", |
| 4 | "category": "updated", |
| 5 | "payload": { |
| 6 | "id": "INV_2025_03_62fcb6bc256f6fad7622", |
| 7 | "timestamp": "2025-03-31T09:18:11.890364+00:00", |
| 8 | "event_type": "invoice.paid", |
| 9 | "unit_amount": "2000000000000000000000000", |
| 10 | "formatted_amount": "0.000002", |
| 11 | "nominal_amount": "0.000002", |
| 12 | "is_paid": true, |
| 13 | "is_expired": false, |
| 14 | "currency": "EUR", |
| 15 | "exchange_rate": "0.8221560000" |
| 16 | } |
| 17 | } |
| 1 | { |
| 2 | "timestamp": "2025-03-31T09:18:13.595621+00:00", |
| 3 | "message_type": "invoice", |
| 4 | "category": "updated", |
| 5 | "payload": { |
| 6 | "id": "INV_2025_03_62fcb6bc256f6fad7622", |
| 7 | "timestamp": "2025-03-31T09:18:13.585571+00:00", |
| 8 | "event_type": "invoice.forwarded", |
| 9 | "unit_amount": "2000000000000000000000000", |
| 10 | "formatted_amount": "0.000002", |
| 11 | "nominal_amount": "0.000002", |
| 12 | "is_paid": true, |
| 13 | "is_expired": false, |
| 14 | "is_overpaid": false, |
| 15 | "currency": "EUR", |
| 16 | "exchange_rate": "0.8221560000" |
| 17 | } |
| 18 | } |
| 1 | { |
| 2 | "timestamp": "2025-03-31T09:18:13.596625+00:00", |
| 3 | "message_type": "invoice", |
| 4 | "category": "completed", |
| 5 | "payload": { |
| 6 | "id": "INV_2025_03_62fcb6bc256f6fad7622", |
| 7 | "timestamp": "2025-03-31T09:18:13.592363+00:00", |
| 8 | "event_type": "invoice.done", |
| 9 | "unit_amount": "2000000000000000000000000", |
| 10 | "formatted_amount": "0.000002", |
| 11 | "nominal_amount": "0.000002", |
| 12 | "is_paid": true, |
| 13 | "is_expired": false, |
| 14 | "currency": "EUR", |
| 15 | "exchange_rate": "0.8221560000" |
| 16 | } |
| 17 | } |
Python Example
| 1 | import websocket |
| 2 | import json |
| 3 | import threading |
| 4 | import time |
| 5 | |
| 6 | def on_message(ws, message): |
| 7 | data = json.loads(message) |
| 8 | print(f"Received event: {data['payload']['event_type']}") |
| 9 | |
| 10 | if data['payload']['event_type'] == 'invoice.paid': |
| 11 | print(f"Amount (formatted): {data['payload']['formatted_amount']}") |
| 12 | print(f"Amount (raw): {data['payload']['unit_amount']}") |
| 13 | |
| 14 | if data['category'] == 'completed': |
| 15 | print("Invoice processing completed") |
| 16 | ws.close() |
| 17 | |
| 18 | def on_error(ws, error): |
| 19 | print(f"Error: {error}") |
| 20 | |
| 21 | def on_close(ws, close_status_code, close_reason): |
| 22 | print(f"Connection closed: {close_reason}") |
| 23 | |
| 24 | def on_open(ws): |
| 25 | print("Connection established") |
| 26 | |
| 27 | if __name__ == "__main__": |
| 28 | invoice_id = "INV_2025_03_62fcb6bc256f6fad7622" |
| 29 | ws_url = f"wss://api.splitroute.com/api/v1/ws/invoices/{invoice_id}" |
| 30 | |
| 31 | ws = websocket.WebSocketApp(ws_url, |
| 32 | on_open=on_open, |
| 33 | on_message=on_message, |
| 34 | on_error=on_error, |
| 35 | on_close=on_close) |
| 36 | |
| 37 | wst = threading.Thread(target=ws.run_forever) |
| 38 | wst.daemon = True |
| 39 | wst.start() |
| 40 | |
| 41 | # Keep the main thread running |
| 42 | try: |
| 43 | while True: |
| 44 | time.sleep(1) |
| 45 | except KeyboardInterrupt: |
| 46 | ws.close() |
Connection Limits
- Each invoice can have up to 5 simultaneous WebSocket connections
- Connections will be automatically closed after 15 minutes (900 seconds) of inactivity
- If an invoice is marked as done or expired, all connections will be closed after a 5-minute grace period