WebSocket Events

Connect to the WebSocket endpoint for real-time delivery of SMS status updates and inbound messages. WebSocket is an alternative to polling the status endpoint and complements webhook delivery.

When to use this

  • Track outbound message status changes in real time (queuedsending sent / failed).
  • Receive inbound SMS instantly without waiting for webhook delivery.
  • Build dashboards or monitoring interfaces with live message feeds.

When not to use this

  • Server-to-server integrations where webhooks are more reliable (no persistent connection needed).
  • Low-frequency polling is sufficient for your use case.

Connecting

Open a WebSocket connection to the /ws endpoint:

Connect
const ws = new WebSocket("wss://smsgateway-api.onrender.com/ws");

Authentication

Authentication happens in-band. After the connection opens, send your API key as the first message:

Authenticate
ws.onopen = () => {
ws.send(JSON.stringify({ type: "auth", apiKey: "YOUR_API_KEY" }));
};

The server responds with a confirmation:

Auth success
{
"type": "auth_ok"
}

If the API key is invalid:

Auth failure
{
"type": "auth_error",
"error": "Invalid API key"
}
You must authenticate within a few seconds of connecting. Unauthenticated connections do not receive any events.

Event types

sms_status

Published when an outbound message changes status:

sms_status — sent
{
"type": "sms_status",
"messageId": "msg_a1b2c3d4e5f6",
"appId": "app_abc123def456abcd",
"tenantId": "tenant_abc123def456abcd",
"status": "sent",
"rcMessageId": "123456789",
"timestamp": "2026-03-01T10:00:01.000Z"
}
sms_status — failed
{
"type": "sms_status",
"messageId": "msg_a1b2c3d4e5f6",
"appId": "app_abc123def456abcd",
"tenantId": "tenant_abc123def456abcd",
"status": "failed",
"error": "RingCentral not connected for this tenant",
"timestamp": "2026-03-01T10:00:02.000Z"
}

sms_inbound

Published when an inbound SMS is received:

sms_inbound
{
"type": "sms_inbound",
"messageId": "msg_c3d4e5f6a1b2",
"from": "+15559876543",
"to": "+15551234567",
"body": "Yes, I confirm",
"rcMessageId": "123456789",
"tenantId": "tenant_abc123def456abcd",
"timestamp": "2026-03-01T14:30:00.000Z"
}

ping

The server sends a heartbeat every 30 seconds to keep the connection alive:

ping
{
"type": "ping"
}

Event scoping

Events are filtered based on the authenticated connection's role:

  • app role — Receives events for messages matching the app's appId and tenantId.
  • admin role — Receives all events across all tenants and apps.

Reconnection

WebSocket connections can be dropped by network issues, server restarts, or idle timeouts. Implement automatic reconnection with exponential backoff:

Reconnection with backoff
let retryDelay = 1000;
const MAX_DELAY = 30000;
function connect() {
const ws = new WebSocket("wss://smsgateway-api.onrender.com/ws");
ws.onopen = () => {
retryDelay = 1000;
ws.send(JSON.stringify({ type: "auth", apiKey: "YOUR_API_KEY" }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === "ping") return;
if (data.type === "auth_ok") return;
// Handle sms_status, sms_inbound events
console.log("Event:", data);
};
ws.onclose = () => {
setTimeout(() => {
retryDelay = Math.min(retryDelay * 2, MAX_DELAY);
connect();
}, retryDelay);
};
}
connect();

Missed events

Events sent while the connection is down are not queued or replayed. To recover missed events after reconnecting:

  • Query GET /v1/sms/messages with a startDate filter to fetch messages created since the connection dropped.
  • Check individual message status with GET /v1/sms/status/:messageId for any in-flight messages.
For guaranteed delivery of inbound messages, configure a webhookUrl on your app in addition to using WebSocket. Webhooks and WebSocket events are delivered independently.

Best practices

  • Authenticate immediately — Send the auth message as soon as the connection opens to start receiving events.
  • Handle ping events — Acknowledge or ignore ping events gracefully. Do not treat them as errors.
  • Implement reconnection — Always reconnect with exponential backoff when the connection closes.
  • Parse defensively — Validate the type field before processing. Ignore unknown event types to maintain forward compatibility.

Related docs