Connect
The API host depends on the data region of your project:The examples on this page use the EU host. If your project lives in another region, replace
https://api.gleap.io with your region’s host. API tokens are per project and only valid in the project’s region.authEndpoint (with your token as the Authorization header) and subscribe to channel. Include two custom auth params:
consumerIdnames the connecting instance ([A-Za-z0-9_-]{1,64}).typingEvents: trueopts this stream intoagent.typing.*(high volume; default off).
userAuthEndpoint (Pusher “signin”) — that is what allows the server to cleanly terminate a superseded connection during takeover.
Event data arrives as a JSON string of the same envelope webhooks receive — JSON.parse it (Pusher clients usually do this for you). Payloads larger than the socket limit arrive as a slim pointer (data.truncated: true with the conversation/message id) — fetch the full object over REST.
Exactly one consumer
Gleap allows one activeconsumerId per project. When a connection authorizes with a different consumerId, it takes over: the previous consumer’s connections are closed and a s2s.takeover control event is published on the channel.
Connections that share the same consumerId are the same consumer: none of them is taken over, and each receives every event. Use this to run the stream on several instances at once (active-active), so losing one instance loses no events. Deduplicate by the envelope id (for example SET NX in Redis with a short TTL) or make processing idempotent. typingEvents is a project-wide setting and the most recent auth wins, so use the same value on every instance.
Rolling deployments need no coordination either way: with the same consumerId old and new pods overlap and both receive events; with a new consumerId the new pod takes over and the old one is dropped.
Detecting a takeover
s2s.takeover goes out at the same moment as the disconnect, so it is best effort: it may arrive just before the close or not at all. It is a control event, not wrapped in the envelope:
consumerId with your own and ignore it when it matches. A taken-over connection is closed with code 4009 and reason You got disconnected by the app., which only reaches connections that completed user authentication.
You were taken over when you receive s2s.takeover with a foreign consumerId, or a close 4009 with that reason. Do not reconnect automatically in that case: two instances with different ids would keep evicting each other. Reconnect on every other close.
Disconnects
Every close initiated by the realtime server first sends apusher:error message ({ "code", "message" }) and then a close frame with the same code. Log both.
There is no maximum connection lifetime. A failed channel authorization (for example a revoked token) does not close the socket; it arrives as
pusher:subscription_error. In general the Pusher ranges apply: 4000–4099 fix the cause before reconnecting, 4100–4199 reconnect with backoff, 4200–4299 reconnect immediately.
Resync on every (re)connect — mandatory
A dropped connection loses the events published during the gap; there is no replay. After every connect — first connect, reconnect, and after a takeover — run per-conversation catch-up for anything you track:GET /v3/s2s/contacts/{userId}/conversationsfor contacts with open activity, and/orGET /v3/s2s/conversations/{id}/messages?after=<last message id you have>per conversation.