WebSocket events¶
API Maker runs a WebSocket server on the port of its APIs : 38246 on a server set up by Launchpad, wss:// behind its reverse proxy. A client connects with its tokens and subscribes to what it wants to hear about. Then it gets a message after each successful call of a subscribed API, on any server of the cluster, or when your code emits a WebSocket event. No polling, and no message broker of your own.
In short
- Your app keeps a WebSocket open to API Maker, and hears about changes as they happen : no asking again and again.
- It says what it wants to hear about : a table, an API, or an event of your code.
- It sends its tokens, so it hears only about what it may read.
- It works across every server of a cluster.
1. Connect¶
The tokens go in the query string of the WebSocket URL, with the same names as the request headers.
x-am-authorization: the API user, always. The person token of the auth provider of the events you subscribe to :x-am-user-authorization,x-google-authorization,x-azure-authorization,x-aws-authorizationorx-custom-authorization. API Maker finds out which provider a token belongs to ;&authProviders=name1,name2narrows it when you want.- The first message from the server is
CONNECTED:
- A bad token closes the connection with a
TOKEN_VALIDATIONmessage which says which header is wrong, then the close code1000with the reasonInvalid client.
2. Subscribe¶
Send a REGISTER message with the events you want, then read the answer : the ones accepted, with their eventId, and the ones refused with the reason.
| Key of an event | Meaning |
|---|---|
eventType |
INSTANCES, CUSTOM_APIS, SYSTEM_APIS or CUSTOM_WS_EVENTS. |
apiName |
The API id for a table (SCHEMA_GET_ALL, GEN_POST_BULK_INSERT…), the name of the custom API, the id of the system API (ENCRYPT_DATA…), or the name of the custom WebSocket event. |
instance, database, collection or table |
The table, for INSTANCES. |
condition |
{ conditionType: 'RESPONSE', criteria: { field: value } } : only the answers whose data has these values reach this client. For a custom event, the emitted data must match the criteria exactly, which is how one notification reaches one person. |
select |
The fields of the data to send, { field: 1 }. |
getEventData |
true sends the data ; false sends only the fact that it happened. |
- Subscriptions are checked against the groups of the API user and the auth provider of the event :
invalidOnEventssays why one was refused.
3. Receive¶
{ "type": "NOTIFICATION", "response": { "eventId": "…", "eventData": [ { "order_no": 1001, "status": "PAID" } ] } }
eventIdis the one of the register answer : it says which subscription the notification is for.eventDatais there when the subscription asked for it withgetEventData.- Notifications come from any server of the cluster : Redis passes them between the servers.
- Unsubscribe with the
eventIdof the register answer :
When a notification is sent¶
- After each successful call of a subscribed API. The answer of the call is the data, filtered by
conditionandselect. The call can come over HTTP or from your code (custom APIs, hooks, events…). A call that failed sends nothing : its status is 400 or more, or its answer says"success": false. - For a custom WebSocket event, when your code emits it :
POST /api/system-api/admin/emit-event-ws
{ "name": "default/orders-updated", "eventData": { "recipientId": "42", "order_no": 1001 } }
- To notify N people, emit once per person with their id in the data ; each client subscribes with its id in
criteria.
Custom WebSocket events¶
APIs & Logic → WebSockets lists the event names of the account. A custom event needs to exist there before it can be subscribed to or emitted.
| Field | Meaning |
|---|---|
| Name | The apiName of the subscription and the name given to emitEventWS. |
| Auth providers | The provider whose token a client must have validated to subscribe. Empty : the first auth provider of the account. |
| Can user connect code | A function which decides whether a client may subscribe : return { canConnect: true } or { canConnect: false, errorText: '…' }. It sees the tokens in g.req.auth. |
| Trigger on API | Optional : an API whose answer also emits this event. |
Who may subscribe¶
A subscription is taken when all of these hold, and invalidOnEvents says which one did not :
- The groups of the API user grant the event : every WebSocket event, or the ones the group names. That goes for the events of tables, of custom and system APIs, and for the custom WebSocket events of the account alike.
- The client connected with a token of an auth provider of the event, when the account has auth providers.
- The can user connect code of the event says yes, when the event has one.
What a client proved when it connected is looked at again while it stays connected :
| What changes | What the client sees |
|---|---|
| The API user is deactivated or removed, its password changes, or the account is deactivated. | A TOKEN_VALIDATION message which says why, then the close code 1000 (Invalid client). Its subscriptions are gone. |
| A token the client connected with runs out : the token of the API user, or its person token. | A TOKEN_VALIDATION message with the code 401 which names the token, then the close code 1000 (Invalid client). Its subscriptions are gone. See Staying connected. |
| The root user changes the secret the tokens of API users are signed with (root user settings). | A TOKEN_VALIDATION message with the code 401 which names the token, then the close code 1000 (Invalid client), for every client which connected with a token of the secret before. Its subscriptions are gone. See Staying connected. |
| A group stops granting an event, or the API user leaves the group. | { "type": "UNREGISTER", "response": { "eventId": "…" } } for each subscription which is taken back. The connection and the other subscriptions stay. |
| A group starts granting an event. | Nothing : the client can subscribe to it from then on, without connecting again. |
| A custom WebSocket event is removed. | { "type": "UNREGISTER", "response": { "eventId": "…" } } for its subscriptions. |
- A connection lasts as long as the first of its tokens : until the
expof the token of the API user and of the person token it connected with. A Google token counts five minutes longer, as its check does ; a token checked by the code of a custom provider counts until theexpthat code answers, when it answers one. A token without an end closes nothing.
The connection¶
- Ping : the server pings every client once a minute. A WebSocket client answers by itself (browsers and the usual libraries do, there is nothing to write), which keeps proxies from closing a quiet connection. A client may stay silent for hours ; one which answers nothing at all for two and a half minutes is taken for gone and closed.
- Close codes :
1000Invalid clientwhen the tokens do not let the client in, or do not any more (one of them ran out, the API user changed) ;1001Server is going awaywhen the server stops or restarts, which is no refusal. - Reconnect : subscriptions belong to the connection. When it closes, connect again and send
REGISTERagain ; after1001another server of the cluster takes the client at once. See Staying connected. - Size : a message of a client is at most 16 MiB (a
REGISTERis a few hundred bytes). A longer one ends the connection. - A client which does not read : what it is told waits for it up to 64 MiB, and is skipped past that until it reads again. The connection stays.
- Many clients : a client which waits costs a little memory and almost no CPU. How many a server holds, and what to look at before it holds them, is in Many clients.
Staying connected¶
A client stays connected for as long as it is reachable and its tokens are valid. It sends nothing for that : no request to repeat, no heartbeat of its own. The answers to the pings of the server are sent by the browser or the WebSocket library.
| What closes a connection | What the client sees | What it does |
|---|---|---|
| One of its tokens runs out. | A TOKEN_VALIDATION message with the code 401 which names the token, then the close code 1000. |
Gets a new token (with the refresh token, or a new login), connects again and sends REGISTER again. |
| The API user or the account is deactivated, or the password of the API user changes. | A TOKEN_VALIDATION message which says why, then the close code 1000. |
The same, once it has a token which is valid again. |
| The root user changes the secret the tokens of API users are signed with. | A TOKEN_VALIDATION message with the code 401 which names the token, then the close code 1000. |
Gets a new token, which is signed with the new secret, connects again and sends REGISTER again. A token of the secret before, and its refresh token, are not accepted any more : a new login. |
| The server stops or restarts. | The close code 1001 (Server is going away). |
Connects again with the same tokens : another server of the cluster takes it at once. |
| Its machine or its network is gone : nothing came from it for two and a half minutes, not even the answers to the pings. | Nothing : it is not there. | Connects again when it is back. |
{ "type": "TOKEN_VALIDATION", "errors": [ { "code": 401, "message": "The token of 'x-am-authorization' expired : connect again with a new token." } ] }
{ "type": "TOKEN_VALIDATION", "errors": [ { "code": 401, "message": "The secret the token of 'x-am-authorization' is signed with changed : connect again with a new token." } ] }
One handler covers all five : when the socket closes, come back with a token which is valid at that moment, and subscribe again.
-
Wait a random moment before connecting again : when a server stops, every client it held sees the close at the same instant. Clients which all come back after exactly one second arrive together ; with a random wait they arrive spread over a few seconds, and each one is let in sooner. See Many clients.
-
How long a token lasts :
expires_inof the token answer, 72 hours unlessexpiresInSecondsor the configuration say otherwise. The tokens of Google, Azure AD and AWS Cognito usually last an hour. - Nothing is sent again : what happens between the close and the new
REGISTERdoes not reach the client afterwards. A client which may miss nothing opens a second connection with its new token a little before the old token runs out, sendsREGISTERthere, and closes the first one once the second answered.
Many clients¶
A client which waits costs memory, and almost nothing else : its socket, its tokens and its subscriptions. How many clients a server holds is a question of its memory, not of its CPU.
| Server | Clients connected at once | Memory per client | New clients a second | A notification, p50 |
|---|---|---|---|---|
| 1 shared vCPU · 1 GB | 26,484 | 8.6 KB | 2,602 | 2.51 ms |
| 2 shared vCPUs · 4 GB | 338,016 | 8.5 KB | 4,041 | 1.45 ms |
| 4 shared vCPUs · 8 GB | 801,972 | 8.7 KB | 7,009 | 1.06 ms |
| 8 shared vCPUs · 32 GB | 3,662,564 | 8.8 KB | 10,939 | 0.94 ms |
| 2 dedicated vCPUs · 4 GB | 338,006 | 8.5 KB | 3,464 | 1.74 ms |
| 4 dedicated vCPUs · 8 GB | 795,888 | 8.7 KB | 6,627 | 1.04 ms |
| 8 dedicated vCPUs · 16 GB | 1,732,035 | 8.8 KB | 10,093 | 0.83 ms |
Measured on 14 October 2026 with API Maker 4.0.0 on Linode servers set up by the install script, MongoDB and Redis on the same server. Every client connected with both tokens and one subscription ; « new clients a second » connect, subscribe and leave ; the notification is for one person, from the HTTP call which emits it to the client. As API servers which share one Redis : 2,022,026 clients on 1 × 8 vCPU · 16 GB, 1,915,960 clients on 2 × 4 vCPU · 8 GB, 1,619,372 clients on 4 × 2 vCPU · 4 GB. Every run, with its memory second by second : WebSocket benchmarks.
- Count the servers from the memory : the clients one server holds, plus one server as a spare. Every server of a cluster takes clients. They share the Redis of API Maker, which carries each notification to the server that holds the client : a load balancer needs no sticky session.
- Leave room : keep a quarter of the memory free. Then the clients of a server that stops fit on the others.
- Clients come back together after a restart. API Maker lets them in a few hundred at a time. A client that waits a random moment before it connects again helps (see Staying connected).
- Too many WebSockets : a part of the connections stays kept for the calls of your APIs. A client that comes when the server is full is answered
503withRetry-After: 5. - A server that is killed cannot take its clients out of Redis. Another server does it within a minute.
- The limits of Linux (open files, the connection table of the firewall, the memory of TCP) and how API Maker and Launchpad raise them : see Scale to N users.
From your code¶
g.sys.system.emitEventWS(name, data)from any custom API, hook, event, scheduler or migration.- The sample custom API
/default/ws-notifyof a new account has the complete client snippet in its comments. - The system API does the same over HTTP.
Good to know¶
- The admin panel and the local client use the same server with their own tokens : what they ask and what they are told (the files of the account) is for them only, an API user is refused it.
- Groups grant WebSocket events : an API user subscribes to a custom event only when a group allows it.
- The WebSocket is on the port of the APIs : at the address of
BE_HOST_PORT, withws://forhttp://andwss://forhttps://. Behind Caddy, it is on the domain of the APIs.