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