Skip to main content

Error Codes

When the server can't honor a client request but the connection should stay open, you get an error message:

{
"type": "error",
"requestId": "pub-2",
"code": "channel_not_authorized",
"message": "not authorized: other-app/room-1"
}

code is the stable machine-readable identifier — branch on this. message is human-readable for logging and is informational only.

Codes

codeWhat it meansWhen to retry
malformed_messageThe frame wasn't valid JSON, or didn't have the required type field.Never — fix the client.
unknown_typetype was a string but not one the server recognizes.Never — fix the client.
invalid_channelChannel name failed validation (empty, oversize, non-printable).Never — fix the input.
invalid_peer_idto field on a send failed peerId validation.Never.
channel_not_authorizedChannel is outside your key's channelPatterns.Never on this connection — either fix the key on the dashboard or use a different channel.
channel_reservedChannel uses a reserved prefix (_metered/, _internal/, _system/).Never — pick a different name.
channel_limit_exceededThis connection already holds 100 channel subscriptions.Unsubscribe from something first.
peer_not_foundsend target peer isn't online on this instance.Sometimes — they may reconnect. Don't loop on it.
missing_datapublish / send lacked the data field.Never.
action_not_permittedYour key's actions list doesn't include the operation you tried.Never on this connection — fix the key.
over_message_quotaPlan's maxMessagesPerPeriod exhausted AND overages are off / balance empty. Connection stays open, but publish/send keep failing until the period rolls over or the customer re-enables overages.After the period boundary; or after the customer changes their billing posture.
backend_errorTransient server-side fault while routing the message (Redis blip, in-memory backend race).Yes, after a short backoff. If persistent, open a support ticket.
internal_errorGeneric catch-all for unexpected server-side errors.Yes, after a short backoff.

Soft drop vs hard close

error is a soft drop — the connection stays open. The closes-the-connection cousins are:

  • 4001 invalid_token — auth failed at connect; no error message, just a close frame
  • 4011 over_message_rate — token-bucket abuse; close frame, not an error message
  • 4010 over_concurrent_limit — connect rejected; no error message
  • 4020 admin_disconnect — backend kicked the peer; close frame

See Close Codes for the close-side counterparts.

Correlating errors with requests

If your client sent a requestId on the original request, the server echoes it back on error. Use this to map the failure to the source action:

const pendingRequests = new Map();   // requestId → resolve/reject

function send(msg, requestId) {
return new Promise((resolve, reject) => {
pendingRequests.set(requestId, { resolve, reject });
ws.send(JSON.stringify({ ...msg, requestId }));
});
}

ws.on("message", (raw) => {
const msg = JSON.parse(raw.toString());
if (msg.type === "ack" && msg.requestId) {
pendingRequests.get(msg.requestId)?.resolve(msg);
pendingRequests.delete(msg.requestId);
}
if (msg.type === "error" && msg.requestId) {
pendingRequests.get(msg.requestId)?.reject(new Error(msg.code));
pendingRequests.delete(msg.requestId);
}
});

requestId is optional — if you don't send one, an error coming back without correlation is harder to attribute. We recommend always including a requestId for subscribe / publish / send if your code is doing anything beyond fire-and-forget.

Server-side dispatch errors (internal_error / backend_error)

Rare. Indicates a server-side fault (Redis blip, in-memory backend race). Safe to retry after a short backoff; if persistent, open a support ticket.